// GamePatches.cpp -- Runtime memory patches for DDO.exe
//
// All game-specific patches live here: English word wrap, lockless
// shadow allocator (LightFix), scheduler tweaks, DOF disable, shadow
// resolution override, and the core FPS/frametime patches.
#include "GamePatches.h"
#include "Config.h"
#include "Hooks.h"
#include "Logger.h"
#include "Utils.h"
#include "AntiDebug.h"

// -------------------------------------------------------
// English word wrap for the chat layout engine
// -------------------------------------------------------
#define GUI_SYSTEM_BASE        0x022044a0UL
#define GUI_ALLOC_TOKEN        0x0148bed0UL
#define CHAT_WRAP_OVERFLOW_FN  0x01695510UL
#define TEXT_TOKEN_CHAR        0x04
#define TEXT_TOKEN_LINE        0x15

struct TextLayoutToken {
    BYTE            type;
    BYTE            flags;
    WORD            reserved02;
    TextLayoutToken* next;
    float           width;
    float           reserved0C;
    void*           payload;
    float           x;
    float           y;
    float           reserved1C;
    float           reserved20;
};

struct TextLayoutContext {
    void*           vtable;
    TextLayoutToken* tail;
    DWORD           flags;
    DWORD           reserved0C;
    void*           owner;
    DWORD           reserved14;
    DWORD           reserved18;
    DWORD           variant;
    DWORD           parseFlags;
    int             wrapLimit;
    DWORD           reserved28;
    DWORD           mode;
    float           maxWidth;
    float           maxHeight;
    float           lineAdvance;
    float           lineOffset;
    TextLayoutToken* head;
    TextLayoutToken* currentLine;
    TextLayoutToken* freeList;
};

C_ASSERT(offsetof(TextLayoutToken, next) == 0x04);
C_ASSERT(offsetof(TextLayoutToken, payload) == 0x10);
C_ASSERT(offsetof(TextLayoutToken, x) == 0x14);
C_ASSERT(offsetof(TextLayoutContext, flags) == 0x08);
C_ASSERT(offsetof(TextLayoutContext, maxWidth) == 0x30);
C_ASSERT(offsetof(TextLayoutContext, head) == 0x40);
C_ASSERT(offsetof(TextLayoutContext, freeList) == 0x48);

static Hook hookWrapOverflow = { 0 };

typedef void  (__fastcall* WrapOverflowFn)(TextLayoutContext*, void*, TextLayoutToken*);
typedef void* (__fastcall* GuiAllocTokenFn)(void*, void*, int);

static DWORD GetTokenCodepoint(const TextLayoutToken* token)
{
    if (!token || token->type != TEXT_TOKEN_CHAR || !token->payload)
        return 0;
    return *(const DWORD*)token->payload;
}

static BOOL IsAsciiWordChar(DWORD codepoint)
{
    return (codepoint >= '0' && codepoint <= '9') ||
           (codepoint >= 'A' && codepoint <= 'Z') ||
           (codepoint >= 'a' && codepoint <= 'z');
}

static TextLayoutToken* AcquireLayoutToken(TextLayoutContext* ctx)
{
    TextLayoutToken* token = ctx->freeList;
    if (token) {
        ctx->freeList = token->next;
        return token;
    }

    void* guiSystem = *(void**)GUI_SYSTEM_BASE;
    if (!guiSystem) {
        ctx->flags |= 0x10;
        return NULL;
    }

    token = (TextLayoutToken*)((GuiAllocTokenFn)GUI_ALLOC_TOKEN)(guiSystem, NULL, 0x10);
    if (!token)
        ctx->flags |= 0x10;
    return token;
}

