#define _CRT_SECURE_NO_WARNINGS
#define NOMINMAX

#include <Windows.h>
#include <gl/GL.h>
#include <gl/GLU.h>
#include <cstdio>
#include <iostream>
#include <thread>
#include <vector>
#include <unordered_set>
#include <chrono>
#include <fstream>
#include <DbgHelp.h>
#include "jar_loader.h"   // unhook template buradan geliyor — once include et
#include "jni_helper.h"
#include "bypass.hpp"
#include "rac_guard_disabler.hpp"
#include "MinHook.h"

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#pragma comment(lib, "DbgHelp.lib")

JavaVM* g_jvm = nullptr;
JNIEnv* g_env = nullptr;
jvmtiEnv* g_jvmti = nullptr;
jclass g_mainClass = nullptr;
jfieldID g_unloadedField = nullptr;


static bool isClientInitialized = false;

static std::string g_dllDirPath = "";

static void InitDllPath(HMODULE hModule) {
    if (g_dllDirPath.empty()) {
        char path[MAX_PATH];
        if (GetModuleFileNameA(hModule, path, MAX_PATH)) {
            std::string fullPath(path);
            size_t lastSlash = fullPath.find_last_of("\\/");
            if (lastSlash != std::string::npos) {
                g_dllDirPath = fullPath.substr(0, lastSlash + 1);
            }
        }
    }
}

static void WriteStackTrace(uintptr_t rsp, FILE* f) {
    uintptr_t* stack = (uintptr_t*)rsp;
    HMODULE hJvm  = GetModuleHandleA("jvm.dll");
    HMODULE hSelf = GetModuleHandleA("Sarco.dll");
    for (int i = 0; i < 20; i++) {
        __try {
            uintptr_t addr = stack[i];
            if (addr > 0x10000) {
                fprintf(f, "  [%2d] 0x%016llx", i, (unsigned long long)addr);
                if (hJvm  && addr >= (uintptr_t)hJvm  && addr < (uintptr_t)hJvm  + 0x800000)
                    fprintf(f, "  (jvm.dll+0x%llx)",  (unsigned long long)(addr - (uintptr_t)hJvm));
                else if (hSelf && addr >= (uintptr_t)hSelf && addr < (uintptr_t)hSelf + 0x100000)
                    fprintf(f, "  (Sarco.dll+0x%llx)", (unsigned long long)(addr - (uintptr_t)hSelf));
                else {
                    // Hangi modülde olduğunu bul
                    HMODULE hMod = nullptr;
                    if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
                                           GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
                                           (LPCSTR)addr, &hMod) && hMod) {
                        char modName[MAX_PATH] = {};
                        GetModuleFileNameA(hMod, modName, MAX_PATH);
                        // Sadece dosya adını al
                        const char* slash = strrchr(modName, '\\');
                        fprintf(f, "  (%s+0x%llx)", slash ? slash + 1 : modName,
                                (unsigned long long)(addr - (uintptr_t)hMod));
                    }
                }
                fprintf(f, "\n");
            }
        } __except(1) { break; }
    }
}

void WriteCrashLog(EXCEPTION_POINTERS* pExceptionInfo) {
    return;
}

LONG WINAPI ExceptionHandler(EXCEPTION_POINTERS* pExceptionInfo) {
    printf("[-] EXCEPTION CAUGHT!\n");
    WriteCrashLog(pExceptionInfo);
    return EXCEPTION_CONTINUE_SEARCH;
}

// OpenGL Typedefs
namespace GLHooks {
    typedef void(__stdcall* wglSwapBuffers_t)(HDC);
    wglSwapBuffers_t original_wglSwapBuffers = nullptr;
}

void InitializeLogging() {
    AllocConsole();
    
    // UTF-8 encoding ayarla
    SetConsoleOutputCP(CP_UTF8);
    SetConsoleCP(CP_UTF8);
    
    FILE* pCout;
    freopen_s(&pCout, "CONOUT$", "w", stdout);
    freopen_s(&pCout, "CONOUT$", "w", stderr);
    
    SetConsoleTitleA("Sarco Loader - Debug Console");
}

