Files
2026-03-08 16:19:24 -06:00

654 lines
20 KiB
C++

#include "UI.h"
#include "FPSModule.h"
#include "ModuleManager.h"
#include "SettingsTAB.h"
#include "helpers.h"
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
namespace {
ID3D11Device* g_d3d_device = nullptr;
ID3D11DeviceContext* g_d3d_context = nullptr;
IDXGISwapChain* g_swap_chain = nullptr;
ID3D11RenderTargetView* g_main_rtv = nullptr;
HWND g_overlay_hwnd = nullptr;
bool g_overlay_interactive = false;
WNDPROC g_game_wndproc = nullptr;
bool g_game_wndproc_installed = false;
bool g_game_thread_attached_logged = false;
constexpr LONG_PTR kOverlayExStyleInteractive = WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW;
constexpr LONG_PTR kOverlayExStylePassthrough = kOverlayExStyleInteractive | WS_EX_TRANSPARENT;
constexpr UINT_PTR kGameThreadTickTimerId = 0xEF01;
constexpr UINT kGameThreadTickMs = 16;
void CleanupRenderTarget() {
if (g_main_rtv) {
g_main_rtv->Release();
g_main_rtv = nullptr;
}
}
void CreateRenderTarget() {
ID3D11Texture2D* back_buffer = nullptr;
if (g_swap_chain && SUCCEEDED(g_swap_chain->GetBuffer(0, IID_PPV_ARGS(&back_buffer)))) {
g_d3d_device->CreateRenderTargetView(back_buffer, nullptr, &g_main_rtv);
back_buffer->Release();
}
}
bool CreateDeviceD3D(HWND hwnd) {
DXGI_SWAP_CHAIN_DESC sd{};
sd.BufferCount = 2;
sd.BufferDesc.Width = 0;
sd.BufferDesc.Height = 0;
sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
sd.BufferDesc.RefreshRate.Numerator = 60;
sd.BufferDesc.RefreshRate.Denominator = 1;
sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.OutputWindow = hwnd;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.Windowed = TRUE;
sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
constexpr D3D_FEATURE_LEVEL levels[] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0,
};
D3D_FEATURE_LEVEL created_level{};
HRESULT hr = D3D11CreateDeviceAndSwapChain(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, 0,
levels, ARRAYSIZE(levels), D3D11_SDK_VERSION,
&sd, &g_swap_chain, &g_d3d_device,
&created_level, &g_d3d_context);
if (FAILED(hr)) {
return false;
}
CreateRenderTarget();
return true;
}
void CleanupDeviceD3D() {
CleanupRenderTarget();
if (g_swap_chain) {
g_swap_chain->Release();
g_swap_chain = nullptr;
}
if (g_d3d_context) {
g_d3d_context->Release();
g_d3d_context = nullptr;
}
if (g_d3d_device) {
g_d3d_device->Release();
g_d3d_device = nullptr;
}
}
bool GetGameRect(RECT* rect) {
if (!rect) {
return false;
}
if (!g_Runtime.game_hwnd || !IsWindow(g_Runtime.game_hwnd)) {
g_Runtime.game_hwnd = FindWindowA("UnityWndClass", nullptr);
}
if (!g_Runtime.game_hwnd || !IsWindow(g_Runtime.game_hwnd)) {
return false;
}
return GetWindowRect(g_Runtime.game_hwnd, rect) == TRUE;
}
void ApplyOverlayInteractivity(bool interactive) {
if (!g_overlay_hwnd) {
return;
}
if (g_overlay_interactive == interactive) {
return;
}
const LONG_PTR ex_style = interactive ? kOverlayExStyleInteractive : kOverlayExStylePassthrough;
SetWindowLongPtrW(g_overlay_hwnd, GWL_EXSTYLE, ex_style);
UINT flags = SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW;
if (!interactive) {
flags |= SWP_NOACTIVATE;
}
SetWindowPos(g_overlay_hwnd, HWND_TOPMOST, 0, 0, 0, 0, flags);
if (interactive) {
BringWindowToTop(g_overlay_hwnd);
SetForegroundWindow(g_overlay_hwnd);
SetFocus(g_overlay_hwnd);
}
g_overlay_interactive = interactive;
}
bool ContainsNoCase(const std::string& text, const std::string& needle) {
if (needle.empty()) {
return true;
}
auto lower = [](const std::string& s) {
std::string out = s;
std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) {
return static_cast<char>(std::tolower(c));
});
return out;
};
const std::string hay = lower(text);
const std::string ned = lower(needle);
return hay.find(ned) != std::string::npos;
}
bool MainTabToCategory(MainTab tab, ModuleCategory* out_category) {
if (!out_category) {
return false;
}
switch (tab) {
case MainTab::Player:
*out_category = ModuleCategory::Player;
return true;
case MainTab::ESP:
*out_category = ModuleCategory::ESP;
return true;
case MainTab::World:
*out_category = ModuleCategory::World;
return true;
case MainTab::Visual:
*out_category = ModuleCategory::Visual;
return true;
default:
break;
}
return false;
}
void HandleTabHotkeys() {
if (IsKeyPressedOnce(VK_F1)) g_Runtime.active_tab.store(MainTab::About, std::memory_order_relaxed);
if (IsKeyPressedOnce(VK_F2)) g_Runtime.active_tab.store(MainTab::Player, std::memory_order_relaxed);
if (IsKeyPressedOnce(VK_F3)) g_Runtime.active_tab.store(MainTab::ESP, std::memory_order_relaxed);
if (IsKeyPressedOnce(VK_F4)) g_Runtime.active_tab.store(MainTab::World, std::memory_order_relaxed);
if (IsKeyPressedOnce(VK_F5)) g_Runtime.active_tab.store(MainTab::Visual, std::memory_order_relaxed);
if (IsKeyPressedOnce(VK_F6)) g_Runtime.active_tab.store(MainTab::Settings, std::memory_order_relaxed);
}
void RenderAboutPanel() {
ImGui::Text("%s", "Endfield CTB3");
ImGui::Text("%s", "Version: 1.0.0");
ImGui::Text("%s", "Author: gktwo");
ImGui::Separator();
ImGui::BulletText("F12: Show/Hide panel");
ImGui::BulletText("F1..F6: Switch tabs quickly");
ImGui::BulletText("Use search to filter modules");
ImGui::BulletText("Use Quick Toggles for fast ON/OFF");
}
void RenderCategoryModules(MainTab selected_tab, const std::string& filter) {
if (!g_Runtime.il2cpp_thread_attached.load(std::memory_order_relaxed)) {
ImGui::TextDisabled("IL2CPP thread is not attached.");
ImGui::TextDisabled("Skipping module calls to prevent GC crash.");
return;
}
ModuleCategory category{};
if (!MainTabToCategory(selected_tab, &category)) {
return;
}
ModuleManager& manager = GetModuleManager();
std::vector<BaseModule*> modules = manager.GetModulesByCategory(category);
if (modules.empty()) {
ImGui::TextDisabled("No modules available.");
return;
}
if (ImGui::CollapsingHeader("Quick Toggles", ImGuiTreeNodeFlags_DefaultOpen)) {
int shown = 0;
for (BaseModule* mod : modules) {
if (!mod) {
continue;
}
const bool visible = filter.empty() ||
ContainsNoCase(mod->Name(), filter) ||
ContainsNoCase(mod->TabLabel(), filter);
if (!visible) {
continue;
}
bool on = mod->Enabled();
const std::string label = mod->TabLabel() + "##quick";
if (ImGui::Checkbox(label.c_str(), &on)) {
mod->SetEnabled(on);
}
shown++;
}
if (shown == 0) {
ImGui::TextDisabled("No module matches \"%s\".", filter.c_str());
}
}
ImGui::Separator();
int rendered = 0;
for (BaseModule* mod : modules) {
if (!mod) {
continue;
}
const bool visible = filter.empty() ||
ContainsNoCase(mod->Name(), filter) ||
ContainsNoCase(mod->TabLabel(), filter);
if (!visible) {
continue;
}
bool pushed_id = false;
try {
ImGui::PushID(mod->Name().c_str());
pushed_id = true;
mod->Render();
ImGui::PopID();
pushed_id = false;
} catch (...) {
if (pushed_id) {
ImGui::PopID();
}
Log("[ERROR] Failed to render overlay for module %s", mod->Name().c_str());
}
rendered++;
}
if (rendered == 0) {
ImGui::TextDisabled("No module matches \"%s\".", filter.c_str());
}
}
void RenderFpsOverlay() {
if (!IsFPSOverlayEnabled()) {
return;
}
ImGuiIO& io = ImGui::GetIO();
const int fps_rounded = static_cast<int>(io.Framerate + 0.5f);
char text[32] = {};
sprintf_s(text, "FPS: %d", fps_rounded);
ImU32 color = IM_COL32(255, 64, 64, 255);
if (fps_rounded >= 60) {
color = IM_COL32(64, 255, 96, 255);
} else if (fps_rounded >= 30) {
color = IM_COL32(255, 225, 80, 255);
}
ImDrawList* draw = ImGui::GetForegroundDrawList();
draw->AddText(ImVec2(12.0f, 12.0f), IM_COL32(0, 0, 0, 255), text);
draw->AddText(ImVec2(10.0f, 10.0f), color, text);
}
void RenderSidebar() {
static constexpr const char* tabs[] = {
"About",
"Player",
"ESP",
"World",
"Visual",
"Settings",
};
static constexpr const char* tab_buttons[] = {
"F1 About",
"F2 Player",
"F3 ESP",
"F4 World",
"F5 Visual",
"F6 Settings",
};
static char module_filter[96] = {};
static int language_idx = static_cast<int>(g_Runtime.language.load(std::memory_order_relaxed));
HandleTabHotkeys();
ImGui::SetNextWindowSize(ImVec2(1320.0f, 980.0f), ImGuiCond_Always);
ImGui::SetNextWindowSizeConstraints(ImVec2(1100.0f, 850.0f), ImVec2(1800.0f, 1500.0f));
ImGui::SetNextWindowBgAlpha(0.96f);
if (!ImGui::Begin("Sidebar", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar)) {
ImGui::End();
return;
}
ImGui::TextUnformatted("EF - Cheeto");
ImGui::SameLine();
ImGui::TextDisabled("(F12 show/hide)");
ImGui::TextUnformatted(Tr("This is free, if you spend money, then you have been deceived"));
ImGui::Separator();
const char* languages[] = {"English", "Chinese (Simplified)"};
ImGui::SetNextItemWidth(320.0f);
if (ImGui::Combo("##Language", &language_idx, languages, IM_ARRAYSIZE(languages))) {
SetLanguage(language_idx == 0 ? Language::English : Language::ZhCN);
}
MainTab selected = g_Runtime.active_tab.load(std::memory_order_relaxed);
const float available_width = ImGui::GetContentRegionAvail().x;
const float nav_width = std::clamp(available_width * 0.45f, 460.0f, 620.0f);
ImGui::BeginChild("##nav", ImVec2(nav_width, 0.0f), true);
ImGui::TextDisabled("Navigation");
ImGui::Separator();
for (int i = 0; i < static_cast<int>(IM_ARRAYSIZE(tabs)); ++i) {
const bool is_active = (static_cast<int>(selected) == i);
if (is_active) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.30f, 0.45f, 0.63f, 0.95f));
}
if (ImGui::Button(tab_buttons[i], ImVec2(-1.0f, 62.0f))) {
selected = static_cast<MainTab>(i);
}
if (is_active) {
ImGui::PopStyleColor();
}
}
ImGui::Separator();
ImGui::TextDisabled("Hotkeys: F1..F6 switch tabs");
ImGui::TextDisabled("F12 show/hide menu");
ImGui::EndChild();
g_Runtime.active_tab.store(selected, std::memory_order_relaxed);
ImGui::SameLine();
ImGui::BeginChild("##content", ImVec2(0.0f, 0.0f), true);
if (selected != MainTab::About && selected != MainTab::Settings) {
ImGui::InputTextWithHint("##module_filter", "Search module (name/tab)...",
module_filter, IM_ARRAYSIZE(module_filter));
ImGui::Separator();
} else {
module_filter[0] = '\0';
}
switch (selected) {
case MainTab::About:
RenderAboutPanel();
break;
case MainTab::Settings:
RenderSettingsTab();
break;
default:
RenderCategoryModules(selected, module_filter);
break;
}
ImGui::EndChild();
ImGui::End();
}
void DispatchModulesOnGameThread() {
SetGameThreadContext(true);
g_Runtime.game_thread_id.store(GetCurrentThreadId(), std::memory_order_relaxed);
if (EnsureIl2CppThreadAttached()) {
g_Runtime.il2cpp_thread_attached.store(true, std::memory_order_relaxed);
if (!g_game_thread_attached_logged) {
Log("IL2CPP game-thread dispatcher attached");
g_game_thread_attached_logged = true;
}
GetModuleManager().DispatchHotkeys();
GetModuleManager().DispatchUpdate();
} else {
g_Runtime.il2cpp_thread_attached.store(false, std::memory_order_relaxed);
g_game_thread_attached_logged = false;
}
SetGameThreadContext(false);
}
LRESULT CALLBACK GameWndProcHook(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if (msg == WM_TIMER && wparam == kGameThreadTickTimerId) {
DispatchModulesOnGameThread();
return 0;
}
if (g_game_wndproc) {
return CallWindowProcW(g_game_wndproc, hwnd, msg, wparam, lparam);
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
void InstallGameThreadDispatcher() {
if (g_game_wndproc_installed) {
return;
}
if (!g_Runtime.game_hwnd || !IsWindow(g_Runtime.game_hwnd)) {
return;
}
SetLastError(0);
LONG_PTR prev = SetWindowLongPtrW(g_Runtime.game_hwnd, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(GameWndProcHook));
if (prev == 0 && GetLastError() != 0) {
return;
}
g_game_wndproc = reinterpret_cast<WNDPROC>(prev);
if (SetTimer(g_Runtime.game_hwnd, kGameThreadTickTimerId, kGameThreadTickMs, nullptr) == 0) {
if (g_game_wndproc) {
SetWindowLongPtrW(g_Runtime.game_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(g_game_wndproc));
}
g_game_wndproc = nullptr;
return;
}
g_game_wndproc_installed = true;
Log("Installed game-thread dispatcher");
}
void UninstallGameThreadDispatcher() {
if (!g_game_wndproc_installed) {
return;
}
if (g_Runtime.game_hwnd && IsWindow(g_Runtime.game_hwnd)) {
KillTimer(g_Runtime.game_hwnd, kGameThreadTickTimerId);
if (g_game_wndproc) {
SetWindowLongPtrW(g_Runtime.game_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(g_game_wndproc));
}
}
g_game_wndproc = nullptr;
g_game_wndproc_installed = false;
g_game_thread_attached_logged = false;
g_Runtime.il2cpp_thread_attached.store(false, std::memory_order_relaxed);
}
} // namespace
LRESULT CALLBACK OverlayWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
if (ImGui::GetCurrentContext() != nullptr) {
if (ImGui_ImplWin32_WndProcHandler(hwnd, msg, wparam, lparam)) {
return 1;
}
}
switch (msg) {
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_SIZE:
if (g_swap_chain != nullptr && wparam != SIZE_MINIMIZED) {
CleanupRenderTarget();
g_swap_chain->ResizeBuffers(0, LOWORD(lparam), HIWORD(lparam), DXGI_FORMAT_UNKNOWN, 0);
CreateRenderTarget();
}
return 0;
case WM_SYSCOMMAND:
if ((wparam & 0xFFF0) == SC_KEYMENU) {
return 0;
}
break;
default:
break;
}
return DefWindowProcW(hwnd, msg, wparam, lparam);
}
bool InitializeOverlayWindow() {
if (g_overlay_hwnd) {
return true;
}
WNDCLASSEXW wc{};
wc.cbSize = sizeof(wc);
wc.style = CS_CLASSDC;
wc.lpfnWndProc = OverlayWndProc;
wc.hInstance = GetModuleHandleW(nullptr);
wc.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wc.lpszClassName = L"EF_Cheeto_OverlayWndClass";
if (!RegisterClassExW(&wc)) {
return false;
}
RECT rect{};
if (!GetGameRect(&rect)) {
rect = {0, 0, GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN)};
}
g_overlay_hwnd = CreateWindowExW(
kOverlayExStylePassthrough,
wc.lpszClassName,
L"EF-Cheeto Overlay",
WS_POPUP,
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top,
nullptr, nullptr, wc.hInstance, nullptr);
if (!g_overlay_hwnd) {
UnregisterClassW(wc.lpszClassName, wc.hInstance);
return false;
}
SetLayeredWindowAttributes(g_overlay_hwnd, RGB(0, 0, 0), 255, LWA_ALPHA);
MARGINS margins{-1};
DwmExtendFrameIntoClientArea(g_overlay_hwnd, &margins);
if (!CreateDeviceD3D(g_overlay_hwnd)) {
DestroyWindow(g_overlay_hwnd);
g_overlay_hwnd = nullptr;
UnregisterClassW(wc.lpszClassName, wc.hInstance);
return false;
}
ShowWindow(g_overlay_hwnd, SW_SHOWDEFAULT);
UpdateWindow(g_overlay_hwnd);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
io.IniFilename = "imgui.ini";
io.LogFilename = "imgui_log.txt";
io.FontGlobalScale = 2.00f;
ImGuiStyle& style = ImGui::GetStyle();
style.WindowRounding = 7.0f;
style.FrameRounding = 6.0f;
style.GrabRounding = 6.0f;
style.ScrollbarRounding = 8.0f;
style.FramePadding = ImVec2(10.0f, 8.0f);
style.ItemSpacing = ImVec2(10.0f, 10.0f);
style.CellPadding = ImVec2(8.0f, 8.0f);
ImGui_ImplWin32_Init(g_overlay_hwnd);
ImGui_ImplDX11_Init(g_d3d_device, g_d3d_context);
g_Runtime.overlay_hwnd = g_overlay_hwnd;
Log("Initialized");
return true;
}
void RunOverlayLoop() {
if (!InitializeOverlayWindow()) {
return;
}
MSG msg{};
while (g_Runtime.running.load(std::memory_order_relaxed)) {
while (PeekMessageW(&msg, nullptr, 0U, 0U, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
if (msg.message == WM_QUIT) {
g_Runtime.running.store(false, std::memory_order_relaxed);
}
}
if (!g_Runtime.running.load(std::memory_order_relaxed)) {
break;
}
RECT rect{};
const bool show_panel = g_Runtime.show_panel.load(std::memory_order_relaxed);
ApplyOverlayInteractivity(show_panel);
if (GetGameRect(&rect) && g_overlay_hwnd) {
InstallGameThreadDispatcher();
UINT pos_flags = SWP_NOOWNERZORDER;
if (!show_panel) {
pos_flags |= SWP_NOACTIVATE;
}
SetWindowPos(g_overlay_hwnd, HWND_TOPMOST, rect.left, rect.top,
rect.right - rect.left, rect.bottom - rect.top, pos_flags);
}
if (IsKeyPressedOnce(VK_F12)) {
const bool new_state = !g_Runtime.show_panel.load(std::memory_order_relaxed);
g_Runtime.show_panel.store(new_state, std::memory_order_relaxed);
ApplyOverlayInteractivity(new_state);
}
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
RenderFpsOverlay();
if (show_panel) {
RenderSidebar();
}
ImGui::Render();
const float clear_color[4] = {0.f, 0.f, 0.f, 0.f};
g_d3d_context->OMSetRenderTargets(1, &g_main_rtv, nullptr);
g_d3d_context->ClearRenderTargetView(g_main_rtv, clear_color);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
g_swap_chain->Present(1, 0);
}
}
void ShutdownOverlayWindow() {
UninstallGameThreadDispatcher();
ImGui_ImplDX11_Shutdown();
ImGui_ImplWin32_Shutdown();
if (ImGui::GetCurrentContext()) {
ImGui::DestroyContext();
}
CleanupDeviceD3D();
if (g_overlay_hwnd) {
DestroyWindow(g_overlay_hwnd);
g_overlay_hwnd = nullptr;
}
HINSTANCE hinst = GetModuleHandleW(nullptr);
UnregisterClassW(L"EF_Cheeto_OverlayWndClass", hinst);
}