#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <windows.h>
#include "MinHook.h"

static inline LONG AtomicRead(LONG *v) { return InterlockedCompareExchange(v, 0, 0); }
static inline void AtomicWrite(LONG *v, LONG n) { InterlockedExchange(v, n); }

FILE *logFile = NULL;
char g_outDir[MAX_PATH] = {0};

void Log(const char *fmt, ...) {
    if (!logFile) return;
    va_list ap; va_start(ap, fmt);
    SYSTEMTIME st; GetLocalTime(&st);
    fprintf(logFile, "[%02u:%02u:%02u.%03u] ", st.wHour, st.wMinute, st.wSecond, st.wMilliseconds);
    vfprintf(logFile, fmt, ap); fprintf(logFile, "\n");
    fflush(logFile);
    va_end(ap);
}

void InitLogFiles(void) {
    char p[MAX_PATH];
    GetModuleFileNameA(NULL, p, MAX_PATH);
    char *s = strrchr(p, '\\'); if (!s) s = strrchr(p, '/');
    if (s) *(s+1) = '\0'; else p[0] = '\0';
    strcpy(g_outDir, p);
    strcat(p, "proxy_p2p.log");
    logFile = fopen(p, "a");
    if (logFile) {
        setvbuf(logFile, NULL, _IOFBF, 1 << 16);
        SYSTEMTIME st; GetLocalTime(&st);
        fprintf(logFile, "\n===== Proxy (key-capture + scanner) %02u:%02u:%02u =====\n", st.wHour, st.wMinute, st.wSecond);
        fflush(logFile);
    }
}

void CloseLogFiles(void) {
    if (logFile) { fclose(logFile); logFile = NULL; }
}

void ProxySdkLogCallback(const void *msg) {
    Log("[SDK] log callback");
    (void)msg;
}

typedef int32_t EOS_EResult;
#define EOS_Success 0
typedef void *EOS_ProductUserId;

typedef void (*TickFn)(void*);
typedef EOS_EResult (*SetLogCbFn)(void*);
typedef EOS_EResult (*SetLogLvlFn)(int32_t, int32_t);

static TickFn origTick = NULL;
static SetLogCbFn origSetLogCb = NULL;
static SetLogLvlFn origSetLogLvl = NULL;

struct AuthLoginCbInfo {
    int32_t rc; void *cd; void *lu; void *p1; void *p2; void *p3; void *sa;
};
typedef void (*AuthLoginCb)(const struct AuthLoginCbInfo *);

struct ConnectLoginCbInfo {
    int32_t rc; void *cd; void *lu; void *ct;
};
typedef void (*ConnectLoginCb)(const struct ConnectLoginCbInfo *);

// Key capture GenerateKey (RVA 0x25F8650)
// IL2CPP byte[] layout: klass(8) + monitor(8) + length(8) + data
#define GENKEY_RVA       0x25F8650ULL
#define BYTE_LEN(p)      (*(int32_t *)((uint8_t *)(p) + 0x18))
#define BYTE_DATA(p)     ((uint8_t *)(p) + 0x20)

// RVA definitions for hooks
#define GENERATEIV_RVA       0x25F8720ULL
#define GENERATEKEY_STR_RVA  0x25F86E0ULL
#define LOADLOCAL_RVA        0x25E93C0ULL

// IL2CPP string layout: klass(8) + monitor(8) + length(int32_t@+0x10) + chars(utf-16@+0x14)
#define STR_LEN(p)    (*(int32_t*)((uint8_t*)(p) + 0x10))
#define STR_CHARS(p)  ((uint16_t*)((uint8_t*)(p) + 0x14))

static LONG g_keyDone = 0, g_claimed = 0;
static LONG g_genKeyCallCount = 0;
typedef uint8_t *(*GenKeyFn)(void*);

static void WriteKeyRaw(const uint8_t *d, uint32_t len, const char *suffix) {
    char p[MAX_PATH];
    snprintf(p, sizeof(p), "%saes_key_%s.bin", g_outDir, suffix);
    FILE *f = fopen(p, "wb");
    if (f) { uint32_t n = len < 64 ? len : 64; fwrite(d, 1, n, f); fclose(f); }
    Log("[Key] Wrote %u bytes to %s", len < 64 ? len : 64, p);
}