jfieldID g_nativeTrackField = nullptr;
jfieldID g_nativeArtistField = nullptr;
jfieldID g_nativePlayingField = nullptr;
jfieldID g_nativeAvailableField = nullptr;
jfieldID g_nativeCommandField = nullptr;
jfieldID g_nativePositionField = nullptr;
jfieldID g_nativeDurationField = nullptr;

// g_jarLoaded — JarLoader::IsHudReady() ile sync tutulur
static bool g_jarLoaded = false;

// =====================================================
// GetLoadedClasses HOOK
// =====================================================
static jvmtiError(JNICALL* orig_GetLoadedClasses)(
    jvmtiEnv* env,
    jint* class_count_ptr,
    jclass** classes_ptr
) = nullptr;

static std::atomic<int> g_getLoadedClassesCallCount{ 0 };

// g_bypassHook — jar_loader.cpp ve jni_helper.cpp'nin kendi GetLoadedClasses
// cagrilarinda hook'u bypass etmek icin kullanilir (gercek liste lazim olunca)
std::atomic<bool> g_bypassHook{ false };

// =====================================================
// PRE-JAR CLASS CACHE
// JAR yüklenmeden önce alınan snapshot — credos class'lari
// henüz yüklü değil, dolayısıyla temiz liste.
// Hook içinde sıfır JVMTI/JNI çağrısı: sadece bu cache döndürülür.
// =====================================================
static jclass* g_preJarClasses  = nullptr; // JVMTI-allocated, bizim kopyamız
static jint    g_preJarCount    = 0;
static std::atomic<bool> g_preJarReady{ false };

AvamHookAction Hook_GetLoadedClasses(CONTEXT* ctx) {
    auto jvmti_env       = (jvmtiEnv*)ctx->Rcx;
    auto class_count_ptr = (jint*)ctx->Rdx;
    auto classes_ptr     = (jclass**)ctx->R8;

    jclass* classes     = nullptr;
    jint    class_count = 0;

    jvmtiError ret = orig_GetLoadedClasses(jvmti_env, &class_count, &classes);

    int callNum = ++g_getLoadedClassesCallCount;

    if (g_bypassHook.load()) {
        if (class_count_ptr) *class_count_ptr = class_count;
        if (classes_ptr)     *classes_ptr     = classes;
        ctx->Rax = ret;
        return AvamHookAction::SKIP_ORIGINAL;
    }

    // Eski (Cache) Yaklaşım: JAR öncesi temiz listeyi döndür
    if (g_preJarReady.load() && g_preJarClasses && g_preJarCount > 0) {
        if (class_count_ptr) *class_count_ptr = g_preJarCount;
        if (classes_ptr) {
            auto Allocate_ = (jvmtiError(JNICALL*)(jvmtiEnv*, jlong, unsigned char**))unhook(jvmti_env->functions->Allocate);
            jclass* copyClasses = nullptr;
            
            if (Allocate_(jvmti_env, g_preJarCount * sizeof(jclass), (unsigned char**)&copyClasses) == JVMTI_ERROR_NONE && copyClasses) {
                // HATA ÇÖZÜMÜ: AC'nin kendi thread'inde geçerli olacak Local Referanslar oluştur!
                JNIEnv* current_env = nullptr;
                extern JavaVM* g_jvm;
                if (g_jvm->GetEnv((void**)&current_env, JNI_VERSION_1_6) == JNI_OK && current_env) {
                    auto EnsureLocalCapacity_ = (jint(JNICALL*)(JNIEnv*, jint))unhook(current_env->functions->EnsureLocalCapacity);
                    EnsureLocalCapacity_(current_env, g_preJarCount + 50);

                    auto NewLocalRef_ = (jobject(JNICALL*)(JNIEnv*, jobject))unhook(current_env->functions->NewLocalRef);
                    for (int i = 0; i < g_preJarCount; i++) {
                        copyClasses[i] = (jclass)NewLocalRef_(current_env, g_preJarClasses[i]);
                    }
                } else {
                    // Fallback: Doğrudan kopyala (riskli ama mecburi)
                    memcpy(copyClasses, g_preJarClasses, g_preJarCount * sizeof(jclass));
                }
                *classes_ptr = copyClasses;
            } else {
                *classes_ptr = g_preJarClasses; 
            }
        }
        
        if (classes) {
            auto Deallocate_ = (jvmtiError(JNICALL*)(jvmtiEnv*, unsigned char*))unhook(jvmti_env->functions->Deallocate);
            Deallocate_(jvmti_env, (unsigned char*)classes);
        }

        ctx->Rax = JVMTI_ERROR_NONE;

        printf("hwbp #%d tetiklendi cache: %d (irl: %d)\n", callNum, (int)g_preJarCount, (int)class_count);

        return AvamHookAction::SKIP_ORIGINAL;
    }

    // Cache yoksa
    if (class_count_ptr) *class_count_ptr = class_count;
    if (classes_ptr)     *classes_ptr     = classes;
    ctx->Rax = ret;
    return AvamHookAction::SKIP_ORIGINAL;
}




