// LauncherHook.cpp - Modern implementation of LauncherHook.DLL for Decal
//
// This DLL is loaded into the Asheron's Call launcher/lobby process.
// It intercepts CreateProcessW to detect when client.exe is launched,
// then injects Inject.DLL into the suspended game process before resuming it.
//
// Exported functions:
//   LauncherHookEnable   - Install the CBT hook for injection into launcher
//   LauncherHookDisable  - Remove the CBT hook
//   LauncherHookLauncherStartup - Called when launcher starts (no-op placeholder)

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <tchar.h>
#include <string>

// ---------------------------------------------------------------------------
// Shared data section - persists across all processes that load this DLL
// ---------------------------------------------------------------------------
#pragma data_seg(".LHookDll")
static HHOOK g_hHook = NULL;
static HINSTANCE g_hInstance = NULL;
#pragma data_seg()
#pragma comment(linker, "/SECTION:.LHookDll,RWS")

// ---------------------------------------------------------------------------
// Import Address Table hooking infrastructure
// (Simplified from ApiHook.h for this single-purpose DLL)
// ---------------------------------------------------------------------------
#define MakePtr(cast, ptr, addValue) (cast)((DWORD_PTR)(ptr) + (DWORD_PTR)(addValue))

typedef BOOL(WINAPI* PFN_CreateProcessW)(
    LPCWSTR, LPWSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES,
    BOOL, DWORD, LPVOID, LPCWSTR, LPSTARTUPINFOW, LPPROCESS_INFORMATION);

static PFN_CreateProcessW g_pfnOrigCreateProcessW = NULL;

// ---------------------------------------------------------------------------
// DLL injection via CreateRemoteThread + LoadLibraryW
// ---------------------------------------------------------------------------
static BOOL InjectDLL(HANDLE hProcess, const WCHAR* szDllPath)
{
    SIZE_T cbPath = (wcslen(szDllPath) + 1) * sizeof(WCHAR);

    // Allocate memory in target process for the DLL path string
    LPVOID pRemotePath = VirtualAllocEx(hProcess, NULL, cbPath, MEM_COMMIT, PAGE_READWRITE);
    if (!pRemotePath)
        return FALSE;

    // Write the DLL path into the target process
    if (!WriteProcessMemory(hProcess, pRemotePath, szDllPath, cbPath, NULL))
    {
        VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
        return FALSE;
    }

    // Get LoadLibraryW address (same in all processes due to ASLR kernel32 sharing)
    LPTHREAD_START_ROUTINE pfnLoadLibrary =
        (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "LoadLibraryW");

    if (!pfnLoadLibrary)
    {
        VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
        return FALSE;
    }

    // Create remote thread that calls LoadLibraryW(dllPath)
    HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnLoadLibrary, pRemotePath, 0, NULL);
    if (!hThread)
    {
        VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);
        return FALSE;
    }

    // Wait for the remote LoadLibrary to complete
    WaitForSingleObject(hThread, 10000);

    CloseHandle(hThread);
    VirtualFreeEx(hProcess, pRemotePath, 0, MEM_RELEASE);

    return TRUE;
}

// ---------------------------------------------------------------------------
// Hooked CreateProcessW - intercepts game client launch
// ---------------------------------------------------------------------------
static BOOL WINAPI Hooked_CreateProcessW(
    LPCWSTR lpApplicationName,
    LPWSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCWSTR lpCurrentDirectory,
    LPSTARTUPINFOW lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation)
{
    static LONG s_recursion = 0;

    // Check if this is launching client.exe / acclient.exe
    bool bIsClient = false;
    if (lpCommandLine)
    {
        std::wstring cmd(lpCommandLine);
        // Case-insensitive search for client.exe in command line
        for (auto& c : cmd) c = towlower(c);
        bIsClient = (cmd.find(L"client.exe") != std::wstring::npos);
    }
    if (!bIsClient && lpApplicationName)
    {
        std::wstring app(lpApplicationName);
        for (auto& c : app) c = towlower(c);
        bIsClient = (app.find(L"client.exe") != std::wstring::npos);
    }

    if (InterlockedIncrement(&s_recursion) == 1 && bIsClient)
    {
        // Read the Inject.DLL path from registry
        WCHAR szDllPath[MAX_PATH] = {};
        HKEY hKey = NULL;

        if (RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\Decal\\Agent",
                          0, KEY_READ, &hKey) == ERROR_SUCCESS)
        {
            DWORD dwSize = (MAX_PATH - 20) * sizeof(WCHAR);
            if (RegQueryValueExW(hKey, L"AgentPath", NULL, NULL,
                                 (LPBYTE)szDllPath, &dwSize) == ERROR_SUCCESS)
            {
                // Ensure trailing backslash
                size_t len = wcslen(szDllPath);
                if (len > 0 && szDllPath[len - 1] != L'\\')
                    wcscat_s(szDllPath, L"\\");
                wcscat_s(szDllPath, L"Inject.dll");
            }
            RegCloseKey(hKey);
        }

        // Create the process in SUSPENDED state
        PROCESS_INFORMATION localPI = {};
        LPPROCESS_INFORMATION pPI = lpProcessInformation ? lpProcessInformation : &localPI;

        BOOL bResult = g_pfnOrigCreateProcessW(
            lpApplicationName, lpCommandLine,
            lpProcessAttributes, lpThreadAttributes,
            bInheritHandles,
            dwCreationFlags | CREATE_SUSPENDED,
            lpEnvironment, lpCurrentDirectory,
            lpStartupInfo, pPI);

        if (bResult)
        {
            // Only inject if Decal Agent window is running
            if (FindWindowW(NULL, L"Decal Agent") != NULL && szDllPath[0] != L'\0')
            {
                InjectDLL(pPI->hProcess, szDllPath);
            }

            // Resume the process (unless caller originally wanted it suspended)
            if (!(dwCreationFlags & CREATE_SUSPENDED))
            {
                ResumeThread(pPI->hThread);
            }
        }

        InterlockedDecrement(&s_recursion);
        return bResult;
    }
    else
    {
        // Not client.exe or recursive call - pass through
        BOOL bResult = g_pfnOrigCreateProcessW(
            lpApplicationName, lpCommandLine,
            lpProcessAttributes, lpThreadAttributes,
            bInheritHandles, dwCreationFlags,
            lpEnvironment, lpCurrentDirectory,
            lpStartupInfo, lpProcessInformation);

        InterlockedDecrement(&s_recursion);
        return bResult;
    }
}

