941 lines
33 KiB
C++
941 lines
33 KiB
C++
#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<const Il2CppMethod*> found_methods;
|
||
std::mutex log_mutex;
|
||
|
||
// Global variable for storing ShowErrorScreen methods
|
||
std::vector<const Il2CppMethod*> 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<std::mutex> 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<std::string>& 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<std::string>& 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;
|
||
}
|