// hwbp_130 logu — render thread'de (wglSwapBuffers) cagirilir
// Artık sadece g_preJarClasses içeriğini loglar
static std::atomic<bool> g_hwbp130_pending{ false };

static void WriteHwbp130Log(JNIEnv* env) {
    if (!g_hwbp130_pending.load()) return;
    if (!JNIHelper::oCallObjectMethod || !JNIHelper::oFindClass) return;

    g_hwbp130_pending.store(false);

    if (!g_preJarClasses || g_preJarCount <= 0) return;

    jclass    classClass = unhook(env->functions->FindClass)(env, "java/lang/Class");
    jmethodID getNameMid = unhook(env->functions->GetMethodID)(env, classClass, "getName", "()Ljava/lang/String;");

    if (!classClass || !getNameMid) {
        printf("[HWBP] WriteHwbp130Log: class/method bulunamadi\n");
        return;
    }

    for (int i = 0; i < (int)g_preJarCount; i++) {
        if (!g_preJarClasses[i]) { continue; }

        jstring jname = (jstring)unhook(env->functions->CallObjectMethod)(env, g_preJarClasses[i], getNameMid);

        if (env->ExceptionCheck()) { env->ExceptionClear(); continue; }
        if (!jname) { continue; }

        const char* str = unhook(env->functions->GetStringUTFChars)(env, jname, nullptr);
        if (str) {
            bool issarco = strstr(str, "sarco") || strstr(str, "sarco");
            if (issarco) printf("[HWBP] sarco CACHE'DE GORUNUYOR (BUG!) [%d]: %s\n", i, str);
            unhook(env->functions->ReleaseStringUTFChars)(env, jname, str);
        }
        unhook(env->functions->DeleteLocalRef)(env, jname);
    }
}


// Bypass çalışıyorsa 500 dönmeli, çalışmıyorsa gerçek sayı döner
static void DumpAllLoadedClasses() {
    // JNIHelper'ın JVMTI'sini kullan — main.cpp'deki farklı instance bypass'ı tetiklemiyor
    jvmtiEnv* jvmti = JNIHelper::g_jvmti;
    if (!jvmti) {
        printf("[DUMP] JNIHelper::g_jvmti null, atlanıyor\n");
        return;
    }

    printf("[DUMP] Kullanılan JVMTI: %p\n", jvmti);

    auto GetLoadedClasses_ = unhook((jvmtiError(JNICALL*)(jvmtiEnv*, jint*, jclass**))
        jvmti->functions->GetLoadedClasses);
    auto GetClassSignature_ = unhook((jvmtiError(JNICALL*)(jvmtiEnv*, jclass, char**, char**))
        jvmti->functions->GetClassSignature);
    auto Deallocate_ = unhook((jvmtiError(JNICALL*)(jvmtiEnv*, unsigned char*))
        jvmti->functions->Deallocate);

    jint classCount = 0;
    jclass* classes = nullptr;

    // Hook tetiklensin — gerçek çağrı gibi davran, 130 gelmeli
    jvmtiError err = JVMTI_ERROR_NONE;
    err = GetLoadedClasses_(jvmti, &classCount, &classes);
    if (err != JVMTI_ERROR_NONE) {
        printf("[DUMP] GetLoadedClasses hata: %d\n", (int)err);
        return;
    }


    for (jint i = 0; i < classCount; i++) {
        char* sig = nullptr;
        if (GetClassSignature_(jvmti, classes[i], &sig, nullptr) == JVMTI_ERROR_NONE && sig) {
            if (strstr(sig, "sarco") || strstr(sig, "sarco"))
                printf("[DUMP] sarco GORUNUYOR: %s\n", sig);
            Deallocate_(jvmti, (unsigned char*)sig);
        }
    }

    Deallocate_(jvmti, (unsigned char*)classes);
}