// ---------------------------------------------------------------------------
// IAT patching - hook CreateProcessW in the current process
// ---------------------------------------------------------------------------
static PIMAGE_IMPORT_DESCRIPTOR FindImportDescriptor(HMODULE hModule, LPCSTR szImportMod)
{
    PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hModule;
    PIMAGE_NT_HEADERS pNT = MakePtr(PIMAGE_NT_HEADERS, pDOS, pDOS->e_lfanew);

    if (pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress == 0)
        return NULL;

    PIMAGE_IMPORT_DESCRIPTOR pImport = MakePtr(PIMAGE_IMPORT_DESCRIPTOR, pDOS,
        pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

    while (pImport->Name)
    {
        LPCSTR szName = MakePtr(LPCSTR, pDOS, pImport->Name);
        if (_stricmp(szName, szImportMod) == 0)
            return pImport;
        pImport++;
    }

    return NULL;
}

static bool PatchIAT(HMODULE hTarget, LPCSTR szModule, LPCSTR szFunction,
                     void* pNewFunc, void** ppOrigFunc, bool bInstall)
{
    PIMAGE_IMPORT_DESCRIPTOR pImport = FindImportDescriptor(hTarget, szModule);
    if (!pImport)
        return false;

    PIMAGE_THUNK_DATA pOrigThunk = MakePtr(PIMAGE_THUNK_DATA, hTarget, pImport->OriginalFirstThunk);
    PIMAGE_THUNK_DATA pRealThunk = MakePtr(PIMAGE_THUNK_DATA, hTarget, pImport->FirstThunk);

    for (; pOrigThunk->u1.Function; pOrigThunk++, pRealThunk++)
    {
        if (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
            continue;

        PIMAGE_IMPORT_BY_NAME pByName = MakePtr(PIMAGE_IMPORT_BY_NAME, hTarget, pOrigThunk->u1.AddressOfData);
        if (pByName->Name[0] == '\0')
            continue;

        if (_stricmp((const char*)pByName->Name, szFunction) != 0)
            continue;

        // Found it - patch
        MEMORY_BASIC_INFORMATION mbi;
        VirtualQuery(pRealThunk, &mbi, sizeof(mbi));
        VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect);

        if (bInstall)
        {
            if (ppOrigFunc)
                *ppOrigFunc = (void*)pRealThunk->u1.Function;
            pRealThunk->u1.Function = (DWORD_PTR)pNewFunc;
        }
        else if (ppOrigFunc && *ppOrigFunc)
        {
            pRealThunk->u1.Function = (DWORD_PTR)*ppOrigFunc;
        }

        DWORD dwOld;
        VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOld);
        return true;
    }

    return false;
}

static void InstallHooks(bool bInstall)
{
    // Hook CreateProcessW in the main executable
    HMODULE hExe = GetModuleHandle(NULL);
    PatchIAT(hExe, "kernel32.dll", "CreateProcessW",
             (void*)Hooked_CreateProcessW, (void**)&g_pfnOrigCreateProcessW, bInstall);

    // Also try hooking in AsheronsCall.dll if loaded (legacy launcher)
    HMODULE hAC = GetModuleHandleA("AsheronsCall.dll");
    if (hAC)
    {
        PatchIAT(hAC, "kernel32.dll", "CreateProcessW",
                 (void*)Hooked_CreateProcessW, (void**)&g_pfnOrigCreateProcessW, bInstall);
    }
}

// ---------------------------------------------------------------------------
// CBT hook procedure - gets us loaded into the launcher process
// ---------------------------------------------------------------------------
static LRESULT CALLBACK CBTHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

// ---------------------------------------------------------------------------
// Exported functions
// ---------------------------------------------------------------------------
extern "C" __declspec(dllexport) void __stdcall LauncherHookEnable()
{
    if (!g_hHook)
    {
        g_hHook = SetWindowsHookExW(WH_CBT, CBTHookProc, g_hInstance, 0);
    }
}

extern "C" __declspec(dllexport) void __stdcall LauncherHookDisable()
{
    if (g_hHook)
    {
        UnhookWindowsHookEx(g_hHook);
        g_hHook = NULL;
    }
}

extern "C" __declspec(dllexport) void __stdcall LauncherHookLauncherStartup()
{
    // Placeholder - called by DenAgent when launcher starts
    // The actual hooking is done in DllMain when we detect the launcher process
}

// ---------------------------------------------------------------------------
// DllMain
// ---------------------------------------------------------------------------
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
    case DLL_PROCESS_ATTACH:
        g_hInstance = hInstance;
        DisableThreadLibraryCalls(hInstance);

        // Install IAT hooks to intercept CreateProcessW
        InstallHooks(true);
        break;

    case DLL_PROCESS_DETACH:
        // Remove IAT hooks
        InstallHooks(false);
        break;
    }

    return TRUE;
}
