/*
 * hook.c -- DLL "freestanding" (sem CRT) que hookeia WSASend/WSARecv via patch direto na IAT do
 * AMo.exe (client Karos/Rosh Online), pra logar todo trafego de rede (send E recv) no formato
 * exato como o jogo manda/recebe -- util pra capturar contra o SERVIDOR REAL sem depender de
 * pcap/wireshark.
 *
 * ENDERECOS HARDCODED pra essa build especifica do AMo.exe (base 0x00400000, SEM ASLR, confirmado
 * via Ghidra em 22/08/2026):
 *   slot da IAT pra WSASend -> 0x007f646c  (simbolo Ghidra: PTR_WSASend_007f646c)
 *   slot da IAT pra WSARecv -> 0x007f6470  (simbolo Ghidra: PTR_WSARecv_007f6470)
 *
 * Sem CRT: o DllMain daqui EH o entry point cru da DLL (linkado com /ENTRY:_DllMain@12
 * /NODEFAULTLIB). So depende de kernel32.dll -- nada de msvcrt/ucrt.
 *
 * Log vai pra C:\RoshHook\traffic.log (cria a pasta se nao existir), formato:
 *   [HH:MM:SS.mmm] SEND sock=0x1234 len=42
 *   <hex bytes, 32 por linha>
 */

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

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

#define DLL_PROCESS_ATTACH 1

#define GENERIC_WRITE 0x40000000
#define FILE_SHARE_READ 0x00000001
#define FILE_SHARE_WRITE 0x00000002
#define OPEN_ALWAYS 4
#define FILE_ATTRIBUTE_NORMAL 0x80
#define INVALID_HANDLE_VALUE ((HANDLE)(LONG)-1)
#define PAGE_EXECUTE_READWRITE 0x40
#define FILE_BEGIN 0
#define FILE_END 2

typedef struct {
    DWORD len;
    char *buf;
} WSABUF, *LPWSABUF;

typedef struct {
    WORD wYear, wMonth, wDayOfWeek, wDay, wHour, wMinute, wSecond, wMilliseconds;
} SYSTEMTIME;

/* CRITICAL_SECTION real tem 24 bytes em x86 -- reservamos um buffer opaco maior por seguranca. */
typedef struct { BYTE opaque[32]; } MYCRITSEC;

__declspec(dllimport) HANDLE WINAPI CreateFileA(LPCSTR, DWORD, DWORD, LPVOID, DWORD, DWORD, HANDLE);
__declspec(dllimport) BOOL WINAPI WriteFile(HANDLE, LPCVOID, DWORD, DWORD *, LPVOID);
__declspec(dllimport) DWORD WINAPI SetFilePointer(HANDLE, LONG, LONG *, DWORD);
__declspec(dllimport) BOOL WINAPI VirtualProtect(LPVOID, SIZE_T, DWORD, DWORD *);
__declspec(dllimport) void WINAPI GetLocalTime(SYSTEMTIME *);
__declspec(dllimport) void WINAPI InitializeCriticalSection(MYCRITSEC *);
__declspec(dllimport) void WINAPI EnterCriticalSection(MYCRITSEC *);
__declspec(dllimport) void WINAPI LeaveCriticalSection(MYCRITSEC *);
__declspec(dllimport) BOOL WINAPI CreateDirectoryA(LPCSTR, LPVOID);

typedef int(WINAPI *WSASend_t)(SOCKET, LPWSABUF, DWORD, DWORD *, DWORD, LPVOID, LPVOID);
typedef int(WINAPI *WSARecv_t)(SOCKET, LPWSABUF, DWORD, DWORD *, DWORD *, LPVOID, LPVOID);
typedef BOOL(WINAPI *GQCS_t)(HANDLE, DWORD *, ULONG_PTR *, LPVOID *, DWORD);

static WSASend_t g_origWSASend = 0;
static WSARecv_t g_origWSARecv = 0;
static GQCS_t g_origGQCS = 0;
static HANDLE g_logFile = 0;
static MYCRITSEC g_lock;
static MYCRITSEC g_pendLock;

/* rastreamento de recv overlapped/IOCP: correlaciona o LPOVERLAPPED passado no WSARecv com o
   buffer de destino, pra poder logar quando o GetQueuedCompletionStatus devolver esse mesmo
   ponteiro (eh assim que IOCP sinaliza "terminou", sem isso a gente nao sabe quantos bytes
   vieram nem onde). */
#define MAX_PENDING 128
typedef struct {
    LPVOID overlapped;
    char *buf;
    SOCKET sock;
    int used;
} PendingRecv;
static PendingRecv g_pending[MAX_PENDING];

static void pending_add(LPVOID ov, char *buf, SOCKET sock) {
    int i;
    EnterCriticalSection(&g_pendLock);
    for (i = 0; i < MAX_PENDING; i++) {
        if (!g_pending[i].used) {
            g_pending[i].overlapped = ov;
            g_pending[i].buf = buf;
            g_pending[i].sock = sock;
            g_pending[i].used = 1;
            break;
        }
    }
    LeaveCriticalSection(&g_pendLock);
}