static bool AcquireJNIEnv() {
    if (g_env && g_jvm) return true;

    HMODULE hJvm = GetModuleHandleA("jvm.dll");
    if (!hJvm) {
        printf("[-] jvm.dll not found in AcquireJNIEnv\n");
        return false;
    }

    // Dynamic approach using JNI_GetCreatedJavaVMs first
    typedef jint(JNICALL* p_GetCreatedJavaVMs)(JavaVM**, jsize, jsize*);
    p_GetCreatedJavaVMs fnGetVMs = (p_GetCreatedJavaVMs)GetProcAddress(hJvm, "JNI_GetCreatedJavaVMs");
    if (fnGetVMs) {
        jsize numVMs = 0;
        JavaVM* vmList[1] = { nullptr };
        if (fnGetVMs(vmList, 1, &numVMs) == JNI_OK && numVMs > 0) {
            g_jvm = vmList[0];
            printf("[+] Acquired JavaVM dynamically via JNI_GetCreatedJavaVMs: %p\n", g_jvm);
            g_jvm->GetEnv((void**)&g_env, JNI_VERSION_1_8);
        }
    }

    // Offset fallback if dynamic retrieval fails
    if (!g_env) {
        printf("[*] Falling back to hardcoded jvm.dll offset...\n");
        typedef jint(JNICALL* p_GetEnv)(JavaVM*, JNIEnv**, jint);
        p_GetEnv fnGetEnv = (p_GetEnv)((uintptr_t)hJvm + 0x144080);
        fnGetEnv(nullptr, &g_env, JNI_VERSION_1_8);
    }

    if (!g_env) {
        printf("[-] Failed to get JNIEnv\n");
        return false;
    }
    printf("JNIEnv: %p\n", g_env);

    if (!g_jvm) {
        g_env->GetJavaVM(&g_jvm);
    }
    if (!g_jvm) {
        printf("[-] Failed to get JavaVM from JNIEnv\n");
        return false;
    }
    printf("JVM: %p\n", g_jvm);

    jint res = g_jvm->GetEnv((void**)&g_jvmti, JVMTI_VERSION_1_2);

    if (res != JNI_OK) {
        printf("[-] Failed to get JVMTI: %d\n", res);
        g_jvmti = nullptr;
    } else {
        printf("JVMTI: %p\n", g_jvmti);
    }

    // ACKiller'a JVMTI ver
    ACKiller::g_jvmti_ref = g_jvmti;

    return true;
}

