mirror of
https://github.com/ApfelTeeSaft/expresso.git
synced 2026-08-26 19:23:27 +00:00
113 lines
4.7 KiB
C++
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
|