#include "pch.h" // ================================================================= // 0. IL2CPP Structs and Function Type Definitions // ================================================================= typedef void (*Il2CppMethodPointer)(); struct Il2CppDomain; struct Il2CppAssembly; struct Il2CppThread; struct Il2CppClass; struct Il2CppMethod; // Forward declaration struct Il2CppImage; // IL2CPP API function pointer type definitions using il2cpp_get_root_domain_prot = Il2CppDomain * (*)(); static il2cpp_get_root_domain_prot il2cpp_get_root_domain = nullptr; // harmless using il2cpp_thread_attach_prot = Il2CppThread * (*)(Il2CppDomain*); static il2cpp_thread_attach_prot il2cpp_thread_attach = nullptr; // harmless using il2cpp_domain_get_assemblies_prot = const Il2CppAssembly** (*)(Il2CppDomain*, size_t*); static il2cpp_domain_get_assemblies_prot il2cpp_domain_get_assemblies = nullptr; //harmless using il2cpp_class_from_name_prot = Il2CppClass * (*)(const Il2CppImage*, const char*, const char*); static il2cpp_class_from_name_prot il2cpp_class_from_name = nullptr; // not working, test needed using il2cpp_class_get_methods_prot = const Il2CppMethod* (*)(Il2CppClass*, void**); static il2cpp_class_get_methods_prot il2cpp_class_get_methods = nullptr; // not working, test needed using il2cpp_method_get_name_prot = const char* (*)(const Il2CppMethod*); static il2cpp_method_get_name_prot il2cpp_method_get_name = nullptr; // not working using il2cpp_assembly_get_image_prot = const Il2CppImage* (*)(const Il2CppAssembly*); static il2cpp_assembly_get_image_prot il2cpp_assembly_get_image = nullptr; // harmless using il2cpp_image_get_name_prot = const char* (*)(const Il2CppImage*); static il2cpp_image_get_name_prot il2cpp_image_get_name = nullptr; // harmless // IL2CPP runtime invoke function pointer type using il2cpp_runtime_invoke_prot = void* (*)(const Il2CppMethod*, void*, void**, void**); static il2cpp_runtime_invoke_prot il2cpp_runtime_invoke = nullptr; // harmless // Global variables for storing search results and log synchronization std::vector found_methods; std::mutex log_mutex; // Global variable for storing ShowErrorScreen methods std::vector error_screen_methods; /// GVARS BEGIN // CameraManager tracking related variables const Il2CppMethod* g_cachedGetInstanceMethod = nullptr; // Cache the get_Instance method void* lastCameraManager = nullptr; // Track the previous CameraManager instance // TarkovApplication instance tracking void* g_TarkovApplicationInstance = nullptr; void* g_CameraManagerInstance = nullptr; // ======================= // IMGUI // ======================= static bool g_ShowMenu = true; static ID3D11Device* g_pd3dDevice = nullptr; static ID3D11DeviceContext* g_pd3dDeviceContext = nullptr; static IDXGISwapChain* g_pSwapChain = nullptr; static ID3D11RenderTargetView* g_mainRenderTargetView = nullptr; static WNDPROC g_oOriginalWndProc = nullptr; /// GVARS END #define STATIC_FIELDS_OFFSET 0xB8 // il2cpp patch // Helper: pattern scan with mask ('x' == match, '?' == wildcard) static uint8_t* find_pattern(uint8_t* base, SIZE_T size, const unsigned char* pattern, const char* mask, SIZE_T pattern_len) { if (!base || !pattern || !mask || pattern_len == 0) return nullptr; SIZE_T max_scan = (size > pattern_len) ? (size - pattern_len) : 0; for (SIZE_T i = 0; i <= max_scan; ++i) { bool matched = true; for (SIZE_T j = 0; j < pattern_len; ++j) { if (mask[j] == '?') continue; if (base[i + j] != pattern[j]) { matched = false; break; } } if (matched) return base + i; } return nullptr; } // Find the real il2cpp_class_from_name implementation by scanning GameAssembly for an IDA-provided signature. // Returns the function pointer if found, otherwise nullptr. static il2cpp_class_from_name_prot find_il2cpp_class_from_name(HMODULE handle) { if (!handle) return nullptr; // Get module size via PE headers PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)handle; if (dos->e_magic != IMAGE_DOS_SIGNATURE) return nullptr; PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((uint8_t*)handle + dos->e_lfanew); if (nt->Signature != IMAGE_NT_SIGNATURE) return nullptr; SIZE_T module_size = nt->OptionalHeader.SizeOfImage; uint8_t* base = (uint8_t*)handle; // IDA signature: // 4C 89 44 24 ? 48 89 54 24 ? 53 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? // Build pattern and mask (use 0x00 as placeholder for wildcard bytes) const unsigned char pattern[] = { 0x4C, 0x89, 0x44, 0x24, 0x00, // 4C 89 44 24 ? 0x48, 0x89, 0x54, 0x24, 0x00, // 48 89 54 24 ? 0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x48, 0x81, 0xEC, 0x00, 0x00, 0x00, 0x00 // 48 81 EC ?? ?? ?? ?? }; // Mask: 'x' for fixed bytes, '?' for wildcard const char mask[] = "xxxx?xxxx?xxxxxxxxxxxx????"; const SIZE_T pattern_len = sizeof(pattern); uint8_t* found = find_pattern(base, module_size, pattern, mask, pattern_len); if (found) { // The found address should point to the real function entry. Return as function pointer. return (il2cpp_class_from_name_prot)found; } // If not found with this signature, attempt a looser fallback: // Search for the common prologue bytes "53 55 56 57 41 54 41 55 41 56 41 57" (function push regs) const unsigned char fallback_pattern[] = { 0x53, 0x55, 0x56, 0x57, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57 }; const char fallback_mask[] = "xxxxxxxxxxxx"; uint8_t* fallback_found = find_pattern(base, module_size, fallback_pattern, fallback_mask, sizeof(fallback_pattern)); if (fallback_found) { // Walk backwards up to a small range to try to find the full expected prologue start (heuristic). // Limit to 32 bytes back to avoid scanning too far. for (int back = 0; back < 32; ++back) { uint8_t* candidate = fallback_found - back; // Check the bytes before candidate for the 0x4C 0x89 sequence which often starts the prologue we expect. if (candidate >= base && candidate[0] == 0x4C && candidate[1] == 0x89) { return (il2cpp_class_from_name_prot)candidate; } } // Otherwise return the fallback_found as best-effort. return (il2cpp_class_from_name_prot)fallback_found; } return nullptr; } // Find the real il2cpp_class_get_methods. // Returns the function pointer if found, otherwise nullptr. static il2cpp_class_get_methods_prot find_il2cpp_class_get_methods(HMODULE handle) { if (!handle) return nullptr; return (il2cpp_class_get_methods_prot)GetProcAddress(handle, "mono_class_get_methods"); } static il2cpp_method_get_name_prot find_il2cpp_method_get_name(HMODULE handle) { if (!handle) return nullptr; return (il2cpp_method_get_name_prot)GetProcAddress(handle, "mono_property_get_set_method"); } // il2cpp patch end /// IMGUI START extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL isGameReady() { HWND gameWindow = FindWindowA("UnityWndClass", nullptr); if (!gameWindow) { return false; } return true; } LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_KEYDOWN && wParam == VK_INSERT) g_ShowMenu = !g_ShowMenu; if (g_ShowMenu && ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam)) return true; return CallWindowProc(g_oOriginalWndProc, hWnd, msg, wParam, lParam); } // Present hooks typedef HRESULT(__stdcall* PresentFn)(IDXGISwapChain*, UINT, UINT); static PresentFn oPresent = nullptr; HRESULT __stdcall hkPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) { if (!g_pd3dDevice) { g_pSwapChain = pSwapChain; // D3D11 Device if (FAILED(pSwapChain->GetDevice(__uuidof(ID3D11Device), (void**)&g_pd3dDevice))) return oPresent(pSwapChain, SyncInterval, Flags); g_pd3dDevice->GetImmediateContext(&g_pd3dDeviceContext); // hwnd DXGI_SWAP_CHAIN_DESC sd{}; pSwapChain->GetDesc(&sd); HWND hWnd = sd.OutputWindow; // WndProc for Insert key listener g_oOriginalWndProc = (WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)WndProc); // ImGui IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange; ImGui::StyleColorsDark(); ImGui_ImplWin32_Init(hWnd); ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dDeviceContext); // create RenderTargetView ID3D11Texture2D* pBackBuffer = nullptr; if (SUCCEEDED(pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBuffer))) { g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView); pBackBuffer->Release(); } else { return oPresent(pSwapChain, SyncInterval, Flags); } } // render ImGui_ImplDX11_NewFrame(); ImGui_ImplWin32_NewFrame(); ImGui::NewFrame(); if (g_ShowMenu) { ImGui::SetNextWindowSize(ImVec2(500, 400), ImGuiCond_FirstUseEver); ImGui::Begin("Operator PvE for v1.0 - build 0.0.1", &g_ShowMenu); ImGui::TextColored(ImVec4(0, 1, 0, 1), "第一阶段彻底成功!"); ImGui::Separator(); ImGui::Text("FPS: %.1f", ImGui::GetIO().Framerate); ImGui::Text("按 INSERT 键切换菜单"); ImGui::Text("所有初始化已完成,无 goto 漏洞"); ImGui::TextColored(ImVec4(1, 1, 0, 1), "你可以开始第二阶段了!"); ImGui::End(); } ImGui::Render(); g_pd3dDeviceContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr); ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); return oPresent(pSwapChain, SyncInterval, Flags); } // VTable Hook void HookPresent() { while (true) { // 1. 等待游戏窗口创建(EFT 的窗口类名是 "UnityWndClass") HWND gameWindow = FindWindowA("UnityWndClass", nullptr); if (!gameWindow) { Sleep(1000); continue; } // 2. 获取游戏真正的 SwapChain IDXGISwapChain* pSwapChain = nullptr; ID3D11Device* pDevice = nullptr; // 用游戏窗口创建一个“伪”描述,但实际会返回游戏正在用的 SwapChain DXGI_SWAP_CHAIN_DESC sd{}; sd.BufferCount = 1; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = gameWindow; sd.SampleDesc.Count = 1; sd.Windowed = TRUE; sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; HRESULT hr = D3D11CreateDeviceAndSwapChain( nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0, nullptr, 0, D3D11_SDK_VERSION, &sd, &pSwapChain, &pDevice, nullptr, nullptr); if (SUCCEEDED(hr) && pSwapChain && pDevice) { void** vtable = *(void***)pSwapChain; oPresent = (PresentFn)vtable[8]; // 关键:用原子操作写内存,防止崩溃 DWORD oldProtect; VirtualProtect(&vtable[8], 8, PAGE_EXECUTE_READWRITE, &oldProtect); vtable[8] = (void*)hkPresent; VirtualProtect(&vtable[8], 8, oldProtect, &oldProtect); printf("[+] We're hooked @ %p\n", &hkPresent); pSwapChain->Release(); pDevice->Release(); break; } if (pSwapChain) pSwapChain->Release(); if (pDevice) pDevice->Release(); std::cout << "Hit 4." << std::endl; Sleep(1000); } } /// IMGUI END // ================================================================= // 1. Logging and Helper Functions // ================================================================= // Thread-safe logging function to avoid conflicts during multi-threaded writes void log_info(const char* fmt, ...) { std::lock_guard lock(log_mutex); va_list args; va_start(args, fmt); vprintf(fmt, args); printf("\n"); va_end(args); } /** * @brief Finds methods with specified names in a given IL2CPP class * @Param klass Target IL2CPP class pointer * @Param names List of method names to find */ void find_methods_by_names(Il2CppClass* klass, const std::vector& names) { found_methods.clear(); if (!klass) return; void* iter = nullptr; const Il2CppMethod* method; while ((method = (const Il2CppMethod*)il2cpp_class_get_methods(klass, &iter))) { const char* mname = il2cpp_method_get_name(method); if (!mname) continue; for (const auto& target : names) // Use const auto& for optimized iteration { if (strcmp(mname, target.c_str()) == 0) { found_methods.push_back(method); log_info("[FIND] Method %s found @ %p", mname, method); } } } } /** * @brief Core patch function: Applies a RET Hook (0xC3) * @details Changes the first byte of the method's entry address to RET (0xC3), causing the function to return immediately, effectively disabling the feature. * @Param method Target IL2Cpp method pointer */ void patch_method_ret(const Il2CppMethod* method) { if (!method) return; // Get the actual function pointer of the method void* fn = *(void**)method; if (!fn) { log_info("[-] Method pointer is null. Skipping patch."); return; } DWORD oldProtect; // Change memory protection to PAGE_EXECUTE_READWRITE if (!VirtualProtect(fn, 1, PAGE_EXECUTE_READWRITE, &oldProtect)) { log_info("[-] VirtualProtect failed on address %p", fn); return; } // Write the RET instruction (0xC3) *(uint8_t*)fn = 0xC3; // Restore original memory protection VirtualProtect(fn, 1, oldProtect, &oldProtect); log_info("[+] Patch successfully applied, address %p -> RET", fn); } // Safe version of patch_method_ret with waiting void patch_method_ret_safe(const Il2CppMethod* method) { if (!method) return; void* fn = nullptr; while (!(fn = *(void**)method)) { Sleep(50); } DWORD oldProtect; if (VirtualProtect(fn, 16, PAGE_EXECUTE_READWRITE, &oldProtect)) { uint8_t* code = (uint8_t*)fn; code[0] = 0xC3; VirtualProtect(fn, 16, oldProtect, &oldProtect); } } /** * @brief Core patch function: Forces a boolean Getter to return a specified value * @details Changes the method machine code to MOV AL, value; RET, used to force a property to return True or False. * @Param fn Target function pointer * @Param value Boolean value to force return */ void patch_bool_getter(void* fn, bool value) { if (!fn) return; DWORD oldProtect; // Change memory protection if (!VirtualProtect(fn, 16, PAGE_EXECUTE_READWRITE, &oldProtect)) { log_info("[-] VirtualProtect failed on patch_bool_getter at address %p", fn); return; } uint8_t* code = (uint8_t*)fn; code[0] = 0xB0; // mov al, imm8 (opcode) code[1] = value ? 1 : 0; // immediate value (1 or 0) code[2] = 0xC3; // ret (return instruction) // Restore original memory protection VirtualProtect(fn, 16, oldProtect, &oldProtect); } void patch_float_getter(void* fn, float value) { if (!fn) return; static float forced_value = 0.0f; forced_value = value; uint8_t stub[32]; memset(stub, 0x90, sizeof(stub)); stub[0] = 0x48; stub[1] = 0xB8; uint64_t addr = (uint64_t)&forced_value; memcpy(&stub[2], &addr, sizeof(addr)); // imm64 int idx = 2 + (int)sizeof(addr); stub[idx++] = 0xF3; stub[idx++] = 0x0F; stub[idx++] = 0x10; stub[idx++] = 0x00; stub[idx++] = 0xC3; DWORD oldProtect; SIZE_T len = idx; VirtualProtect(fn, len, PAGE_EXECUTE_READWRITE, &oldProtect); memcpy(fn, stub, len); FlushInstructionCache(GetCurrentProcess(), fn, len); VirtualProtect(fn, len, oldProtect, &oldProtect); } /** * @brief Finds and applies the RET Hook patch to a group of methods */ void patch_methods_with_logging(Il2CppClass* klass, const char* ns, const char* name, const std::vector& methods) { if (!klass) { log_info("[-] Cannot find class %s.%s. Skipping patch.", ns, name); return; } log_info("[PATCHING] Applying patches for class %s.%s", ns, name); void* iter = nullptr; const Il2CppMethod* method; while ((method = (const Il2CppMethod*)il2cpp_class_get_methods(klass, &iter))) { const char* mname = il2cpp_method_get_name(method); if (!mname) continue; for (const auto& t : methods) { if (strcmp(mname, t.c_str()) == 0) { void* fn = *(void**)method; log_info("[PATCH] %s.%s.%s @ %p", ns, name, mname, fn); patch_method_ret(method); } } } } /** * @brief Finds the BattleEye initialization method (EFT.TarkovApplication) */ const Il2CppMethod* find_battleye_init(Il2CppClass* main_application) { void* iter = nullptr; const Il2CppMethod* method; while ((method = (const Il2CppMethod*)il2cpp_class_get_methods(main_application, &iter))) { const char* name = il2cpp_method_get_name(method); if (!name) continue; // In some versions, the BE init method name is obfuscated, identified by a characteristic byte sequence if ((unsigned(name[0]) & 0xFF) == 0xEE && (unsigned(name[1]) & 0xFF) == 0x80 && (unsigned(name[2]) & 0xFF) == 0x81) { return method; } // In other versions, it might be ValidateAnticheat if (strcmp(name, "ValidateAnticheat") == 0) { return method; } } return nullptr; } // Wait for battleye init method const Il2CppMethod* wait_for_battleye_init(Il2CppClass* main_application) { const Il2CppMethod* method = nullptr; while (!(method = find_battleye_init(main_application))) Sleep(200); return method; } // Find show error screen methods void find_show_error_screen(Il2CppClass* preloader_ui) { error_screen_methods.clear(); void* iter = nullptr; const Il2CppMethod* method; while ((method = il2cpp_class_get_methods(preloader_ui, &iter))) { const char* name = il2cpp_method_get_name(method); if (!name) continue; if (strcmp(name, "ShowErrorScreen") == 0) { error_screen_methods.push_back(method); } } } /** * @brief Finds the Unity core Assembly-CSharp.dll module */ Il2CppImage* il2cpp_image_loaded(const char* image_name) { Il2CppDomain* domain = il2cpp_get_root_domain(); if (!domain) return nullptr; size_t size = 0; const Il2CppAssembly* const* asmbl = il2cpp_domain_get_assemblies(domain, &size); if (!asmbl) return nullptr; for (size_t i = 0; i < size; i++) { const Il2CppImage* img = il2cpp_assembly_get_image(asmbl[i]); if (!img) continue; const char* nm = il2cpp_image_get_name(img); if (!nm) continue; if (_stricmp(nm, image_name) == 0) return (Il2CppImage*)img; } return nullptr; } /** * @brief Gets the singleton instance of EFT.CameraControl.CameraManager via IL2CPP runtime invoke. * @Param image Il2CppImage pointer of Assembly-CSharp.dll * @returN Pointer to the CameraManager instance, or nullptr on failure */ void* get_camera_manager_instance(Il2CppImage* image) { // 1. Find CameraManager class auto cameraManagerClass = il2cpp_class_from_name(image, "EFT.CameraControl", "CameraManager"); if (!cameraManagerClass) return nullptr; // 2. Find and cache get_Instance method (only search on first execution) if (!g_cachedGetInstanceMethod) { void* iter = nullptr; const Il2CppMethod* method; while ((method = (const Il2CppMethod*)il2cpp_class_get_methods(cameraManagerClass, &iter))) { const char* mname = il2cpp_method_get_name(method); if (!mname) continue; if (strcmp(mname, "get_Instance") == 0) { g_cachedGetInstanceMethod = method; break; } } if (!g_cachedGetInstanceMethod) return nullptr; // get_Instance not found } // 3. Execute il2cpp_runtime_invoke if (!il2cpp_runtime_invoke) return nullptr; // Ensure API is loaded void* exc = nullptr; // Exception pointer // Call static method get_Instance, so instance object pointer is nullptr void* instance = il2cpp_runtime_invoke(g_cachedGetInstanceMethod, nullptr, nullptr, &exc); // 4. Check call result and exception if (exc) { // In a real application, exception information could be logged return nullptr; } return instance; } // Get TarkovApplication instance void* get_tarkov_application_instance(Il2CppClass* klass) { uintptr_t static_fields_ptr = *(uintptr_t*)((uintptr_t)klass + STATIC_FIELDS_OFFSET); if (!static_fields_ptr) return nullptr; uintptr_t* static_fields = (uintptr_t*)static_fields_ptr; for (int i = 0; i < 32; i++) { uintptr_t candidate = static_fields[i]; if (!candidate) continue; Il2CppClass* objClass = *(Il2CppClass**)candidate; if (objClass == klass) return (void*)candidate; } return nullptr; } // ================================================================= // 2. Main Execution Logic // ================================================================= void start() { // Enable console output AllocConsole(); FILE* conout = nullptr; if (freopen_s(&conout, "CONOUT$", "w", stdout) != 0) { printf("freopen_s failed\n"); } if (freopen_s(&conout, "CONOUT$", "w", stderr) != 0) { printf("freopen_s failed\n"); } if (freopen_s(&conout, "CONIN$", "r", stdin) != 0) { printf("freopen_s failed\n"); } // 1. Wait for GameAssembly.dll to load and get IL2CPP API addresses HMODULE il2cpp = nullptr; while (!(il2cpp = GetModuleHandleA("GameAssembly.dll"))) { log_info("[-] Waiting for GameAssembly.dll..."); Sleep(2000); } printf("GameAssembly Address = %p\n", il2cpp); // Get all required IL2CPP core function pointers il2cpp_get_root_domain = (il2cpp_get_root_domain_prot)GetProcAddress(il2cpp, "il2cpp_domain_get"); il2cpp_thread_attach = (il2cpp_thread_attach_prot)GetProcAddress(il2cpp, "il2cpp_thread_attach"); il2cpp_domain_get_assemblies = (il2cpp_domain_get_assemblies_prot)GetProcAddress(il2cpp, "il2cpp_domain_get_assemblies"); il2cpp_class_from_name = find_il2cpp_class_from_name(il2cpp); if (! il2cpp_class_from_name) { printf("[-] il2cpp_class_from_name was not found."); Sleep(10000); exit(-1); } printf("[+] il2cpp_class_from_name loaded."); il2cpp_class_get_methods = find_il2cpp_class_get_methods(il2cpp); if (!il2cpp_class_get_methods) { printf("[-] il2cpp_class_get_methods was not found."); Sleep(10000); exit(-1); } printf("[+] il2cpp_class_get_methods loaded."); il2cpp_method_get_name = find_il2cpp_method_get_name(il2cpp); if (!il2cpp_method_get_name) { printf("[-] il2cpp_method_get_name was not found."); Sleep(10000); exit(-1); } printf("[+] il2cpp_method_get_name loaded."); il2cpp_assembly_get_image = (il2cpp_assembly_get_image_prot)GetProcAddress(il2cpp, "il2cpp_assembly_get_image"); il2cpp_image_get_name = (il2cpp_image_get_name_prot)GetProcAddress(il2cpp, "il2cpp_image_get_name"); // Load runtime invoke function il2cpp_runtime_invoke = (il2cpp_runtime_invoke_prot)GetProcAddress(il2cpp, "il2cpp_runtime_invoke"); // Add a startup delay to avoid operating too early printf("[-] Waiting for game window."); while (!isGameReady()) { printf("."); Sleep(100); } printf("\n"); Il2CppDomain* domain = il2cpp_get_root_domain(); if (!domain) { log_info("[-] Could not get root domain. Exiting."); return; } il2cpp_thread_attach(domain); // 2. Wait for Assembly-CSharp.dll to load Il2CppImage* image = nullptr; while (!(image = il2cpp_image_loaded("Assembly-CSharp.dll"))) { log_info("[-] Waiting for Assembly-CSharp.dll..."); Sleep(2000); } printf("[+] Assembly-CSharp.dll Address @ %p\n", image); // 3. Thread Attach: Must be done after the core DLL (Assembly-CSharp) is loaded il2cpp_thread_attach(domain); printf("[+] Successfully attached to IL2CPP domain\n"); Sleep(2000); // 4. Find and apply BattleEye & UI patches // Find EFT.TarkovApplication class auto main_app = il2cpp_class_from_name(image, "EFT", "TarkovApplication"); if (!main_app) { printf("[-] Could not find TarkovApplication. Patching aborted.\n"); } else { printf("[+] TarkovApplication Address @ %p\n", main_app); // Patch BattleEye initialization method const Il2CppMethod* be = find_battleye_init(main_app); if (be) { log_info("[+] BattleEye initialization method found."); patch_method_ret(be); } else { log_info("[-] BattleEye initialization method not found. Will attempt manual DLL unload."); } } // Find EFT.UI.PreloaderUI class to disable the error screen auto preloader_ui = il2cpp_class_from_name(image, "EFT.UI", "PreloaderUI"); if (!preloader_ui) { printf("[-] Could not find PreloaderUI. Skipping error screen patch.\n"); } else { printf("[+] PreloaderUI Address @ %p\n", preloader_ui); // Patch ShowErrorScreen error_screen_methods.clear(); const Il2CppMethod* m = nullptr; void* iter = nullptr; while ((m = (const Il2CppMethod*)il2cpp_class_get_methods(preloader_ui, &iter))) { const char* name = il2cpp_method_get_name(m); if (!name) continue; if (strcmp(name, "ShowErrorScreen") == 0) { printf("[+] Found ShowErrorScreen @ %p\n", m); error_screen_methods.push_back(m); } } for (const auto& method_to_patch : error_screen_methods) patch_method_ret(method_to_patch); } // 5. Apply core PVE patches (RET Hooks) // Make the player immortal and disable fall damage patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.HealthSystem", "ActiveHealthController"), "EFT.HealthSystem", "ActiveHealthController", { "ChangeHydration", "ChangeEnergy", "HandleFall", "SetEncumbered", "SetOverEncumbered", "AddFatigue" }); // Infinite stamina patch_methods_with_logging(il2cpp_class_from_name(image, "", "Stamina"), "", "Stamina", { "Process", "Consume" }); // Disable AI attack and tracking patch_methods_with_logging(il2cpp_class_from_name(image, "EFT", "BotMemory"), "EFT", "BotMemory", { "Spotted", "SetLastTimeSeeEnemy", "LoseVisionCurrentEnemy", "LastEnemyVisionOld", "set_GoalEnemy" }); // Disable recoil (multiple classes) patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Animations.NewRecoil", "NewRecoilShotEffect"), "EFT.Animations.NewRecoil", "NewRecoilShotEffect", { "AddRecoilForce" }); patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Animations", "NewRotationRecoilProcess"), "EFT.Animations", "NewRotationRecoilProcess", { "CalculateRecoil" }); patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Animations", "OldRecoilShotEffect"), "EFT.Animations", "OldRecoilShotEffect", { "AddRecoilForce" }); patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Animations", "OldRotationRecoilProcess"), "EFT.Animations", "OldRotationRecoilProcess", { "CalculateRecoil" }); patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Animations", "RecoilProcessBase"), "EFT.Animations", "RecoilProcessBase", { "CalculateRecoil" }); // Disable boundary and trap damage patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Interactive", "BarbedWire"), "EFT.Interactive", "BarbedWire", { "AddPenalty", "ProceedDamage" }); patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Interactive", "Minefield"), "EFT.Interactive", "Minefield", { "DealExplosionDamage", "Explode" }); patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.Interactive", "SniperFiringZone"), "EFT.Interactive", "SniperFiringZone", { "Shoot" }); // Disable weapon wear/durability loss patch_methods_with_logging(il2cpp_class_from_name(image, "EFT.InventoryLogic", "Weapon"), "EFT.InventoryLogic", "Weapon", { "OnShot" }); // Can breach any door with the "Breach" option successfully auto door_class = il2cpp_class_from_name(image, "EFT.Interactive", "Door"); find_methods_by_names(door_class, { "BreachSuccessRoll" }); if (!found_methods.empty()) { void* fn = *(void**)found_methods[0]; patch_bool_getter(fn, true); log_info("Patched Door.BreachSuccessRoll @ %p to return TRUE", fn); } // 6. Apply Boolean Getter Patches (Force return False) auto skill_class = il2cpp_class_from_name(image, "EFT", "SkillManager"); if (skill_class) { find_methods_by_names(skill_class, { "get_AttentionEliteLuckySearchValue" }); if (!found_methods.empty()) { struct MyIl2CppMethod { void* methodPointer; }; void* fn = ((MyIl2CppMethod*)found_methods[0])->methodPointer; if (fn) { patch_float_getter(fn, 1.0f); printf("patched EFT.SkillManager.get_AttentionEliteLuckySearchValue @ %p -> returns 1.0f\n", fn); } else { printf("[-] methodPointer is null for get_AttentionEliteLuckySearchValue\n"); } } else { printf("[-] EFT.SkillManager.get_AttentionEliteLuckySearchValue unfound\n"); } } else { printf("[-] EFT.SkillManager unfound\n"); } auto weapon_class = il2cpp_class_from_name(image, "EFT.InventoryLogic", "Weapon"); // Disable weapon malfunction find_methods_by_names(weapon_class, { "get_AllowMalfunction" }); if (!found_methods.empty()) { void* fn = *(void**)found_methods[0]; patch_bool_getter(fn, false); log_info("Patched get_AllowMalfunction @ %p to return FALSE", fn); } // Disable weapon overheating find_methods_by_names(weapon_class, { "get_AllowOverheat" }); if (!found_methods.empty()) { void* fn = *(void**)found_methods[0]; patch_bool_getter(fn, false); log_info("Patched get_AllowOverheat @ %p to return FALSE", fn); } // 7. Unload BattleEye client DLL HMODULE beclient = GetModuleHandleA("BEClient_x64.dll"); if (beclient) { printf("[+] BEClient_x64 @ %p is unloading...\n", beclient); FreeLibrary(beclient); } // ================================================================= // 8. Idle Loop and CameraManager Tracking (Core Merge) // ================================================================= log_info("\n[+] All patches applied. Entering idle tracking loop. Hooking menu."); CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)HookPresent, nullptr, 0, nullptr); // Keep the thread alive to prevent it from exiting the IL2CPP domain, avoiding fatal GC errors. while (true) { // TarkovApplication instance tracking if (!g_TarkovApplicationInstance) { g_TarkovApplicationInstance = get_tarkov_application_instance(main_app); if (g_TarkovApplicationInstance) log_info("[+] TarkovApplication = 0x%llX", (uintptr_t)g_TarkovApplicationInstance); } // CameraManager tracking logic void* cm = get_camera_manager_instance(image); if (cm) { // Track CameraManager instance changes - print on initial detection OR when instance changes if (cm != g_CameraManagerInstance) { if (!g_CameraManagerInstance) { log_info("[+] CameraManager initial detection: 0x%llX", (uintptr_t)cm); } else { log_info("[+] CameraManager instance updated: 0x%llX", (uintptr_t)cm); } g_CameraManagerInstance = cm; } } // Keep the thread active, using 3-second sleep to minimize CPU usage and set the tracking interval Sleep(3000); } } // ================================================================= // 3. DLL Entry Point // ================================================================= BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID) { // Create a new thread to run the main logic when the DLL is attached to the process if (reason == DLL_PROCESS_ATTACH) CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)start, nullptr, 0, nullptr); return TRUE; }