static void InitializeClient() {
    if (!isClientInitialized) {
        isClientInitialized = true; // Önce set et, döngüye girmesin
        printf("instalize\n");

        // Render thread'den JNIEnv al
        if (!AcquireJNIEnv()) {
            printf("[-] Failed to acquire JNIEnv, aborting\n");
            return;
        }

        if (!g_jvmti || !g_jvmti->functions) {
            printf("[-] JVMTI null, cannot hook GetLoadedClasses\n");
            return;
        }

        orig_GetLoadedClasses = g_jvmti->functions->GetLoadedClasses;

        // =========================================================
        // PRE-JAR SNAPSHOT — hook kurulmadan once al, bypass gerekmez
        // credos class'lari henüz yüklü değil, liste temiz.
        // =========================================================
        {
            auto GetLoadedClasses_ = (jvmtiError(JNICALL*)(jvmtiEnv*, jint*, jclass**))
                unhook(orig_GetLoadedClasses); // doğrudan orijinal fn, hook yok henüz

            jint    snapCount   = 0;
            jclass* snapClasses = nullptr;

            jvmtiError snapErr = GetLoadedClasses_(g_jvmti, &snapCount, &snapClasses);

            if (snapErr == JVMTI_ERROR_NONE && snapClasses && snapCount > 0) {
                auto NewGlobalRef_ = (jobject(JNICALL*)(JNIEnv*, jobject))unhook(g_env->functions->NewGlobalRef);
                auto Deallocate_ = (jvmtiError(JNICALL*)(jvmtiEnv*, unsigned char*))unhook(g_jvmti->functions->Deallocate);

                jclass* globalSnapClasses = new jclass[snapCount];
                for(int i = 0; i < snapCount; i++) {
                    globalSnapClasses[i] = (jclass)NewGlobalRef_(g_env, snapClasses[i]);
                }

                g_preJarClasses = globalSnapClasses;
                g_preJarCount   = snapCount;
                g_preJarReady.store(true);
                printf("ilk snapshot: %d class\n", (int)snapCount);
                g_hwbp130_pending.store(true);

                Deallocate_(g_jvmti, (unsigned char*)snapClasses);
            } else {
                printf("[-] Pre-JAR snapshot alinamadi: %d\n", (int)snapErr);
            }
        }

        bool hookSuccess = AvamHook::Hook((void*)orig_GetLoadedClasses, Hook_GetLoadedClasses, nullptr);
        if (hookSuccess) {
            printf("[+] getloadedclasses\n");
        } else {
            printf("[-] getloadedclasses hook atilamadi\n");
        }
        printf("[+] bypass basarili\n");

        // JNIHelper başlat (class listesini temizleyerek)
        JNIHelper::g_loadedClasses.clear();
        printf("jnihelper\n");
        if (!JNIHelper::Initialize(g_env)) {
            printf("[-] JNIHelper::Initialize failed\n");
            return;
        }

        // JAR yükle
        std::string jarPathStr = g_dllDirPath + "client.jar";
        if (GetFileAttributesA(jarPathStr.c_str()) == INVALID_FILE_ATTRIBUTES) {
            jarPathStr = g_dllDirPath + "Sarco.jar";
        }
        const char* jarPath = jarPathStr.c_str();
        printf("jarload startup: %s\n", jarPath);
        if (JarLoader::LoadJar(g_env, g_jvmti, jarPath)) {
            printf("jar basarili\n");
        } else {
            printf("jar basarisiz\n");
        }

        // Manuel dump — hook'tan bağımsız, JVMTI ile tüm classları yaz
        DumpAllLoadedClasses();
    }
}

