// BunMain.CPP -- DLL entry point and initialization thread
//
// Loads settings from DDO_perf.ini, then installs all patches
// and hooks in the correct order on a background thread.
#include "pch.h"
#include "Logger.h"
#include "Config.h"
#include "Utils.h"
#include "AntiDebug.h"
#include "GamePatches.h"
#include "CrashHandler.h"
#include "DInput8Proxy.h"
#include "D3D9Hook.h"

static DWORD WINAPI PatchThread(LPVOID)
{
    char exeDir[MAX_PATH], iniPath[MAX_PATH];
    GetGameDir(exeDir, MAX_PATH);
    EnsureIniExists(exeDir, iniPath);

    int   debugging = 0;
    int   lightFix  = 0;
    int   wordwrap  = 1;
    int   dof       = 1;
    float shadowSizeMul = 1.0f;
    float fps = ReadIniSettings(iniPath, 100.0f, &g_logging, &debugging,
                                &lightFix, &wordwrap,
                                &g_disableIdealProcessor, &g_threadPriorityOverride,
                                &g_parallelWorkers, &g_minParallelJobs,
                                &g_workerIdleSleepMs,
                                &g_d3d9StateCache, &g_d3d9ShaderConstCache,
                                &dof, &shadowSizeMul);
    g_wordwrap      = wordwrap;
    g_dof           = dof;
    g_shadowSizeMul = shadowSizeMul;

    // Clamp all settings to valid ranges (canonical location -- no
    // other file should duplicate these clamps)
    if (g_threadPriorityOverride != 99) {
        if (g_threadPriorityOverride < THREAD_PRIORITY_IDLE)
            g_threadPriorityOverride = THREAD_PRIORITY_IDLE;
        else if (g_threadPriorityOverride > THREAD_PRIORITY_TIME_CRITICAL)
            g_threadPriorityOverride = THREAD_PRIORITY_TIME_CRITICAL;
    }
    if (g_parallelWorkers < 0) g_parallelWorkers = 0;
    if (g_parallelWorkers > 8) g_parallelWorkers = 8;
    if (g_minParallelJobs < 0) g_minParallelJobs = 0;
    if (g_workerIdleSleepMs < 0) g_workerIdleSleepMs = 0;
    if (g_workerIdleSleepMs > 127) g_workerIdleSleepMs = 127;
    if (g_shadowSizeMul < 1.0f) g_shadowSizeMul = 1.0f;
    if (g_shadowSizeMul > 4.0f)  g_shadowSizeMul = 4.0f;

    if (g_logging)
    {
        OpenLogFile();
        LogIf("=================================================");
        LogIf("Bunie's DDON Perf Patch Proxy Log");
        LogIf("=================================================");
        LogFmtIf("INI path       : %s", iniPath);
        LogFmtIf("Debugging      : %d", debugging);
        LogFmtIf("LightFix       : %d", lightFix);
        LogFmtIf("Wordwrap       : %d", g_wordwrap);
        LogFmtIf("DisableIdealProcessor : %d", g_disableIdealProcessor);
        LogFmtIf("ThreadPriorityOverride: %d", g_threadPriorityOverride);
        LogFmtIf("ParallelWorkers       : %d", g_parallelWorkers);
        LogFmtIf("MinParallelJobs       : %d", g_minParallelJobs);
        LogFmtIf("WorkerIdleSleepMs     : %d", g_workerIdleSleepMs);
        LogFmtIf("D3D9StateCache        : %d", g_d3d9StateCache);
        LogFmtIf("D3D9ShaderConstCache  : %d", g_d3d9ShaderConstCache);
        LogFmtIf("DOF            : %d (1=on, 0=off)", dof);
        LogFmtIf("ShadowSizeMul  : %.2f (1.0=default, >1=4K shadows)", g_shadowSizeMul);
        if (g_realDinput8) {
            char loadedPath[MAX_PATH] = { 0 };
            GetModuleFileNameA(g_realDinput8, loadedPath, MAX_PATH);
            LogFmtIf("dinput8        : %s", loadedPath);
        }

        float rawFps = fps;
        if (fps < 40.0f)  fps = 40.0f;
        if (fps > 500.0f) fps = 500.0f;
        if (rawFps != fps) LogFmtIf("FPS clamped: %.1f -> %.1f", rawFps, fps);
        else               LogFmtIf("FPS      : %.1f", fps);
        LogFmtIf("Frametime: %.8f  (1 / %.1f)", 1.0f / fps, fps);

        LogSystemInfo();
        LogLoadedModules();

        LogIf("-------------------------------------------------");
        LogIf("Pre-patch bytes:");
        HMODULE ntdll = GetModuleHandleA("ntdll.dll");
        LogFunctionBytes("NtQueryInformationProcess",
            GetProcAddress(ntdll, "NtQueryInformationProcess"));
        LogFunctionBytes("NtSetContextThread",
            GetProcAddress(ntdll, "NtSetContextThread"));
        if (lightFix)
            LogFunctionBytes("ShadowAllocator (017ac910)", (void*)0x017ac910UL);
    }
    else
    {
        if (fps < 40.0f)  fps = 40.0f;
        if (fps > 500.0f) fps = 500.0f;
    }

    float frametime = 1.0f / fps;

    InstallAntiDebugHooks();

    LogIf("-------------------------------------------------");
    if (lightFix)
        InstallLightFix();
    else
        LogIf("LightFix disabled (LightFix = 0 in INI).");

    if (g_wordwrap)
        InstallEnglishWordWrap();
    else
        LogIf("Wordwrap disabled (Wordwrap = 0 in INI).");

    if (!dof)
        InstallDOFDisable();
    else
        LogIf("DOF enabled (DOF = 1 in INI).");

    InstallSchedulerTweaks();
    InstallD3D9Hook();

    if (g_shadowSizeMul != 1.0f)
        InstallShadowResolution(g_shadowSizeMul);
    else
        LogIf("ShadowSizeMul default (1.0) - no shadow override.");

    ApplyMemoryPatches(fps, frametime, debugging);

    return 0;
}

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD reason, LPVOID)
{
    if (reason == DLL_PROCESS_ATTACH)
    {
        char exeDir[MAX_PATH] = { 0 };
        char iniPath[MAX_PATH] = { 0 };

        DisableThreadLibraryCalls(hInst);
        AddVectoredExceptionHandler(1, CrashHandler);
        LoadRealDinput8();
        GetGameDir(exeDir, MAX_PATH);
        EnsureIniExists(exeDir, iniPath);
        CreateThread(NULL, 0, PatchThread, NULL, 0, NULL);
    }
    if (reason == DLL_PROCESS_DETACH)
    {
        LogLightFixStats();
        if (g_realDinput8) FreeLibrary(g_realDinput8);
        if (g_logFile)     fclose(g_logFile);
    }
    return TRUE;
}
