// dllmain.cpp - VEX GMod Thread-Safe Loader
#include <Windows.h>
#include <Psapi.h>
#include <thread>
#include <chrono>
#include <cstdio>
#include <winternl.h>
#include <string>
#include <fstream>
#include <atomic>

#include "exploits.h"

#pragma comment(lib, "Psapi.lib")

HMODULE g_hModule = nullptr;
std::atomic<bool> g_Running = true;

// Thread-safe state synchronization
std::atomic<bool> g_pendingReload = false;
std::atomic<bool> g_pendingUnload = false;
std::atomic<bool> g_unloadDone = false;
std::atomic<bool> g_pendingMenuToggle = false;

// === VMT Hooking Setup ===
using IsInGameFn = bool(__thiscall*)(void*);
inline IsInGameFn oIsInGame = nullptr;
void* g_EngineClient = nullptr;

inline bool __thiscall hkIsInGame(void* thisptr)
{
    bool inGame = oIsInGame(thisptr);

    if (!inGame) {
        if (Exploits::g_LuaReady) {
            printf("[*] Left game, resetting Lua state...\n");
            Exploits::g_LuaReady = false;
            Exploits::g_ClientLuaState = nullptr;
        }
        return false;
    }

    // Retrieve the current lua_State pointer dynamically
    void* curState = Exploits::GetCurrentLuaState();

    if (curState && curState != Exploits::g_ClientLuaState) {
        printf("[*] Client Lua state changed (old: 0x%p, new: 0x%p). Re-initializing...\n", 
            Exploits::g_ClientLuaState, curState);
        Exploits::g_LuaReady = false;
        Exploits::g_ClientLuaState = curState;
        Exploits::g_LuaReady = true; // functions are already resolved from lua_shared.dll
        
        printf("[*] Auto-loading Lua file into new state...\n");
        Exploits::RunEmbeddedLua();
    }
    else if (!curState && Exploits::g_ClientLuaState) {
        // We disconnected / state was destroyed
        Exploits::g_LuaReady = false;
        Exploits::g_ClientLuaState = nullptr;
    }

    // Initialize Lua on the main thread if not ready
    if (!Exploits::g_LuaReady) {
        Exploits::InitLuaExec();
    }

    if (Exploits::g_LuaReady) {
        static bool initialLoaded = false;
        if (!initialLoaded) {
            initialLoaded = true;
            printf("[*] Running initial Lua file load on main thread...\n");
            if (!Exploits::LoadAndRunLuaFile("D:\\Downloads\\GMod Internal Project\\vex_cheat.lua")) {
                printf("[!] Failed to load vex_cheat.lua from disk, using embedded version...\n");
                Exploits::RunEmbeddedLua();
            }
        }

        if (g_pendingMenuToggle.load()) {
            g_pendingMenuToggle.store(false);
            printf("[*] Toggle menu triggered from C++ thread...\n");
            Exploits::RunLua("pcall(function() if concommand.GetTable() and concommand.GetTable()['vex_menu'] then concommand.GetTable()['vex_menu']() end end)");
        }

        if (g_pendingReload.load()) {
            g_pendingReload.store(false);
            printf("[*] Reloading vex_cheat.lua on main thread...\n");
            if (!Exploits::LoadAndRunLuaFile("D:\\Downloads\\GMod Internal Project\\vex_cheat.lua")) {
                printf("[!] Failed to load vex_cheat.lua from disk, using embedded version...\n");
                Exploits::RunEmbeddedLua();
            }
        }

        if (g_pendingUnload.load()) {
            g_pendingUnload.store(false);
            printf("[*] Cleaning up Lua hooks on main thread before ejecting...\n");
            Exploits::RunLua(
                "local hook_names = {'HUDPaint', 'CreateMove', 'CalcView', 'RenderScreenspaceEffects', 'PreDrawViewModel', 'PostDrawViewModel', 'PreDrawPlayerHands', 'PostDrawPlayerHands', 'PrePlayerDraw', 'PostPlayerDraw'}\n"
                "for _, h in ipairs(hook_names) do\n"
                "    hook.Remove(h, 'VEX_ESP')\n"
                "    hook.Remove(h, 'VEX_AIM')\n"
                "    hook.Remove(h, 'VEX_MOVE')\n"
                "    hook.Remove(h, 'VEX_VIEW')\n"
                "    hook.Remove(h, 'VEX_CHAMS')\n"
                "    hook.Remove(h, 'VEX_PlayerChams')\n"
                "    hook.Remove(h, 'VEX_PlayerChams_P')\n"
                "    hook.Remove(h, 'VEX_VM')\n"
                "    hook.Remove(h, 'VEX_VM_P')\n"
                "    hook.Remove(h, 'VEX_HANDS')\n"
                "    hook.Remove(h, 'VEX_HANDS_P')\n"
                "    hook.Remove(h, 'PaintHUD_Base')\n"
                "    hook.Remove(h, 'PhysicsUpdate')\n"
                "    hook.Remove(h, 'CreateMovePrediction')\n"
                "    hook.Remove(h, 'CalcViewOverride')\n"
                "    hook.Remove(h, 'RenderScreenspaceEffectsOverride')\n"
                "    hook.Remove(h, 'DrawModelChams')\n"
                "    hook.Remove(h, 'DrawModelChams_P')\n"
                "    hook.Remove(h, 'DrawVM')\n"
                "    hook.Remove(h, 'DrawVM_P')\n"
                "    hook.Remove(h, 'DrawHands')\n"
                "    hook.Remove(h, 'DrawHands_P')\n"
                "end\n"
                "if IsValid(VEX.MenuFrame) then VEX.MenuFrame:Remove() end\n"
                "print('[VEX] Unloaded successfully!')"
            );
            g_unloadDone.store(true);
        }
    }

    return oIsInGame(thisptr);
}

