/*
 * injector.c -- injetor "freestanding" (sem CRT) que espera o processo AMo.exe aparecer e injeta
 * hook.dll nele via a tecnica classica CreateRemoteThread + LoadLibraryA.
 *
 * Uso: roda esse .exe ANTES de abrir o jogo (ou junto) -- ele fica esperando o processo AMo.exe
 * surgir, injeta assim que aparece, e termina.
 *
 * Caminho da DLL e nome do processo-alvo estao HARDCODED (ferramenta de uso unico pra essa
 * investigacao, nao precisa de linha de comando).
 */

typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned long DWORD;
typedef int BOOL;
typedef long LONG;
typedef void *HANDLE;
typedef void *LPVOID;
typedef const void *LPCVOID;
typedef char *LPSTR;
typedef const char *LPCSTR;
typedef unsigned long ULONG_PTR;
typedef ULONG_PTR SIZE_T;

#define WINAPI __stdcall
#define TRUE 1
#define FALSE 0
#define NULL ((void *)0)

#define TH32CS_SNAPPROCESS 0x00000002
#define INVALID_HANDLE_VALUE ((HANDLE)(LONG)-1)

#define PROCESS_CREATE_THREAD 0x0002
#define PROCESS_QUERY_INFORMATION 0x0400
#define PROCESS_VM_OPERATION 0x0008
#define PROCESS_VM_WRITE 0x0020
#define PROCESS_VM_READ 0x0010

#define MEM_COMMIT 0x1000
#define MEM_RESERVE 0x2000
#define MEM_RELEASE 0x8000
#define PAGE_READWRITE 0x04

#define INFINITE 0xFFFFFFFF

typedef struct {
    DWORD dwSize;
    DWORD cntUsage;
    DWORD th32ProcessID;
    ULONG_PTR th32DefaultHeapID;
    DWORD th32ModuleID;
    DWORD cntThreads;
    DWORD th32ParentProcessID;
    LONG pcPriClassBase;
    DWORD dwFlags;
    char szExeFile[260];
} PROCESSENTRY32;

