// AntiDebug.cpp -- Bypass game's anti-debug checks for modding/debugging
//
// Hooks NtQueryInformationProcess and NtSetContextThread to hide the
// debugger from the game's protection.  A PEB watchdog thread
// continuously clears the BeingDebugged flag.
#include "AntiDebug.h"
#include "Hooks.h"
#include "Logger.h"

#define STATUS_SUCCESS           0x00000000
#define STATUS_PORT_NOT_SET      0xC0000353
#define ProcessDebugPort         7
#define ProcessDebugObjectHandle 30
#define ProcessDebugFlags        31

typedef NTSTATUS(WINAPI* NtQIP_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
typedef NTSTATUS(WINAPI* NtSCT_t)(HANDLE, CONTEXT*);

static Hook hookNtQIP = { 0 };
static Hook hookNtSCT = { 0 };

static NTSTATUS WINAPI MyNtQueryInformationProcess(
    HANDLE ProcessHandle,
    ULONG ProcessInformationClass,
    PVOID ProcessInformation,
    ULONG ProcessInformationLength,
    PULONG ReturnLength)
{
    if (ProcessInformationClass == ProcessDebugPort ||
        ProcessInformationClass == ProcessDebugObjectHandle ||
        ProcessInformationClass == ProcessDebugFlags)
    {
        BOOL isCurrent = (ProcessHandle == GetCurrentProcess());

        if (!isCurrent) {
            struct {
                PVOID Reserved1;
                PVOID PebBaseAddress;
                PVOID Reserved2[2];
                ULONG_PTR UniqueProcessId;
                PVOID Reserved3;
            } pbi = { 0 };

            NTSTATUS status = ((NtQIP_t)hookNtQIP.trampoline)(
                ProcessHandle, 0, &pbi, sizeof(pbi), NULL);

            if (status == STATUS_SUCCESS && pbi.UniqueProcessId == GetCurrentProcessId())
                isCurrent = TRUE;
        }

        if (isCurrent) {
            if (ProcessInformationClass == ProcessDebugPort) {
                if (ProcessInformation && ProcessInformationLength >= 4) *(DWORD*)ProcessInformation = 0;
                if (ReturnLength) *ReturnLength = 4;
                return STATUS_SUCCESS;
            }
            if (ProcessInformationClass == ProcessDebugObjectHandle) {
                if (ProcessInformation && ProcessInformationLength >= 4) *(DWORD*)ProcessInformation = 0;
                if (ReturnLength) *ReturnLength = 4;
                return STATUS_PORT_NOT_SET;
            }
            if (ProcessInformationClass == ProcessDebugFlags) {
                if (ProcessInformation && ProcessInformationLength >= 4) *(DWORD*)ProcessInformation = 1;
                if (ReturnLength) *ReturnLength = 4;
                return STATUS_SUCCESS;
            }
        }
    }

    return ((NtQIP_t)hookNtQIP.trampoline)(
        ProcessHandle, ProcessInformationClass,
        ProcessInformation, ProcessInformationLength, ReturnLength);
}

static NTSTATUS WINAPI MyNtSetContextThread(HANDLE ThreadHandle, CONTEXT* Context)
{
    if (Context)
    {
        BOOL isCurrent = (ThreadHandle == GetCurrentThread());

        if (!isCurrent) {
            typedef NTSTATUS(WINAPI* NtQIT_t)(HANDLE, ULONG, PVOID, ULONG, PULONG);
            static NtQIT_t pNtQIT = (NtQIT_t)GetProcAddress(
                GetModuleHandleA("ntdll.dll"), "NtQueryInformationThread");

            if (pNtQIT) {
                struct {
                    NTSTATUS ExitStatus;
                    PVOID TebBaseAddress;
                    struct { HANDLE UniqueProcess; HANDLE UniqueThread; } ClientId;
                    ULONG_PTR AffinityMask;
                    LONG Priority;
                    LONG BasePriority;
                } tbi = { 0 };

                if (pNtQIT(ThreadHandle, 0, &tbi, sizeof(tbi), NULL) == STATUS_SUCCESS)
                    if ((DWORD)(DWORD_PTR)tbi.ClientId.UniqueThread == GetCurrentThreadId())
                        isCurrent = TRUE;
            }
            else {
                if (GetThreadId(ThreadHandle) == GetCurrentThreadId())
                    isCurrent = TRUE;
            }
        }

        if (isCurrent) {
            Context->ContextFlags &= ~CONTEXT_DEBUG_REGISTERS;
            Context->Dr0 = 0; Context->Dr1 = 0; Context->Dr2 = 0;
            Context->Dr3 = 0; Context->Dr6 = 0; Context->Dr7 = 0;
        }
    }

    return ((NtSCT_t)hookNtSCT.trampoline)(ThreadHandle, Context);
}

bool PatchPEB() {
    unsigned char* peb = (unsigned char*)__readfsdword(0x30);
    if (peb[2] != 0) { peb[2] = 0; return true; }
    return false;
}

DWORD WINAPI PEBWatchdog(LPVOID) {
    for (;;) {
        if (PatchPEB()) Log("[Watchdog] PEB BeingDebugged flag cleared.");
        Sleep(100);
    }
    return 0;
}

void InstallAntiDebugHooks() {
    LogIf("-------------------------------------------------");
    LogIf("Installing anti-debug hooks...");

    HMODULE ntdll = GetModuleHandleA("ntdll.dll");
    void* pNtQIP = GetProcAddress(ntdll, "NtQueryInformationProcess");
    void* pNtSCT = GetProcAddress(ntdll, "NtSetContextThread");

    if (pNtQIP) {
        InstallHook(&hookNtQIP, pNtQIP, (void*)MyNtQueryInformationProcess);
        LogFmtIf("  NtQueryInformationProcess hooked  @ 0x%08X  trampoline @ 0x%08X",
            (DWORD)pNtQIP, (DWORD)hookNtQIP.trampoline);
    }
    else LogIf("  ERROR: NtQueryInformationProcess not found!");

    if (pNtSCT) {
        InstallHook(&hookNtSCT, pNtSCT, (void*)MyNtSetContextThread);
        LogFmtIf("  NtSetContextThread hooked          @ 0x%08X  trampoline @ 0x%08X",
            (DWORD)pNtSCT, (DWORD)hookNtSCT.trampoline);
    }
    else LogIf("  ERROR: NtSetContextThread not found!");

    PatchPEB();
    LogIf("  PEB BeingDebugged flag cleared.");
}