bool SetupEngineHook()
{
    HMODULE hEngine = GetModuleHandleA("engine.dll");
    if (!hEngine) return false;

    using CreateInterfaceFn = void*(*)(const char*, int*);
    auto createInterface = (CreateInterfaceFn)GetProcAddress(hEngine, "CreateInterface");
    if (!createInterface) {
        printf("[-] Failed to find CreateInterface in engine.dll\n");
        return false;
    }

    g_EngineClient = createInterface("VEngineClient015", nullptr);
    if (!g_EngineClient) {
        printf("[-] Failed to find VEngineClient015 interface\n");
        return false;
    }
    printf("[+] VEngineClient015 found at 0x%p\n", g_EngineClient);

    void** vtable = *(void***)g_EngineClient;
    oIsInGame = (IsInGameFn)vtable[26];
    printf("[+] Original IsInGame at 0x%p\n", oIsInGame);

    DWORD oldProtect;
    if (!VirtualProtect(&vtable[26], sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect)) {
        printf("[-] VirtualProtect failed\n");
        return false;
    }
    vtable[26] = (void*)&hkIsInGame;
    VirtualProtect(&vtable[26], sizeof(void*), oldProtect, &oldProtect);
    printf("[+] IsInGame VMT hook applied!\n");

    return true;
}

void ShutdownEngineHook()
{
    if (g_EngineClient && oIsInGame) {
        void** vtable = *(void***)g_EngineClient;
        DWORD oldProtect;
        VirtualProtect(&vtable[26], sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect);
        vtable[26] = (void*)oIsInGame;
        VirtualProtect(&vtable[26], sizeof(void*), oldProtect, &oldProtect);
        printf("[+] IsInGame VMT hook restored\n");
    }
}

// === Anti-Detection ===
namespace AntiDetect
{
    inline void HideThread()
    {
        typedef NTSTATUS(NTAPI* pNtSIT)(HANDLE, UINT, PVOID, ULONG);
        auto fn = reinterpret_cast<pNtSIT>(GetProcAddress(GetModuleHandleW(L"ntdll.dll"), "NtSetInformationThread"));
        if (fn) fn(GetCurrentThread(), 0x11, nullptr, 0);
    }