static BOOL TryInsertEnglishLineBreak(TextLayoutContext* ctx, TextLayoutToken* lineToken)
{
    const float epsilon = 0.01f;
    TextLayoutToken* candidate = NULL;
    int asciiWords = 0;
    int asciiSpaces = 0;

    if (!ctx || !lineToken || lineToken->type != TEXT_TOKEN_LINE)
        return FALSE;
    if (lineToken->width <= (ctx->maxWidth + epsilon))
        return FALSE;

    for (TextLayoutToken* token = lineToken->next;
         token && token->type != TEXT_TOKEN_LINE;
         token = token->next)
    {
        DWORD codepoint = GetTokenCodepoint(token);
        if (codepoint == 0)
            continue;

        if (IsAsciiWordChar(codepoint)) {
            asciiWords++;
            continue;
        }

        if (codepoint != ' ')
            continue;

        TextLayoutToken* nextVisible = token->next;
        while (nextVisible && nextVisible->type != TEXT_TOKEN_LINE) {
            DWORD nextCodepoint = GetTokenCodepoint(nextVisible);
            if (nextCodepoint == ' ' || nextCodepoint == 0) {
                nextVisible = nextVisible->next;
                continue;
            }
            break;
        }
        if (!nextVisible || nextVisible->type == TEXT_TOKEN_LINE)
            continue;

        asciiSpaces++;
        if ((token->x + token->width) <= (ctx->maxWidth + epsilon))
            candidate = token;
    }

    if (!candidate || asciiWords == 0 || asciiSpaces == 0)
        return FALSE;

    TextLayoutToken* lineBreak = AcquireLayoutToken(ctx);
    if (!lineBreak)
        return FALSE;

    lineBreak->type = TEXT_TOKEN_LINE;
    lineBreak->flags = 0;
    lineBreak->reserved02 = 0;
    lineBreak->next = candidate->next;
    lineBreak->width = 0.0f;
    lineBreak->reserved0C = 0.0f;
    lineBreak->payload = NULL;
    lineBreak->x = 0.0f;
    lineBreak->y = 0.0f;
    lineBreak->reserved1C = 0.0f;
    lineBreak->reserved20 = 0.0f;
    candidate->next = lineBreak;
    return TRUE;
}

static void __fastcall MyWrapOverflow(TextLayoutContext* ctx, void*, TextLayoutToken* lineToken)
{
    if (!g_wordwrap || !TryInsertEnglishLineBreak(ctx, lineToken))
        ((WrapOverflowFn)hookWrapOverflow.trampoline)(ctx, NULL, lineToken);
}

void InstallEnglishWordWrap()
{
    if (hookWrapOverflow.original)
        return;

    LogIf("Installing English-aware chat word wrap...");
    InstallHook(&hookWrapOverflow,
        (void*)CHAT_WRAP_OVERFLOW_FN,
        (void*)MyWrapOverflow,
        7);
    LogFmtIf("  Chat wrap overflow hooked @ 0x%08X  trampoline @ 0x%08X",
        CHAT_WRAP_OVERFLOW_FN, (DWORD)hookWrapOverflow.trampoline);
}

// -------------------------------------------------------
// LightFix  -- lockless shadow slot allocator
// -------------------------------------------------------
#define SHADOW_POOL_BASE   0x02207a00UL
#define SHADOW_POOL_NEXT   0x022079c0UL
#define SHADOW_POOL_SIZE   32
#define SHADOW_SLOT_STRIDE 0x70
#define SHADOW_BUSY_OFFSET 0x68

static Hook          hookShadowAlloc = { 0 };
static volatile LONG g_lfTotalAllocs = 0;
static volatile LONG g_lfFallbackCount = 0;
static volatile LONG g_lfPeakBusy = 0;

typedef int(__cdecl* ShadowAllocFn)(void);

static int __cdecl MyShadowAlloc(void)
{
    BYTE* pool = (BYTE*)SHADOW_POOL_BASE;
    DWORD start = (*(DWORD*)SHADOW_POOL_NEXT) & 0x1f;

    if (g_logging) {
        int busy = 0;
        for (int i = 0; i < SHADOW_POOL_SIZE; i++)
            if (pool[i * SHADOW_SLOT_STRIDE + SHADOW_BUSY_OFFSET] != 0) busy++;
        LONG prev = g_lfPeakBusy;
        while (busy > prev)
            prev = _InterlockedCompareExchange(&g_lfPeakBusy, (LONG)busy, prev);
    }

    // Pre-scan with plain reads to skip busy slots cheaply,
    // then only use an interlocked CAS on a slot that looks free.
    for (DWORD i = 0; i < SHADOW_POOL_SIZE; i++)
    {
        DWORD slot = (start + i) & 0x1f;
        volatile char* busyFlag =
            (volatile char*)&pool[slot * SHADOW_SLOT_STRIDE + SHADOW_BUSY_OFFSET];

        // Plain read  -- satisfied from shared cache, no bus lock
        if (*busyFlag != 0)
            continue;

        // Looks free  -- atomic claim
        if (_InterlockedCompareExchange8((char*)busyFlag, 1, 0) == 0)
        {
            *(DWORD*)SHADOW_POOL_NEXT = (slot + 1) & 0x1f;
            if (g_logging) _InterlockedIncrement(&g_lfTotalAllocs);
            return (int)slot;
        }
    }

    if (g_logging) _InterlockedIncrement(&g_lfFallbackCount);
    return ((ShadowAllocFn)hookShadowAlloc.trampoline)();
}

