Files
expresso/processevent_hook.cpp
T
2026-08-11 10:19:16 +02:00

113 lines
4.7 KiB
C++

// Standard PE hook :p
#include "expresso_recovered.h"
#include "overlay_state.h"
#include "platform_shim.h"
namespace expresso {
namespace data_rva_pe {
constexpr std::uint64_t kOrigProcessEvent = 0x5DE548; // MinHook trampoline
constexpr std::uint64_t kObserver = 0x5DE550; // -> 0x167610
constexpr std::uint64_t kBlocklistEnabled = 0x5DA7F8; // read at 0x16828C
} // namespace data_rva_pe
using ProcessEventNative = void (*)(uint64_t object, uint64_t function,
void* params);
using ObserverFn = void (*)(uint64_t object, uint64_t function);
extern ProcessEventNative g_origProcessEvent;
extern ObserverFn g_processEventObserver;
extern bool g_blocklistEnabled;
ProcessEventNative g_origProcessEvent = nullptr;
ObserverFn g_processEventObserver = nullptr;
bool g_blocklistEnabled = true; // .data initial value
IMemory& GameMemory();
// RVA 0x168210: a raw mov eax, [rcx+0x18]. The pointer was validated by the
// caller, so this one load is deliberately not routed through SafeReadInt32
int32_t ReadFunctionNameIndex(uint64_t function);
// RVA 0x167610, reconstructed as a pure state machine in identity_spoof.cpp
// (ClassifyIdentityEvent + OnIdentityEvent) this is the thin shim that
// resolves the UFunction name and feeds it in.
void IdentityEventObserver(uint64_t object, uint64_t function);
// RVA 0x168260 detour
void ProcessEventDetour(uint64_t object, uint64_t function, void* params) {
// passive feed (0x16827D)
// The observer receives every event in the process. It is called before the
// filter, so it sees blocked calls too
if (g_processEventObserver != nullptr)
g_processEventObserver(object, function); // 0x168287
// the censor (0x16828C..0x1682FE)
if (g_blocklistEnabled) { // 0x16828C
if (IsCanonicalUserPointer(function)) { // 0x1682AF
// Read UFunction::NamePrivate.ComparisonIndex directly note this
// is a *raw* load at +0x18 (0x168210), not one of the guarded
// accessors, because the pointer was validated one line above.
const int32_t name_index = ReadFunctionNameIndex(function);
if (static_cast<uint32_t>(name_index - 1) <= 0x1FFFFF) { // 0x1682CE
char name[0xB0];
GetNameString(GameMemory(), g_gameBase, name_index,
name, sizeof(name)); // 0x1682E1
if (IsBlockedIdentityFunction(name)) // 0x1682EA
return; // <-- the engine's own function never runs
}
}
}
// forward (0x168300..0x168313)
if (g_origProcessEvent != nullptr)
g_origProcessEvent(object, function, params); // 0x168313
}
// ===========================================================================
// RVA 0x16AC72..0x16ACF3 -- the installer tail
//
// Runs inside InstallRenderHooks (01_dxgi_present_hook.cpp) once Present and
// ResizeBuffers are live.
// ===========================================================================
bool InstallProcessEventHook() {
if (g_gameBase == 0) return false; // 0x16AC79
const uint64_t target = g_gameBase + game_rva::kProcessEvent; // 0x16AC7E
if (!IsCanonicalUserPointer(target)) return false; // 0x16AC96
void* const target_ptr = reinterpret_cast<void*>(target);
const minhook::Status st = minhook::CreateHook(
target_ptr, reinterpret_cast<void*>(&ProcessEventDetour),
reinterpret_cast<void**>(&g_origProcessEvent)); // 0x16ACBB
bool hooked = false;
if (st == static_cast<minhook::Status>(3)) {
// MH_ERROR_ALREADY_CREATED (0x16ACC0 `cmp eax, 3`). Someone else -- or a
// previous injection of this same DLL -- already owns the hook, and the
// cheat simply adopts the existing trampoline.
hooked = true;
} else if (st != minhook::kOk) {
// 0x16ACC9: on failure it stores the *raw target address* into the
// trampoline slot, so the cheat's own RPC path keeps working by calling
// ProcessEvent directly. The filter is lost; the offence is not.
g_origProcessEvent = reinterpret_cast<ProcessEventNative>(target);
} else if (minhook::EnableHook(target_ptr) == minhook::kOk) { // 0x16ACD5
hooked = true;
}
if (hooked) {
// 0x16ACE5: the cheat's outbound calls go through the trampoline, which
// means they skip its own blocklist.
g_processEvent = reinterpret_cast<uint64_t>(g_origProcessEvent);
}
// 0x16ACEC: set unconditionally, outside every branch above -- the observer
// is armed even when the hook did not install.
g_processEventObserver = &IdentityEventObserver;
return hooked;
}
} // namespace expresso