    inline void ClearDebugFlag()
    {
        __try {
            PPEB pPeb = (PPEB)__readgsqword(0x60);
            if (pPeb) pPeb->BeingDebugged = 0;
        } __except(EXCEPTION_EXECUTE_HANDLER) {}
    }

    inline void ErasePEHeader(HMODULE hModule)
    {
        __try {
            DWORD old;
            if (VirtualProtect(hModule, 0x1000, PAGE_READWRITE, &old)) {
                memset(hModule, 0, 0x1000);
                VirtualProtect(hModule, 0x1000, old, &old);
            }
        } __except(EXCEPTION_EXECUTE_HANDLER) {}
    }
}



void MainThread(HMODULE hModule)
{
    AntiDetect::HideThread();
    AntiDetect::ClearDebugFlag();

    AllocConsole();
    FILE* f;
    freopen_s(&f, "CONOUT$", "w", stdout);
    freopen_s(&f, "CONOUT$", "w", stderr);
    printf("[VEX] GMod Thread-Safe Loader loaded\n");

    // Wait for game modules
    printf("[*] Waiting for game modules...\n");
    while (!GetModuleHandleA("client.dll") || !GetModuleHandleA("engine.dll") || !GetModuleHandleA("lua_shared.dll"))
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    printf("[+] Game modules found\n");
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));



    // Setup the main thread execution hook
    if (!SetupEngineHook()) {
        printf("[-] Failed to setup engine main-thread hook!\n");
        fclose(stdout);
        fclose(stderr);
        FreeConsole();
        FreeLibraryAndExitThread(hModule, 0);
        return;
    }

    AntiDetect::ErasePEHeader(hModule);
    printf("[+] PE header erased\n");
    printf("[*] F8 = Reload cheat script, = = Unload cheat\n");

    // Main loop
    bool f8WasPressed = false;
    bool delWasPressed = false;
    bool insWasPressed = false;
    while (g_Running.load())
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        
        // Check for Unload (= key)
        if (GetAsyncKeyState(VK_OEM_PLUS) & 1) {
            printf("[*] Triggering main-thread cleanup...\n");
            g_pendingUnload.store(true);
            
            // Wait for main-thread to process cleanup
            int timeout = 0;
            while (!g_unloadDone.load() && timeout < 40) { // max 2 seconds
                std::this_thread::sleep_for(std::chrono::milliseconds(50));
                timeout++;
            }
            break;
        }
            
        // Check for Reload (F8 key, virtual key VK_F8)
        bool f8Pressed = (GetAsyncKeyState(VK_F8) & 0x8000) != 0;
        if (f8Pressed && !f8WasPressed) {
            // Signal main-thread to reload
            g_pendingReload.store(true);
        }
        f8WasPressed = f8Pressed;

        // Check for Menu Toggle (DELETE or INSERT key)
        bool delPressed = (GetAsyncKeyState(VK_DELETE) & 0x8000) != 0;
        bool insPressed = (GetAsyncKeyState(VK_INSERT) & 0x8000) != 0;
        if ((delPressed && !delWasPressed) || (insPressed && !insWasPressed)) {
            // Signal main-thread to toggle menu
            g_pendingMenuToggle.store(true);
        }
        delWasPressed = delPressed;
        insWasPressed = insPressed;
    }

    // Restore hook and cleanup
    ShutdownEngineHook();
    g_Running.store(false);

    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    fclose(stdout);
    fclose(stderr);
    FreeConsole();
    FreeLibraryAndExitThread(hModule, 0);
}

BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID)
{
    if (reason == DLL_PROCESS_ATTACH)
    {
        DisableThreadLibraryCalls(hModule);
        g_hModule = hModule;
        CloseHandle(CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)MainThread, hModule, 0, nullptr));
    }
    else if (reason == DLL_PROCESS_DETACH)
    {
        g_Running.store(false);
    }
    return TRUE;
}
