Files
expresso/engine_localplayer.cpp
2026-08-11 09:11:11 +02:00

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,
&params, sizeof(params)}; // 0x160265 r9d=0x40400
CallUFunction(mem, pawn, fn, &params, 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