// OpenGL Hooks
void __stdcall Hooked_wglSwapBuffers(HDC hdc) {
    InitializeClient();
    
    // Check if Java requested unload
    if (g_env && g_mainClass && g_unloadedField) {
        auto GetStaticBooleanField_ = (jboolean(JNICALL*)(JNIEnv*, jclass, jfieldID))unhook(g_env->functions->GetStaticBooleanField);
        auto ExceptionCheck_ = (jboolean(JNICALL*)(JNIEnv*))unhook(g_env->functions->ExceptionCheck);
        auto ExceptionClear_ = (void(JNICALL*)(JNIEnv*))unhook(g_env->functions->ExceptionClear);
        
        jboolean unloaded = GetStaticBooleanField_(g_env, g_mainClass, g_unloadedField);
        if (ExceptionCheck_(g_env)) ExceptionClear_(g_env);
        
        if (unloaded) {
            // Unhook all MinHook hooks (restoring original OpenGL functions)
            MH_DisableHook(MH_ALL_HOOKS);
            MH_Uninitialize();
            
            // Free the debug console
            FreeConsole();
            
            // Spawn a thread to unload the DLL safely from the process memory
            std::thread([]() {
                Sleep(100);
                HMODULE hMod = GetModuleHandleA("Sarco.dll");
                if (hMod) {
                    FreeLibraryAndExitThread(hMod, 0);
                }
            }).detach();
            
            // Pass call to original wglSwapBuffers and return
            GLHooks::original_wglSwapBuffers(hdc);
            return;
        }
    }

    ACKiller::OnTick();
    if (g_env && JarLoader::IsHudReady()) {
        JarLoader::RenderHud(g_env);
    }
    // hwbp_130 logu — render thread'de yaz, g_env bu thread'e ait
    if (g_hwbp130_pending.load() && g_env)
        WriteHwbp130Log(g_env);
    GLHooks::original_wglSwapBuffers(hdc);
}

// Main Thread
DWORD WINAPI MainThread(LPVOID) {
    // JVM'nin yüklenmesini bekle
    printf("jvm bekliyor...\n");
    HMODULE hJvm = nullptr;
    while (!hJvm) {
        hJvm = GetModuleHandleA("jvm.dll");
        Sleep(1);
    }
    printf("[+] jvm.dll\n");

    // RefreshHooks döngüsü — yeni threadlere hook'ları uygula (1ms agresif)
    std::thread([&]() {
        while (true) {
            Sleep(1);
            AvamHook::RefreshHooks();
        }
    }).detach();

    // OpenGL32.dll'yi bekle
    printf("opengl beklenior...\n");
    HMODULE openglModule = nullptr;
    while (!openglModule) {
        openglModule = GetModuleHandleA("opengl32.dll");
        Sleep(100);
    }
    printf("[+] opengl32.dll\n");
    
    // MinHook Initialize
    MH_STATUS mhStatus = MH_Initialize();
    if (mhStatus != MH_OK && mhStatus != MH_ERROR_ALREADY_INITIALIZED) {
        printf("[-] MinHook initialization failed\n");
        return 0;
    }
    printf("[+] MinHook initialized\n");
    
    // wglSwapBuffers hook
    printf("wglswapbuffer hook...\n");
    GLHooks::original_wglSwapBuffers = (GLHooks::wglSwapBuffers_t)GetProcAddress(openglModule, "wglSwapBuffers");
    
    if (!GLHooks::original_wglSwapBuffers) {
        printf("[-] Failed to get wglSwapBuffers\n");
        return 0;
    }
    
    printf("[+] found: %p\n", GLHooks::original_wglSwapBuffers);
    
    if (MH_CreateHook((LPVOID)GLHooks::original_wglSwapBuffers, (LPVOID)Hooked_wglSwapBuffers, (LPVOID*)&GLHooks::original_wglSwapBuffers) != MH_OK) {
        printf("[-] Failed to create wglSwapBuffers hook\n");
        return 0;
    }
    
    if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK) {
        printf("[-] Failed to enable hooks\n");
        return 0;
    }
    
    
    return 0;
}

// DllMain
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
    if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
        // DLL attach olur olmaz Shadow DR hook'larını kur
        // NtGetContextThread/NtSetContextThread hemen gizlensin
        InitDllPath(hModule);
        AvamHook::Init();

        // Initialize RAC Guard Disabler - completely disable anti-cheat
        RACGuardDisabler::Initialize();

        SetUnhandledExceptionFilter(ExceptionHandler);
        InitializeLogging();
        printf("dll started\n");
        printf(" %p\n", hModule);
        DisableThreadLibraryCalls(hModule);
        CreateThread(nullptr, 0, MainThread, nullptr, 0, nullptr);
    }
    else if (ul_reason_for_call == DLL_PROCESS_DETACH) {
        AvamHook::Shutdown();
    }
    return TRUE;
}