/* acha e remove uma entrada pelo ponteiro overlapped; retorna 1 se achou (preenche *buf/*sock) */
static int pending_take(LPVOID ov, char **buf, SOCKET *sock) {
    int i, found = 0;
    EnterCriticalSection(&g_pendLock);
    for (i = 0; i < MAX_PENDING; i++) {
        if (g_pending[i].used && g_pending[i].overlapped == ov) {
            *buf = g_pending[i].buf;
            *sock = g_pending[i].sock;
            g_pending[i].used = 0;
            found = 1;
            break;
        }
    }
    LeaveCriticalSection(&g_pendLock);
    return found;
}

static void *my_memset(void *dst, int val, unsigned long n) {
    unsigned char *p = (unsigned char *)dst;
    unsigned long i;
    for (i = 0; i < n; i++) p[i] = (unsigned char)val;
    return dst;
}

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

static void log_write(const char *data, unsigned long len) {
    DWORD written;
    if (g_logFile == 0 || g_logFile == INVALID_HANDLE_VALUE) return;
    EnterCriticalSection(&g_lock);
    SetFilePointer(g_logFile, 0, NULL, FILE_END);
    WriteFile(g_logFile, data, (DWORD)len, &written, NULL);
    LeaveCriticalSection(&g_lock);
}

static void log_str(const char *s) { log_write(s, my_strlen(s)); }

static const char HEXDIGITS[] = "0123456789ABCDEF";

/* escreve um numero decimal (sem sinal) num buffer, retorna quantos chars escreveu */
static int fmt_udec(char *out, unsigned long v) {
    char tmp[12];
    int n = 0, i;
    if (v == 0) { out[0] = '0'; return 1; }
    while (v > 0 && n < 12) { tmp[n++] = (char)('0' + (v % 10)); v /= 10; }
    for (i = 0; i < n; i++) out[i] = tmp[n - 1 - i];
    return n;
}

static int fmt_hex32(char *out, unsigned long v) {
    int i;
    for (i = 0; i < 8; i++) {
        out[i] = HEXDIGITS[(v >> ((7 - i) * 4)) & 0xF];
    }
    return 8;
}

/* loga cabecalho + dump hex de um buffer de bytes */
static void log_packet(const char *dir, SOCKET sock, const char *data, unsigned long len) {
    char line[256];
    SYSTEMTIME t;
    int p = 0;
    unsigned long i;

    GetLocalTime(&t);
    line[p++] = '[';
    p += fmt_udec(line + p, t.wHour); line[p++] = ':';
    if (t.wMinute < 10) line[p++] = '0';
    p += fmt_udec(line + p, t.wMinute); line[p++] = ':';
    if (t.wSecond < 10) line[p++] = '0';
    p += fmt_udec(line + p, t.wSecond); line[p++] = '.';
    if (t.wMilliseconds < 100) line[p++] = '0';
    if (t.wMilliseconds < 10) line[p++] = '0';
    p += fmt_udec(line + p, t.wMilliseconds);
    line[p++] = ']'; line[p++] = ' ';
    { const char *s = dir; while (*s) line[p++] = *s++; }
    line[p++] = ' '; line[p++] = 's'; line[p++] = 'o'; line[p++] = 'c'; line[p++] = 'k'; line[p++] = '=';
    line[p++] = '0'; line[p++] = 'x';
    p += fmt_hex32(line + p, (unsigned long)sock);
    line[p++] = ' '; line[p++] = 'l'; line[p++] = 'e'; line[p++] = 'n'; line[p++] = '=';
    p += fmt_udec(line + p, len);
    line[p++] = '\r'; line[p++] = '\n';
    log_write(line, (unsigned long)p);

    /* dump hex, 32 bytes por linha */
    for (i = 0; i < len; i += 32) {
        char hexline[110];
        int hp = 0;
        unsigned long j, end = (i + 32 < len) ? i + 32 : len;
        for (j = i; j < end; j++) {
            unsigned char b = (unsigned char)data[j];
            hexline[hp++] = HEXDIGITS[b >> 4];
            hexline[hp++] = HEXDIGITS[b & 0xF];
            hexline[hp++] = ' ';
        }
        hexline[hp++] = '\r'; hexline[hp++] = '\n';
        log_write(hexline, (unsigned long)hp);
    }
    log_str("\r\n");
}

static int WINAPI MyWSASend(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
                             DWORD *lpNumberOfBytesSent, DWORD dwFlags,
                             LPVOID lpOverlapped, LPVOID lpCompletionRoutine) {
    /* o conteudo a mandar ja esta 100% definido em lpBuffers na hora da chamada -- overlapped ou
       nao, o dado de saida eh o mesmo, entao sempre da pra logar aqui direto. */
    DWORD i;
    for (i = 0; i < dwBufferCount; i++) {
        if (lpBuffers[i].len > 0) log_packet("SEND", s, lpBuffers[i].buf, lpBuffers[i].len);
    }
    return g_origWSASend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine);
}