__declspec(dllimport) HANDLE WINAPI CreateToolhelp32Snapshot(DWORD, DWORD);
__declspec(dllimport) BOOL WINAPI Process32First(HANDLE, PROCESSENTRY32 *);
__declspec(dllimport) BOOL WINAPI Process32Next(HANDLE, PROCESSENTRY32 *);
__declspec(dllimport) HANDLE WINAPI OpenProcess(DWORD, BOOL, DWORD);
__declspec(dllimport) LPVOID WINAPI VirtualAllocEx(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
__declspec(dllimport) BOOL WINAPI WriteProcessMemory(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T *);
__declspec(dllimport) BOOL WINAPI VirtualFreeEx(HANDLE, LPVOID, SIZE_T, DWORD);
__declspec(dllimport) void *WINAPI GetModuleHandleA(LPCSTR);
__declspec(dllimport) void *WINAPI GetProcAddress(void *, LPCSTR);
__declspec(dllimport) HANDLE WINAPI CreateRemoteThread(HANDLE, LPVOID, SIZE_T, LPVOID, LPVOID, DWORD, DWORD *);
__declspec(dllimport) DWORD WINAPI WaitForSingleObject(HANDLE, DWORD);
__declspec(dllimport) BOOL WINAPI CloseHandle(HANDLE);
__declspec(dllimport) void WINAPI Sleep(DWORD);
__declspec(dllimport) void WINAPI ExitProcess(DWORD);
__declspec(dllimport) DWORD WINAPI GetModuleFileNameA(LPVOID, LPSTR, DWORD);
__declspec(dllimport) HANDLE WINAPI CreateFileA(LPCSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE);
__declspec(dllimport) BOOL WINAPI WriteFile(HANDLE, LPCVOID, DWORD, DWORD *, LPVOID);

static const char TARGET_EXE[] = "AMo.exe";
/* O caminho da DLL era fixo em D:\RoshEmulator\tools\hook -- o que impedia rodar o
   injetor em outra maquina. Agora ele procura hook.dll AO LADO DO PROPRIO EXECUTAVEL,
   entao a pasta inteira pode ser copiada pra qualquer lugar. */
static char DLL_PATH[520];

static void montar_caminho_dll(void) {
    DWORD n = GetModuleFileNameA(NULL, DLL_PATH, 500);
    int i;
    for (i = (int)n - 1; i >= 0; i--) {
        if (DLL_PATH[i] == 92 || DLL_PATH[i] == 47) { i++; break; }   /* barra */
    }
    if (i < 0) i = 0;
    DLL_PATH[i++] = 'h'; DLL_PATH[i++] = 'o'; DLL_PATH[i++] = 'o'; DLL_PATH[i++] = 'k';
    DLL_PATH[i++] = '.'; DLL_PATH[i++] = 'd'; DLL_PATH[i++] = 'l'; DLL_PATH[i++] = 'l';
    DLL_PATH[i] = 0;
}

static int str_eq_ci(const char *a, const char *b) {
    while (*a && *b) {
        char ca = *a, cb = *b;
        if (ca >= 'a' && ca <= 'z') ca = (char)(ca - 32);
        if (cb >= 'a' && cb <= 'z') cb = (char)(cb - 32);
        if (ca != cb) return 0;
        a++; b++;
    }
    return *a == 0 && *b == 0;
}

static unsigned long str_len(const char *s) {
    unsigned long n = 0;
    while (s[n]) n++;
    return n;
}

static void log_line(const char *s) {
    HANDLE h = CreateFileA("D:\\RoshEmulator\\tools\\hook\\injector.log", 0x40000000, 3, NULL, 4, 0x80, NULL);
    DWORD written;
    if (h == INVALID_HANDLE_VALUE) return;
    WriteFile(h, s, (DWORD)str_len(s), &written, NULL);
    WriteFile(h, "\r\n", 2, &written, NULL);
    CloseHandle(h);
}

static DWORD find_target_pid(void) {
    HANDLE snap;
    PROCESSENTRY32 pe;
    DWORD found = 0;

    snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (snap == INVALID_HANDLE_VALUE) return 0;

    pe.dwSize = sizeof(PROCESSENTRY32);
    if (Process32First(snap, &pe)) {
        do {
            if (str_eq_ci(pe.szExeFile, TARGET_EXE)) {
                found = pe.th32ProcessID;
                break;
            }
        } while (Process32Next(snap, &pe));
    }
    CloseHandle(snap);
    return found;
}

void Entry(void) {
    DWORD pid = 0;
    HANDLE hProc, hThread, kernel32, remoteMem;
    void *loadLibAddr;
    SIZE_T written;
    int tries = 0;

    montar_caminho_dll();
    log_line("injector iniciado, esperando AMo.exe...");

    /* Espera ate 5 minutos (eram 60s -- curto demais: quem rodava o injetor antes de abrir o
       jogo via ele desistir sozinho). */
    while (pid == 0 && tries < 600) {
        pid = find_target_pid();
        if (pid == 0) {
            Sleep(500);
            tries++;
        }
    }

    if (pid == 0) {
        log_line("AMo.exe nao apareceu em 5 minutos, desistindo");
        ExitProcess(1);
    }

    /* Pausa antes de injetar. Injetar no instante em que o processo aparece pega o client com
       a rede ainda nao inicializada, e o resultado e captura SO de envio -- as recepcoes por
       IOCP ficam de fora. 1.5s foi o que funcionou. */
    Sleep(1500);
    log_line("AMo.exe encontrado, injetando");

    /* deixa o processo terminar de inicializar antes de injetar */
    Sleep(300);

    hProc = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION |
                             PROCESS_VM_WRITE | PROCESS_VM_READ,
                         FALSE, pid);
    if (hProc == NULL) {
        log_line("OpenProcess falhou");
        ExitProcess(1);
    }

    remoteMem = VirtualAllocEx(hProc, NULL, sizeof(DLL_PATH), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    if (remoteMem == NULL) {
        log_line("VirtualAllocEx falhou");
        ExitProcess(1);
    }

    if (!WriteProcessMemory(hProc, remoteMem, DLL_PATH, sizeof(DLL_PATH), &written)) {
        log_line("WriteProcessMemory falhou");
        ExitProcess(1);
    }

    kernel32 = GetModuleHandleA("kernel32.dll");
    loadLibAddr = GetProcAddress(kernel32, "LoadLibraryA");
    if (loadLibAddr == NULL) {
        log_line("GetProcAddress(LoadLibraryA) falhou");
        ExitProcess(1);
    }

    hThread = CreateRemoteThread(hProc, NULL, 0, loadLibAddr, remoteMem, 0, NULL);
    if (hThread == NULL) {
        log_line("CreateRemoteThread falhou");
        ExitProcess(1);
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(hProc);

    log_line("injecao concluida com sucesso");
    ExitProcess(0);
}