void InstallLightFix()
{
    LogIf("Installing LightFix (lockless shadow allocator)...");
    void* pShadowAlloc = (void*)0x017ac910UL;
    InstallHook(&hookShadowAlloc, pShadowAlloc, (void*)MyShadowAlloc);
    LogFmtIf("  ShadowAlloc hooked @ 0x%08X  trampoline @ 0x%08X",
        (DWORD)pShadowAlloc, (DWORD)hookShadowAlloc.trampoline);
    LogFmtIf("  Pool: 0x%08X  %d slots x 0x%X bytes  busy flag @ +0x%X",
        SHADOW_POOL_BASE, SHADOW_POOL_SIZE, SHADOW_SLOT_STRIDE, SHADOW_BUSY_OFFSET);
    LogIf("  LightFix active. CriticalSection lock contention eliminated.");
    LogIf("  Session stats will be written to this log on exit.");
    LogIf("  To disable: set LightFix = 0 in DDO_perf.ini");
}

void LogLightFixStats()
{
    if (!g_logging || !g_logFile) return;
    Log("-------------------------------------------------");
    Log("LightFix session stats:");
    LogFmt("  Total lockless allocs : %ld", g_lfTotalAllocs);
    LogFmt("  Peak concurrent busy  : %ld / %d slots", g_lfPeakBusy, SHADOW_POOL_SIZE);
    LogFmt("  Fallbacks to original : %ld  (non-zero = pool was genuinely full)",
        g_lfFallbackCount);
    if (g_lfFallbackCount > 0)
        Log("  WARNING: Fallbacks occurred. Shadow pool saw higher pressure than expected.");
    else
        Log("  OK: Zero fallbacks. Lockless path handled all allocations.");
}

// -------------------------------------------------------
// Scheduler tweaks
// -------------------------------------------------------
#define JOB_DISPATCH_FN          0x0145eac0UL
#define WORKER_IDLE_SLEEP_INSN   0x0145e9f3UL

#ifndef MAXIMUM_PROCESSORS
#define MAXIMUM_PROCESSORS 32
#endif

static Hook hookSetThreadIdealProcessor = { 0 };
static Hook hookSetThreadPriority = { 0 };
static Hook hookJobDispatch = { 0 };

typedef DWORD(WINAPI* SetThreadIdealProcessor_t)(HANDLE hThread, DWORD dwIdealProcessor);
typedef BOOL (WINAPI* SetThreadPriority_t)(HANDLE hThread, int nPriority);
typedef void (__thiscall* JobDispatchFn)(void* self, int mode);

static DWORD ClampPositiveInt(int value, DWORD fallback, DWORD limit)
{
    if (value <= 0)
        return fallback;
    if ((DWORD)value > limit)
        return limit;
    return (DWORD)value;
}

static DWORD WINAPI MySetThreadIdealProcessor(HANDLE hThread, DWORD dwIdealProcessor)
{
    SetThreadIdealProcessor_t pfnSetThreadIdealProcessor =
        (SetThreadIdealProcessor_t)hookSetThreadIdealProcessor.trampoline;
    if (!pfnSetThreadIdealProcessor)
        return MAXDWORD;

    if (g_disableIdealProcessor)
        dwIdealProcessor = MAXIMUM_PROCESSORS;

    return pfnSetThreadIdealProcessor(hThread, dwIdealProcessor);
}

static BOOL WINAPI MySetThreadPriority(HANDLE hThread, int nPriority)
{
    SetThreadPriority_t pfnSetThreadPriority =
        (SetThreadPriority_t)hookSetThreadPriority.trampoline;
    if (!pfnSetThreadPriority)
        return FALSE;

    if (g_threadPriorityOverride != 99)
        nPriority = g_threadPriorityOverride;

    return pfnSetThreadPriority(hThread, nPriority);
}

static void __fastcall MyJobDispatch(void* self, void*, int mode)
{
    BYTE* scheduler = (BYTE*)self;
    DWORD* jobCount = (DWORD*)(scheduler + 0x7c);
    DWORD* workerCount = (DWORD*)(scheduler + 0x88);
    DWORD originalWorkers = *workerCount;
    DWORD restoredWorkers = originalWorkers;
    DWORD effectiveWorkers = originalWorkers;
    BOOL appliedOverride = FALSE;

    if (g_parallelWorkers > 0) {
        restoredWorkers = ClampPositiveInt(g_parallelWorkers, originalWorkers, 8);
        effectiveWorkers = restoredWorkers;
        appliedOverride = TRUE;
    }

    if (g_minParallelJobs > 1 && *jobCount < (DWORD)g_minParallelJobs) {
        effectiveWorkers = 1;
        appliedOverride = TRUE;
    }

    if (appliedOverride)
        *workerCount = effectiveWorkers;

    ((JobDispatchFn)hookJobDispatch.trampoline)(self, mode);

    if (appliedOverride)
        *workerCount = restoredWorkers;
}