static int WINAPI MyWSARecv(SOCKET s, LPWSABUF lpBuffers, DWORD dwBufferCount,
                             DWORD *lpNumberOfBytesRecvd, DWORD *lpFlags,
                             LPVOID lpOverlapped, LPVOID lpCompletionRoutine) {
    int ret;
    if (lpOverlapped != NULL && dwBufferCount > 0) {
        /* overlapped/IOCP: os bytes so existem depois que a operacao completa -- registra o
           buffer aqui e loga de verdade quando o GetQueuedCompletionStatus devolver esse mesmo
           ponteiro overlapped (funciona tanto pra conclusao sincrona quanto assincrona, porque
           por padrao o IOCP enfileira o evento nos dois casos). */
        pending_add(lpOverlapped, lpBuffers[0].buf, s);
    }
    ret = g_origWSARecv(s, lpBuffers, dwBufferCount, lpNumberOfBytesRecvd, lpFlags, lpOverlapped, lpCompletionRoutine);
    if (ret == 0 && lpOverlapped == NULL && lpNumberOfBytesRecvd != NULL && *lpNumberOfBytesRecvd > 0 && dwBufferCount > 0) {
        log_packet("RECV", s, lpBuffers[0].buf, *lpNumberOfBytesRecvd);
    }
    return ret;
}

static BOOL WINAPI MyGetQueuedCompletionStatus(HANDLE CompletionPort, DWORD *lpNumberOfBytes,
                                                ULONG_PTR *lpCompletionKey, LPVOID *lpOverlapped,
                                                DWORD dwMilliseconds) {
    BOOL ret = g_origGQCS(CompletionPort, lpNumberOfBytes, lpCompletionKey, lpOverlapped, dwMilliseconds);
    if (ret && lpOverlapped != NULL && *lpOverlapped != NULL && lpNumberOfBytes != NULL && *lpNumberOfBytes > 0) {
        char *buf;
        SOCKET sock;
        if (pending_take(*lpOverlapped, &buf, &sock)) {
            log_packet("RECV", sock, buf, *lpNumberOfBytes);
        }
    }
    return ret;
}

static void install_hooks(void) {
    DWORD oldProtect;
    void **sendSlot = (void **)0x007f646cUL;
    void **recvSlot = (void **)0x007f6470UL;
    void **gqcsSlot = (void **)0x007f618cUL;

    VirtualProtect((LPVOID)sendSlot, 4, PAGE_EXECUTE_READWRITE, &oldProtect);
    g_origWSASend = (WSASend_t)(*sendSlot);
    *sendSlot = (void *)&MyWSASend;
    VirtualProtect((LPVOID)sendSlot, 4, oldProtect, &oldProtect);

    VirtualProtect((LPVOID)recvSlot, 4, PAGE_EXECUTE_READWRITE, &oldProtect);
    g_origWSARecv = (WSARecv_t)(*recvSlot);
    *recvSlot = (void *)&MyWSARecv;
    VirtualProtect((LPVOID)recvSlot, 4, oldProtect, &oldProtect);

    VirtualProtect((LPVOID)gqcsSlot, 4, PAGE_EXECUTE_READWRITE, &oldProtect);
    g_origGQCS = (GQCS_t)(*gqcsSlot);
    *gqcsSlot = (void *)&MyGetQueuedCompletionStatus;
    VirtualProtect((LPVOID)gqcsSlot, 4, oldProtect, &oldProtect);
}

/* Sem CRT: com /O1 o compilador reconhece os loops e emite chamadas a memset da biblioteca
 * padrao, que aqui nao existe. Fornecemos o simbolo nos proprios. */
void *__cdecl memset(void *dst, int val, unsigned int n) {
    unsigned char *d = (unsigned char *)dst;
    unsigned int i;
    for (i = 0; i < n; i++) d[i] = (unsigned char)val;
    return dst;
}

BOOL WINAPI DllMain(HANDLE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
    (void)hinstDLL;
    (void)lpvReserved;
    if (fdwReason == DLL_PROCESS_ATTACH) {
        my_memset(&g_lock, 0, sizeof(g_lock));
        InitializeCriticalSection(&g_lock);
        my_memset(&g_pendLock, 0, sizeof(g_pendLock));
        InitializeCriticalSection(&g_pendLock);
        my_memset(g_pending, 0, sizeof(g_pending));
        CreateDirectoryA("C:\\RoshHook2", NULL);
        g_logFile = CreateFileA("C:\\RoshHook2\\traffic.log", GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS,
                                 FILE_ATTRIBUTE_NORMAL, NULL);
        log_str("=== hook.dll carregada, instalando hooks WSASend/WSARecv ===\r\n");
        install_hooks();
        log_str("=== hooks instalados ===\r\n");
    }
    return TRUE;
}