static void LogHex(const uint8_t *d, uint32_t len, const char *label) {
    char hex[256] = {0};
    for (uint32_t i = 0; i < len && i < 32; i++)
        sprintf(hex + i*3, "%02X ", d[i]);
    Log("[Key] %s (%u bytes): %s", label, len, hex);
}

static void CaptureGenerateKey(void) {
    HMODULE ga = GetModuleHandleA("GameAssembly.dll");
    if (!ga) { Log("[Key] GameAssembly.dll not loaded yet"); return; }
    GenKeyFn fn = (GenKeyFn)((uint8_t*)ga + GENKEY_RVA);
    uint32_t callNum = (uint32_t)InterlockedIncrement(&g_genKeyCallCount);
    Log("[Key] Calling GenerateKey (call #%u) at %p", callNum, (void*)fn);
    uint8_t *r = fn(NULL);
    if (!r) { Log("[Key] GenerateKey returned NULL"); return; }
    int32_t len = BYTE_LEN(r);
    uint8_t *d = BYTE_DATA(r);
    Log("[Key] GenerateKey #%u: %d bytes", callNum, len);
    if (len > 0 && len <= 256) {
        LogHex(d, (uint32_t)len, "GenerateKey result");
        char suffix[32];
        snprintf(suffix, sizeof(suffix), "genkey_%u", callNum);
        WriteKeyRaw(d, (uint32_t)len, suffix);
        if (!AtomicRead(&g_keyDone)) {
            WriteKeyRaw(d, (uint32_t)len, "raw");
            AtomicWrite(&g_keyDone, 1);
            Log("[Key] *** Primary key captured! ***");
        }
    }
}

static void TryCaptureKeys(void) {
    if (AtomicRead(&g_keyDone)) return;
    if (InterlockedCompareExchange(&g_claimed, 1, 0)) return;
    CaptureGenerateKey();
    if (!AtomicRead(&g_keyDone)) AtomicWrite(&g_claimed, 0);
}

// Hooks for GenerateKey(), GenerateIV, GenerateKey(string), LoadLocal
static void *g_original_GenerateKey = NULL;
static void *g_original_GenerateIV = NULL;
static void *g_original_GenerateKey_str = NULL;
static void *g_original_LoadLocal = NULL;

static LONG g_generateKey_count = 0;
static LONG g_generateIV_count = 0;
static LONG g_generateKey_str_count = 0;
static LONG g_loadLocal_count = 0;

static void WriteStringToFile(const char *filename, const uint16_t *chars, int32_t len) {
    char p[MAX_PATH];
    snprintf(p, sizeof(p), "%s%s", g_outDir, filename);
    FILE *f = fopen(p, "w"); if (!f) return;
    for (int i = 0; i < len; i++) fputc((char)(chars[i] < 128 ? chars[i] : '?'), f);
    fclose(f);
    Log("[Hook] Wrote %s", filename);
}

static void* GenerateIV_hook(void *ivSource, void *method) {
    uint32_t callNum = (uint32_t)InterlockedIncrement(&g_generateIV_count);
    if (ivSource) {
        int32_t len = STR_LEN(ivSource);
        uint16_t *chars = STR_CHARS(ivSource);
        char buf[512] = {0};
        for (int i = 0; i < len && i < 255; i++)
            buf[i] = (char)(chars[i] < 128 ? chars[i] : '?');
        Log("[Hook] GenerateIV #%u: \"%s\" (len=%d chars)", callNum, buf, len);
        if (callNum == 1) WriteStringToFile("iv_source.txt", chars, len);
    } else {
        Log("[Hook] GenerateIV #%u: NULL", callNum);
    }
    return ((void* (*)(void*, void*))g_original_GenerateIV)(ivSource, method);
}

static void* GenerateKey_str_hook(void *keySource, void *method) {
    uint32_t callNum = (uint32_t)InterlockedIncrement(&g_generateKey_str_count);
    if (keySource) {
        int32_t len = STR_LEN(keySource);
        uint16_t *chars = STR_CHARS(keySource);
        char buf[512] = {0};
        for (int i = 0; i < len && i < 255; i++)
            buf[i] = (char)(chars[i] < 128 ? chars[i] : '?');
        Log("[Hook] GenerateKey(string) #%u: \"%s\" (len=%d chars)", callNum, buf, len);
        if (callNum == 1) WriteStringToFile("key_source.txt", chars, len);
    } else {
        Log("[Hook] GenerateKey(string) #%u: NULL", callNum);
    }
    return ((void* (*)(void*, void*))g_original_GenerateKey_str)(keySource, method);
}