void InstallSchedulerTweaks()
{
    // All globals are already clamped by PatchThread in BunMain

    if (!g_disableIdealProcessor &&
        g_threadPriorityOverride == 99 &&
        g_parallelWorkers == 0 &&
        g_minParallelJobs == 0 &&
        g_workerIdleSleepMs == 1)
    {
        LogIf("Scheduler tweaks disabled (all scheduler settings at defaults).");
        return;
    }

    LogIf("Installing scheduler tweaks...");

    if (g_disableIdealProcessor && !hookSetThreadIdealProcessor.original) {
        HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
        void* pSetThreadIdealProcessor = hKernel32
            ? (void*)GetProcAddress(hKernel32, "SetThreadIdealProcessor")
            : NULL;
        if (pSetThreadIdealProcessor) {
            InstallHook(&hookSetThreadIdealProcessor,
                pSetThreadIdealProcessor,
                (void*)MySetThreadIdealProcessor);
            LogIf("  DisableIdealProcessor active.");
        }
        else {
            LogIf("  WARNING: Failed to hook SetThreadIdealProcessor.");
        }
    }

    if (g_threadPriorityOverride != 99 && !hookSetThreadPriority.original) {
        HMODULE hKernel32 = GetModuleHandleA("kernel32.dll");
        void* pSetThreadPriority = hKernel32
            ? (void*)GetProcAddress(hKernel32, "SetThreadPriority")
            : NULL;
        if (pSetThreadPriority) {
            InstallHook(&hookSetThreadPriority,
                pSetThreadPriority,
                (void*)MySetThreadPriority);
            LogFmtIf("  ThreadPriorityOverride active: %d", g_threadPriorityOverride);
        }
        else {
            LogIf("  WARNING: Failed to hook SetThreadPriority.");
        }
    }

    if ((g_parallelWorkers > 0 || g_minParallelJobs > 1) && !hookJobDispatch.original) {
        InstallHook(&hookJobDispatch, (void*)JOB_DISPATCH_FN, (void*)MyJobDispatch, 6);
        LogFmtIf("  ParallelWorkers override: %d", g_parallelWorkers);
        LogFmtIf("  MinParallelJobs override: %d", g_minParallelJobs);
    }

    if (g_workerIdleSleepMs != 1) {
        BYTE sleepPatch[2] = { 0x6A, (BYTE)g_workerIdleSleepMs };
        WriteBytes(WORKER_IDLE_SLEEP_INSN, sleepPatch, sizeof(sleepPatch));
        LogFmtIf("  WorkerIdleSleepMs active: %d", g_workerIdleSleepMs);
    }
}

// ------------------------------------------------------------------
// DOF (Depth of Field) disable
//
// Six DOF filter activation thunks each contain:
//   F6 44 24 08 01   test byte [esp+8], 1   ; activation flag
//   74 15            jz  +0x15              ; skip if flag not set
//   68 <filter_obj>  push <filter object>
//   ...              (filter setup code)
//
// Patching the jz (0x74) -> jmp (0xEB) makes the jump unconditional,
// permanently skipping DOF filter activation for all filter types.
// ------------------------------------------------------------------
void InstallDOFDisable()
{
    LogIf("Installing DOF disable patches...");

    // --- Primary patches: conditional activation thunks ---
    // Each thunk has: test [esp+8],1 / jz +0x15.
    // Patching jz (0x74) -> jmp (0xEB) skips filter setup unconditionally.
    static const BYTE jmp_rel8 = 0xEB;
    WriteBytes(0x010B96BFUL, &jmp_rel8, 1);  // uDOFFilterAfterlife
    WriteBytes(0x010B970FUL, &jmp_rel8, 1);  // uDOFFilterEm021004
    WriteBytes(0x010B975FUL, &jmp_rel8, 1);  // uDOFFilterEvilEye
    WriteBytes(0x010B97AFUL, &jmp_rel8, 1);  // uDOFFilterExt
    WriteBytes(0x014A9B7DUL, &jmp_rel8, 1);  // uBokehFilter
    WriteBytes(0x014A9C8DUL, &jmp_rel8, 1);  // uDOFFilter

    // --- Secondary patches: vtable-dispatched activation callbacks ---
    // These are called indirectly through filter object vtables and
    // bypass the conditional test/jz guard above.  Writing xor eax,eax;
    // ret (33 C0 C3) at the entry makes them return NULL immediately,
    // which callers interpret as "filter not created."
    static const BYTE retNull[3] = { 0x33, 0xC0, 0xC3 };
    WriteBytes(0x010BB500UL, retNull, 3);  // uDOFFilterEm021004 (secondary)
    WriteBytes(0x010BB530UL, retNull, 3);  // uDOFFilterEvilEye (secondary)
    WriteBytes(0x010BB560UL, retNull, 3);  // uDOFFilterExt (secondary)
    WriteBytes(0x014DFFF0UL, retNull, 3);  // uDOFFilter (secondary)
    WriteBytes(0x014DFEC0UL, retNull, 3);  // uBokehFilter (secondary)

    LogIf("  DOF filters disabled (6 primary + 5 secondary patches).");
}

