// 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