// Hook for GenerateKey() no-arg (RVA 0x25F8650) - logs when bundle loading triggers key derivation
static void* GenerateKey_noarg_hook(void *method) {
    uint32_t callNum = (uint32_t)InterlockedIncrement(&g_generateKey_count);
    void *result = ((void* (*)(void*))g_original_GenerateKey)(method);
    if (result) {
        int32_t len = BYTE_LEN(result);
        Log("[Hook] GenerateKey() #%u: %d bytes", callNum, len);
        LogHex(BYTE_DATA(result), (uint32_t)(len > 32 ? 32 : len), "GenerateKey() result");
        if (callNum == 1) {
            WriteKeyRaw(BYTE_DATA(result), (uint32_t)len, "genkey_hook_1");
        }
    }
    return result;
}

// Hook for EncryptAssetBundleResource.LoadLocal (RVA 0x25E93C0)
static void LoadLocal_hook(void *this_ptr, void *method) {
    uint32_t callNum = (uint32_t)InterlockedIncrement(&g_loadLocal_count);
    Log("[Hook] EncryptAssetBundleResource.LoadLocal #%u called (this=%p)", callNum, this_ptr);
    if (this_ptr) {
        // path field at offset +0x48 (Il2CppString*)
        void *pathStr = *(void**)((uint8_t*)this_ptr + 0x48);
        if (pathStr) {
            int32_t len = STR_LEN(pathStr);
            uint16_t *chars = STR_CHARS(pathStr);
            char buf[512] = {0};
            for (int i = 0; i < len && i < 255; i++)
                buf[i] = (char)(chars[i] < 128 ? chars[i] : '?');
            Log("[Hook]   Bundle path: \"%s\" (len=%d chars)", buf, len);
        }
    }
    ((void (*)(void*, void*))g_original_LoadLocal)(this_ptr, method);
}

static LONG g_hooksInstalled = 0;