// ------------------------------------------------------------------
// Shadow Resolution Override (multiplier boost for non-cascade shadows)
//
// Boosts the sShadow runtime multiplier to upscale smaller shadow
// maps (spot/point lights).  Cascade shadows (sun) are handled
// separately by the D3D9 hook's transparent atlas upscaling.
//
// The multiplier lives at sShadow+0x1160, enable flag at +0x115C.
// sShadow singleton pointer at [0x02205C50].
// ------------------------------------------------------------------
void InstallShadowResolution(float sizeMul)
{
    LogFmtIf("Installing shadow resolution override (ShadowSizeMul = %.2f)...", sizeMul);

    // Wait for the sShadow singleton to be initialized.
    DWORD* pSingleton = (DWORD*)0x02205C50UL;
    DWORD sshadow = 0;
    for (int i = 0; i < 60; i++) {
        sshadow = *pSingleton;
        if (sshadow)
            break;
        Sleep(500);
    }

    if (!sshadow) {
        LogIf("  [WARNING] sShadow singleton never initialized -- multiplier not applied.");
        return;
    }

    BYTE*  enableFlag = (BYTE*)(sshadow + 0x115C);
    float* mulPtr     = (float*)(sshadow + 0x1160);

    float currentMul = (*enableFlag == 1) ? *mulPtr : 1.0f;
    float finalMul   = currentMul * sizeMul;

    *mulPtr     = finalMul;
    *enableFlag = 1;

    LogFmtIf("  sShadow at 0x%08X, game mul=%.2f, final=%.2f (%.1fx boost).",
             sshadow, currentMul, finalMul, sizeMul);
    LogIf("  Shadow resolution override applied.");
}

void ApplyMemoryPatches(float fps, float frametime, int debugging)
{
    LogIf("-------------------------------------------------");
    LogIf("Applying memory patches...");

    float prePatchFps = *(float*)0x00609CBEUL;
    if (prePatchFps != 100.0f) {
        BYTE* b = (BYTE*)0x00609CBEUL;
        LogFmtIf("  [WARNING] 0x00609CBE was not 100.0f! Current 4 bytes: %02X %02X %02X %02X",
            b[0], b[1], b[2], b[3]);
    }

    LogFmtIf("  [FPS cap]       0x00609CBE  value: %.1f", fps);
    WriteFloat(0x00609cbeUL, fps);

    LogFmtIf("  [Frametime 1/3] 0x01556FA4  value: %.8f", frametime);
    WriteFloat(0x01556fa4UL, frametime);

    LogFmtIf("  [Frametime 2/3] 0x01556FAE  value: %.8f", frametime);
    WriteFloat(0x01556faeUL, frametime);

    LogFmtIf("  [Frametime 3/3] 0x01556FC2  value: %.8f", frametime);
    WriteFloat(0x01556fc2UL, frametime);

    if (debugging) {
        LogIf("-------------------------------------------------");
        LogIf("Applying debugging patches...");
        static const BYTE nops[5] = { 0x90, 0x90, 0x90, 0x90, 0x90 };
        LogFmtIf("[SB patch]      0x00F09655  value: 90 90 90 90 90");
        WriteBytes(0x00F09655UL, nops, sizeof(nops));
        CreateThread(NULL, 0, PEBWatchdog, NULL, 0, NULL);
        LogIf("  SB patch applied, PEB watchdog started.");
    }

    LogIf("-------------------------------------------------");
    LogIf("All patches applied. Init complete.");
    LogIf("=================================================");
}
