mirror of
https://github.com/ApfelTeeSaft/expresso.git
synced 2026-08-26 19:23:27 +00:00
155 lines
5.6 KiB
C++
155 lines
5.6 KiB
C++
// logic for finding local i think
|
|
#include "expresso_recovered.h"
|
|
|
|
#include <cstring>
|
|
|
|
namespace expresso {
|
|
|
|
const char* const kPawnRoleNames[5] = {
|
|
"Counselor",
|
|
"jason",
|
|
"Slasher",
|
|
"Hunter",
|
|
"SCKiller",
|
|
};
|
|
|
|
const char* const kKillerRoleNames[5] = {
|
|
"jason",
|
|
"Slasher",
|
|
"SCKiller",
|
|
"Savini",
|
|
"Uber",
|
|
};
|
|
|
|
bool NameContains(const char* haystack, const char* needle) {
|
|
if (haystack == nullptr || needle == nullptr) return false;
|
|
return std::strstr(haystack, needle) != nullptr;
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool LooksLikeGameObject(const IMemory& mem, uint64_t object) {
|
|
if (!IsCanonicalUserPointer(object)) return false;
|
|
const uint64_t cls = SafeReadPtr(mem, object + uobject::kClassPrivate);
|
|
if (!IsCanonicalUserPointer(cls)) return false;
|
|
const int32_t idx = SafeReadInt32(mem, cls + uobject::kNameIndex);
|
|
return static_cast<uint32_t>(idx) > limits::kMinClassNameIdx; // 0x15B7DC
|
|
}
|
|
|
|
bool HasPlayerRoleName(const IMemory& mem, uint64_t game_base, uint64_t object) {
|
|
char name[0xB0];
|
|
if (!GetObjectClassName(mem, game_base, object, name, sizeof(name)))
|
|
return false;
|
|
for (size_t i = 0; i < kPawnRoleCount; ++i) {
|
|
if (NameContains(name, kPawnRoleNames[i]))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
uint64_t GetLocalPawn(const IMemory& mem, uint64_t game_base,
|
|
LocalPlayerCache& cache, uint64_t now_ms) {
|
|
const uint64_t pc = GetLocalPlayerController(mem, game_base, cache, now_ms);
|
|
if (pc == 0) return 0; // 0x15B77C
|
|
|
|
// slot A: PlayerController + 0x3E0 (0x15B785)
|
|
const uint64_t a = SafeReadPtr(mem, pc + pc_slot::kA);
|
|
if (LooksLikeGameObject(mem, a)) return a; // 0x15B7E1
|
|
|
|
// slot B: PlayerController + 0x6A0 (0x15B7E7)
|
|
const uint64_t b = SafeReadPtr(mem, pc + pc_slot::kB);
|
|
if (LooksLikeGameObject(mem, b)) return b; // 0x15B82F
|
|
|
|
// slot C: *(*(PlayerController + 0x3D0) + 0x20) (0x15B835/0x15B85A)
|
|
const uint64_t outer = SafeReadPtr(mem, pc + pc_slot::kC);
|
|
if (!IsCanonicalUserPointer(outer)) return 0;
|
|
const uint64_t c = SafeReadPtr(mem, outer + pc_slot::kCInner);
|
|
if (!IsCanonicalUserPointer(c)) return 0;
|
|
|
|
return HasPlayerRoleName(mem, game_base, c) ? c : 0; // 0x15B96A/0x15B945
|
|
}
|
|
|
|
bool IsLocalPawnKiller(const IMemory& mem, uint64_t game_base,
|
|
LocalPlayerCache& cache, uint64_t now_ms) {
|
|
const uint64_t pawn = GetLocalPawn(mem, game_base, cache, now_ms);
|
|
if (!IsCanonicalUserPointer(pawn)) return false; // 0x15BFB8
|
|
|
|
char name[0xB0];
|
|
if (!GetObjectClassName(mem, game_base, pawn, name, sizeof(name)))
|
|
name[0] = '\0'; // 0x15C019
|
|
|
|
for (size_t i = 0; i < kKillerRoleCount; ++i) {
|
|
if (NameContains(name, kKillerRoleNames[i]))
|
|
return true; // 0x15C0A1
|
|
}
|
|
return false; // 0x15C09D
|
|
}
|
|
|
|
AutoBlockResult AutoBlockTick(IMemory& mem, uint64_t game_base,
|
|
LocalPlayerCache& cache, uint64_t now_ms,
|
|
bool feature_enabled, bool menu_open_mirror,
|
|
const AutoBlockInput& input,
|
|
AutoBlockState& state,
|
|
ProcessEventFn invoke, void* user) {
|
|
if (!feature_enabled) // 0x160122
|
|
return AutoBlockResult::kDisabled;
|
|
|
|
const uint64_t pawn = GetLocalPawn(mem, game_base, cache, now_ms); // 0x16012D
|
|
if (!IsCanonicalUserPointer(pawn)) // 0x16013C
|
|
return AutoBlockResult::kNoPawn;
|
|
|
|
// Antagonists do not block, so the feature is suppressed for them
|
|
if (IsLocalPawnKiller(mem, game_base, cache, now_ms)) // 0x160151
|
|
return AutoBlockResult::kWrongRole;
|
|
|
|
// Three bytes that must all read false
|
|
if (SafeReadBool(mem, pawn + pawn::kGateE54) || // 0x16015E
|
|
SafeReadBool(mem, pawn + pawn::kGate1604) || // 0x160172
|
|
SafeReadBool(mem, pawn + pawn::kGate1060)) { // 0x160186
|
|
return AutoBlockResult::kPawnBusy;
|
|
}
|
|
|
|
if (menu_open_mirror) // 0x16019A
|
|
return AutoBlockResult::kMenuOpen;
|
|
|
|
// Held interact ('E'/'F') or attack (LMB/RMB) suppresses the block
|
|
if (input.interact_down || input.attack_down) // 0x1601F5/0x1601FE
|
|
return AutoBlockResult::kInputHeld;
|
|
|
|
// local flag true first, then tell the server. The local write is
|
|
// what makes the client's own prediction agree with the RPC it is about to
|
|
// send ,a server that trusts the RPC therefore sees a consistent client
|
|
SafeSetTrue(mem, pawn + pawn::kFlag1010); // 0x160206
|
|
|
|
// Frame-count rate limit rather than a wall-clock one: 30 ticks of the
|
|
// render loop, so the effective rate scales with frame rate
|
|
if (state.cooldown_frames > 0) { // 0x160218
|
|
--state.cooldown_frames; // 0x16021E
|
|
return AutoBlockResult::kCoolingDown;
|
|
}
|
|
|
|
const uint64_t fn =
|
|
FindFunctionByName(mem, game_base, pawn, "SERVER_OnBlock"); // 0x16025B
|
|
if (fn == 0) // 0x160260
|
|
return AutoBlockResult::kNoFunction;
|
|
|
|
struct { bool bBlocking; } params{true}; // 0x16026B
|
|
const ServerRpcCall call{"SERVER_OnBlock",
|
|
func_flags::kNative | func_flags::kPrivate,
|
|
¶ms, sizeof(params)}; // 0x160265 r9d=0x40400
|
|
CallUFunction(mem, pawn, fn, ¶ms, call.or_flags, invoke, user); // 0x16027B
|
|
|
|
state.cooldown_frames = limits::kAutoBlockCooldownFrames; // 0x160280
|
|
return AutoBlockResult::kFired;
|
|
}
|
|
|
|
bool ForcePawnFloatField(IMemory& mem, uint64_t pawn) {
|
|
if (!IsCanonicalUserPointer(pawn)) return false;
|
|
if (!SafeReadBool(mem, pawn + pawn::kGateFA0)) return false;
|
|
return SafeWriteFloat(mem, pawn + pawn::kFloatF24, 1.0f);
|
|
}
|
|
|
|
} // namespace expresso
|