static void InstallHooks(void) {
    if (InterlockedCompareExchange(&g_hooksInstalled, 1, 0)) return;
    HMODULE ga = GetModuleHandleA("GameAssembly.dll");
    if (!ga) { AtomicWrite(&g_hooksInstalled, 0); return; }

    if (MH_Initialize() != MH_OK) {
        Log("[Hook] MH_Initialize FAILED"); AtomicWrite(&g_hooksInstalled, 0); return;
    }
    Log("[Hook] GameAssembly @ %p, installing hooks...", (void*)ga);

    MH_STATUS s;
    #define HOOK(RVA, handler, orig) do { \
        s = MH_CreateHook((uint8_t*)ga + (RVA), (handler), (void**)&(orig)); \
        if (s == MH_OK) { MH_EnableHook((uint8_t*)ga + (RVA)); Log("[Hook] OK " #handler); } \
        else Log("[Hook] FAIL " #handler " (%d)", s); \
    } while(0)

    HOOK(GENKEY_RVA,          GenerateKey_noarg_hook,      g_original_GenerateKey);
    HOOK(GENERATEIV_RVA,      GenerateIV_hook,             g_original_GenerateIV);
    HOOK(GENERATEKEY_STR_RVA, GenerateKey_str_hook,        g_original_GenerateKey_str);
    HOOK(LOADLOCAL_RVA,       LoadLocal_hook,              g_original_LoadLocal);
    #undef HOOK
    Log("[Hook] All hooks installed");
}

// Memory scanner for UnityFS bundles + key search
#define SCAN_DELAY_MS 30000
#define MAX_BUNDLES 2000

static LONG g_scanStarted = 0;
static LONG g_scanDone = 0;
static int g_bundleCount = 0;

// The known save key in UTF-8 and UTF-16
static const uint8_t kSaveKeyUTF8[]  = "rK7CcATuZk7LAmhqqU4iBLNmAq8QbK3s";
// UTF-16LE: alternating bytes with 0x00
static const uint8_t kSaveKeyUTF16[] = {
    'r',0,'K',0,'7',0,'C',0,'c',0,'A',0,'T',0,'u',0,
    'Z',0,'k',0,'7',0,'L',0,'A',0,'m',0,'h',0,'q',0,
    'q',0,'U',0,'4',0,'i',0,'B',0,'L',0,'N',0,'m',0,
    'A',0,'q',0,'8',0,'Q',0,'b',0,'K',0,'3',0,'s',0
};
#define SAVEKEY_UTF8_LEN  32
#define SAVEKEY_UTF16_LEN 64

static void DumpBundleSafe(const void *addr, size_t avail) {
    char dir[MAX_PATH];
    snprintf(dir, sizeof(dir), "%sBundles", g_outDir);
    CreateDirectoryA(dir, NULL);

    size_t dumpSize = avail > (50 * 1024 * 1024) ? (50 * 1024 * 1024) : avail;
    uint8_t *buf = (uint8_t*)malloc(dumpSize);
    if (!buf) { Log("[Scan] OOM for bundle %d", g_bundleCount); return; }

    SIZE_T read = 0;
    if (!ReadProcessMemory(GetCurrentProcess(), addr, buf, dumpSize, &read) || read == 0) {
        Log("[Scan] ReadProcessMemory failed for bundle %d", g_bundleCount);
        free(buf);
        return;
    }

    char path[MAX_PATH];
    snprintf(path, sizeof(path), "%s\\bundle_%04d.unity3d", dir, g_bundleCount);

    FILE *f = fopen(path, "wb");
    if (!f) { Log("[Scan] FAILED to create %s", path); free(buf); return; }

    fwrite(buf, 1, read, f);
    fclose(f);
    Log("[Scan] Dumped bundle %d: %zu bytes -> %s", g_bundleCount, read, path);
    g_bundleCount++;
    free(buf);
}

// Scan around an address for potential key strings (32-byte printable)
static void ScanForKeysNear(const uint8_t *addr, size_t region_size) {
    // Search for 32+ contiguous printable ASCII bytes
    for (size_t i = 0; i < region_size - 32; i++) {
        int is_printable = 1;
        for (int j = 0; j < 32; j++) {
            if (addr[i+j] < 0x20 || addr[i+j] > 0x7E) { is_printable = 0; break; }
        }
        if (is_printable) {
            char buf[64] = {0};
            memcpy(buf, addr + i, 32);
            // Skip if it's the known save key
            if (memcmp(buf, kSaveKeyUTF8, 32) == 0) continue;
            Log("[KeyScan] Found 32-byte printable at region+0x%zX: \"%s\"", i, buf);
            i += 31; // skip past it
        }
    }
}

// Try to find the save key string in memory (in IL2CPP UTF-16 format)
// and dump context around it to find adjacent key source strings
static void FindKeyStringInMemory(const uint8_t *start, size_t size, const char *region_tag) {
    // Search for the save key in UTF-16LE
    for (size_t i = 0; i < size - SAVEKEY_UTF16_LEN; i++) {
        if (memcmp(start + i, kSaveKeyUTF16, SAVEKEY_UTF16_LEN) == 0) {
            Log("[KeyScan] Found save key (UTF-16) at %s+0x%zX", region_tag, i);
            // Dump 256 bytes before and after for analysis
            size_t dump_start = (i >= 128) ? (i - 128) : 0;
            size_t dump_end = (i + SAVEKEY_UTF16_LEN + 128 < size) ? (i + SAVEKEY_UTF16_LEN + 128) : size;
            size_t dump_len = dump_end - dump_start;

            // Search for other UTF-16 strings nearby (potential bundle key)
            for (size_t j = dump_start; j < dump_end - 4; j++) {
                // Check if this starts a reasonable UTF-16 string (all ASCII chars)
                int utf16_str_len = 0;
                for (int k = 0; j + k*2 + 1 < dump_end; k++) {
                    uint8_t c = start[j + k*2];
                    uint8_t z = start[j + k*2 + 1];
                    if (c >= 0x20 && c <= 0x7E && z == 0) utf16_str_len++;
                    else break;
                }
                if (utf16_str_len >= 8 && utf16_str_len <= 64) {
                    // Skip if it's the save key itself
                    if (j >= i - 4 && j <= i + 4) continue;
                    char str[128] = {0};
                    for (int k = 0; k < utf16_str_len && k < 64; k++)
                        str[k] = start[j + k*2];
                    Log("[KeyScan]   Nearby UTF-16 string at +0x%zX (len=%d): \"%s\"",
                        j - dump_start, utf16_str_len, str);
                    j += utf16_str_len * 2 - 1;
                }
            }

            // Also dump raw hex of the area for later analysis
            char hex[2048] = {0};
            uint32_t hex_show = dump_len > 200 ? 200 : (uint32_t)dump_len;
            for (uint32_t k = 0; k < hex_show && k*3 < 2044; k++)
                sprintf(hex + k*3, "%02X ", start[dump_start + k]);
            Log("[KeyScan]   Context hex (%u bytes): %s", hex_show, hex);
        }
    }
}

#define SCAN_CHUNK (1024 * 1024) // 1 MB chunks

static int ScanChunk(const uint8_t *addr, size_t size, int region_idx) {
    // Read memory safely using ReadProcessMemory
    uint8_t *buf = (uint8_t*)malloc(size);
    if (!buf) return 0;
    SIZE_T read = 0;
    BOOL ok = ReadProcessMemory(GetCurrentProcess(), addr, buf, size, &read);
    if (!ok || read != size) { free(buf); return 0; }

    // Scan for UnityFS
    uint8_t *p = buf;
    uint8_t *end = buf + read;
    while (p < end - 7) {
        p = (uint8_t*)memchr(p, 'U', end - p - 7);
        if (!p) break;
        if (p[1]=='n' && p[2]=='i' && p[3]=='t' && p[4]=='y' && p[5]=='F' && p[6]=='S') {
            size_t offset = p - buf;
            size_t remaining = read - offset;
            Log("[Scan] Found UnityFS in region %d at offset 0x%zX, dumping %zu bytes",
                region_idx, offset, remaining);
            // Dump using the original address, not the buffer
            DumpBundleSafe(addr + offset, remaining);
            p += 16;
            free(buf);
            return 1; // found one, caller handles counting
        } else {
            p++;
        }
    }

    // Search for save key
    FindKeyStringInMemory(buf, read, "chunk");

    free(buf);
    return 0;
}

DWORD WINAPI MemoryScanThread(LPVOID param) {
    (void)param;
    Log("[Scan] Thread started, sleeping %u ms...", SCAN_DELAY_MS);
    Sleep(SCAN_DELAY_MS);
    Log("[Scan] Waking up, starting comprehensive memory scan...");

    SYSTEM_INFO si;
    GetSystemInfo(&si);

    MEMORY_BASIC_INFORMATION mbi;
    LPVOID addr = si.lpMinimumApplicationAddress;
    uint64_t totalScanned = 0;
    uint64_t maxScan = 6ULL * 1024 * 1024 * 1024;
    int scannedRegions = 0;

    while (addr < si.lpMaximumApplicationAddress && totalScanned < maxScan) {
        if (!VirtualQuery(addr, &mbi, sizeof(mbi))) {
            addr = (uint8_t*)addr + 0x10000;
            continue;
        }

        if (mbi.State == MEM_COMMIT && mbi.RegionSize > 0 && mbi.RegionSize < 200*1024*1024) {
            uint8_t *region_start = (uint8_t*)mbi.BaseAddress;
            size_t rsize = mbi.RegionSize;
            totalScanned += rsize;
            scannedRegions++;

            // Scan this region in chunks to avoid large allocations
            for (size_t off = 0; off < rsize && g_bundleCount < MAX_BUNDLES; off += SCAN_CHUNK) {
                size_t chunk = (rsize - off) > SCAN_CHUNK ? SCAN_CHUNK : (rsize - off);
                int found = ScanChunk(region_start + off, chunk, scannedRegions);
                if (found) {
                    Log("[Scan] Bundle found in region %d chunk 0x%zX", scannedRegions, off);
                }
            }
        }

        addr = (uint8_t*)mbi.BaseAddress + mbi.RegionSize;
    }

    Log("[Scan] Scan complete: scanned %llu bytes in %d regions, found %d bundles",
        totalScanned, scannedRegions, g_bundleCount);
    AtomicWrite(&g_scanDone, 1);
    return 0;
}

static void TryStartScanThread(void) {
    if (AtomicRead(&g_scanStarted)) return;
    if (InterlockedCompareExchange(&g_scanStarted, 1, 0)) return;
    Log("[Scan] Creating memory scan thread...");
    HANDLE hThread = CreateThread(NULL, 0, MemoryScanThread, NULL, 0, NULL);
    if (hThread) {
        CloseHandle(hThread);
        Log("[Scan] Thread created successfully");
    } else {
        Log("[Scan] Failed to create thread (err=%lu)", GetLastError());
        AtomicWrite(&g_scanStarted, 0);
    }
}

// Custom exports (not forwarded to original SDK)
__declspec(dllexport) void EOS_Platform_Tick(void *h) {
    if (origTick) origTick(h);
    TryCaptureKeys();
    InstallHooks();
    TryStartScanThread();
}

__declspec(dllexport) EOS_EResult EOS_Logging_SetCallback(void *cb) {
    return origSetLogCb ? origSetLogCb(cb) : EOS_Success;
}

__declspec(dllexport) EOS_EResult EOS_Logging_SetLogLevel(int32_t cat, int32_t lvl) {
    return origSetLogLvl ? origSetLogLvl(cat, lvl) : EOS_Success;
}

__declspec(dllexport) void EOS_Auth_Login(void *h, const void *o, void *cd, AuthLoginCb cb) {
    Log("[Auth] EOS_Auth_Login called");
    if (cb) { struct AuthLoginCbInfo i = {EOS_Success,cd,(void*)1,0,0,0,(void*)1}; cb(&i); }
}

__declspec(dllexport) int32_t EOS_Auth_GetLoginStatus(void *h, void *u) { (void)h; (void)u; return 2; }

__declspec(dllexport) void EOS_Connect_Login(void *h, const void *o, void *cd, ConnectLoginCb cb) {
    Log("[Connect] EOS_Connect_Login called");
    if (cb) { struct ConnectLoginCbInfo i = {EOS_Success,cd,(void*)1,0}; cb(&i); }
}

__declspec(dllexport) int32_t EOS_Connect_GetLoginStatus(void *h, void *u) { (void)h; (void)u; return 2; }

__declspec(dllexport) void *EOS_EpicAccountId_FromString(const char *s) { (void)s; return (void*)1; }
__declspec(dllexport) int32_t EOS_EpicAccountId_IsValid(void *u) { (void)u; return 1; }
__declspec(dllexport) void *EOS_ProductUserId_FromString(const char *s) { (void)s; return (void*)1; }
__declspec(dllexport) int32_t EOS_ProductUserId_IsValid(void *u) { (void)u; return 1; }

__declspec(dllexport) EOS_EResult EOS_ProductUserId_ToString(void *u, char *b, int32_t *l) {
    (void)u; if (!b || !l) return 4;
    const char *v = "DIRECTIP_PUID";
    int32_t n = (int32_t)strlen(v)+1;
    if (*l < n) { *l = n; return 4; }
    memcpy(b, v, n); *l = n; return 0;
}

BOOL APIENTRY DllMain(HMODULE m, DWORD reason, LPVOID r) {
    (void)r;
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(m);
        InitLogFiles();
        Log("[Dll] Key-capture+scan proxy loaded. PID=%lu", GetCurrentProcessId());
        char p[MAX_PATH];
        GetModuleFileNameA(m, p, MAX_PATH);
        char *s = strrchr(p, '\\'); if (!s) s = strrchr(p, '/');
        if (s) *(s+1) = '\0'; else p[0] = '\0';
        strcat(p, "EOSSDK-Win64-Shipping_orig.dll");
        HMODULE o = LoadLibraryA(p);
        if (o) {
            origTick = (TickFn)GetProcAddress(o, "EOS_Platform_Tick");
            origSetLogCb = (SetLogCbFn)GetProcAddress(o, "EOS_Logging_SetCallback");
            origSetLogLvl = (SetLogLvlFn)GetProcAddress(o, "EOS_Logging_SetLogLevel");
            Log("[Dll] Orig SDK loaded. Tick=%p LogCb=%p LogLvl=%p",
                (void*)origTick, (void*)origSetLogCb, (void*)origSetLogLvl);
            if (origSetLogCb) {
                origSetLogCb(ProxySdkLogCallback);
                if (origSetLogLvl) origSetLogLvl(0x7FFFFFFF, 6);
            }
        } else Log("[Dll] Failed to load orig SDK (err=%lu)", GetLastError());

        // Create output directories
        char dir[MAX_PATH];
        snprintf(dir, sizeof(dir), "%sBundles", g_outDir);
        CreateDirectoryA(dir, NULL);
        Log("[Dll] Proxy ready / key capture + memory scanner armed");
    } else if (reason == DLL_PROCESS_DETACH) {
        if (AtomicRead(&g_scanStarted) && !AtomicRead(&g_scanDone)) {
            Log("[Dll] Process detach but scan thread still running!");
        }
        CloseLogFiles();
    }
    return TRUE;
}
