1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
#include "overlay.hpp"
#include <windows.h>
#include "backends/imgui_impl_opengl3.h"
#include "capture.hpp"
#include "esp.hpp"
#include "gl.hpp"
#include "imgui.h"
#include "input.hpp"
#include "log.hpp"
#include "menu.hpp"
#include "state.hpp"
namespace lodestone::overlay {
namespace {
bool g_ready = false;
bool g_failed = false;
bool g_logged_state = false;
HWND g_hwnd = nullptr;
LARGE_INTEGER g_freq{}, g_last{};
using PFNGetFramebufferSize = void (*)(void*, int*, int*);
using PFNGetWin32Window = void* (*)(void*);
PFNGetFramebufferSize g_framebuffer_size = nullptr;
void* glfw_proc(const char* name) {
HMODULE glfw = GetModuleHandleA("glfw.dll");
if (!glfw) return nullptr;
return reinterpret_cast<void*>(GetProcAddress(glfw, name));
}
bool init(void* window) {
gl::load();
if (!gl::g_api.GetIntegerv || !gl::g_api.BindFramebuffer) {
log("gl entry points missing — is this a core profile context?");
return false;
}
g_framebuffer_size =
reinterpret_cast<PFNGetFramebufferSize>(glfw_proc("glfwGetFramebufferSize"));
// The window procedure is what gives us Insert and swallows input while the
// menu is open; without the HWND there is nothing to subclass.
if (auto get_hwnd = reinterpret_cast<PFNGetWin32Window>(glfw_proc("glfwGetWin32Window"))) {
g_hwnd = static_cast<HWND>(get_hwnd(window));
if (g_hwnd) {
capture::set_window(g_hwnd);
if (!input::install(g_hwnd)) log("window procedure hook failed");
}
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.IniFilename = nullptr; // no imgui.ini next to the game
io.LogFilename = nullptr;
ImGui::StyleColorsDark();
// The font atlas is uploaded during backend setup, and that upload reads the
// *game's* pixel-transfer state — so it has to happen with the state
// neutralised or the glyphs come out as noise (the "cursed text" bug).
gl::State saved = gl::State::save();
gl::State::neutralise();
bool ok = ImGui_ImplOpenGL3_Init("#version 130");
if (ok) ok = ImGui_ImplOpenGL3_CreateDeviceObjects();
saved.restore();
if (!ok) {
log("ImGui_ImplOpenGL3_Init failed");
return false;
}
QueryPerformanceFrequency(&g_freq);
QueryPerformanceCounter(&g_last);
log("imgui renderer created");
return true;
}
/// Feed the queued Win32 input into ImGui. The window hook has already decided
/// what belongs to the menu, so anything here is ours.
void pump_input(ImGuiIO& io) {
auto events = state::with([](state::Shared& s) {
auto copy = s.events;
s.events.clear();
return copy;
});
for (const auto& e : events) {
using K = state::UiInput::Kind;
switch (e.kind) {
case K::MouseMove: io.AddMousePosEvent(e.x, e.y); break;
case K::MouseButton: io.AddMouseButtonEvent(e.button, e.down); break;
case K::Wheel: io.AddMouseWheelEvent(0.0f, e.y); break;
case K::Char: io.AddInputCharacter(e.codepoint); break;
case K::Key: break; // module binds are handled before ImGui sees them
}
}
}
} // namespace
void frame(void* window) {
if (g_failed) return;
if (!g_ready) {
if (!init(window)) {
g_failed = true;
return;
}
g_ready = true;
}
int w = 0, h = 0;
if (g_framebuffer_size) g_framebuffer_size(window, &w, &h);
if (w <= 0 || h <= 0) return;
ImGuiIO& io = ImGui::GetIO();
io.DisplaySize = ImVec2(static_cast<float>(w), static_cast<float>(h));
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
double dt =
static_cast<double>(now.QuadPart - g_last.QuadPart) / static_cast<double>(g_freq.QuadPart);
g_last = now;
io.DeltaTime = dt > 0.0 ? static_cast<float>(dt) : 1.0f / 60.0f;
pump_input(io);
ImGui_ImplOpenGL3_NewFrame();
ImGui::NewFrame();
// One lock for both: the ESP draws every frame, the menu only when open.
// Holding it across the draw is what the render thread does anyway, and the
// window procedure only ever touches the state briefly.
state::with([](state::Shared& s) {
esp::draw(s);
menu::hud(s);
if (s.menu_open) menu::draw(s);
// All-or-nothing on the whole window, so this only touches it when the
// switch actually changes rather than once a frame.
static bool capture_hidden = false;
if (s.cfg.misc.hide_from_capture != capture_hidden) {
capture_hidden = s.cfg.misc.hide_from_capture;
capture::apply(capture_hidden);
}
});
ImGui::Render();
gl::State saved = gl::State::save();
if (!g_logged_state) {
g_logged_state = true;
logf("gl at swap: fbo=%d program=%d sampler0=%d unpackBuf=%d rowLen=%d srgb=%d",
saved.draw_framebuffer, saved.program, saved.sampler0, saved.unpack_buffer,
saved.unpack_row_length, static_cast<int>(saved.srgb));
}
gl::State::neutralise();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
saved.restore();
}
void shutdown() {
if (!g_ready) return;
if (g_hwnd) input::remove(g_hwnd);
capture::apply(false);
ImGui_ImplOpenGL3_Shutdown();
ImGui::DestroyContext();
g_ready = false;
}
} // namespace lodestone::overlay