// logic for finding local i think #include "expresso_recovered.h" #include 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(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