diff --git a/.gitignore b/.gitignore index be97518..fa44e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ .vs/ build/ out/ +dep/ +vcpkg/ +vcpkg_installed/ diff --git a/lib/game/inc/1.3528.0.0/offset_haloreach.h b/lib/game/inc/1.3528.0.0/offset_haloreach.h index 36d8333..a7a5ea9 100644 --- a/lib/game/inc/1.3528.0.0/offset_haloreach.h +++ b/lib/game/inc/1.3528.0.0/offset_haloreach.h @@ -9,6 +9,9 @@ #define OFFSET_HALOREACH_PF_ADD_LOCAL_PLAYER 0x43DC8 +#define OFFSET_HALOREACH_PF_GET_SPLITSCREEN_PLAYER_COUNT 0xC30D0 +#define OFFSET_HALOREACH_PF_SPLITSCREEN_RESOLUTION_RESOURCE 0x2CA86C + #define OFFSET_HALOREACH_V_ENTRY_PLAYERS 0x3 #define OFFSET_HALOREACH_V_ENTRY_PLAYERS_ACTION 0x23 #define OFFSET_HALOREACH_V_ENTRY_SPLIT_SCREEN 0x2B diff --git a/src/input/Input.cpp b/src/input/Input.cpp index db83695..2beb7ef 100644 --- a/src/input/Input.cpp +++ b/src/input/Input.cpp @@ -95,6 +95,9 @@ namespace AlphaRing::Input { bool debugKeyIsDown = (GetAsyncKeyState(debugKey) & 0x8000) != 0; if (debugKeyIsDown && !debugKeyWasDown) { AlphaRing::Global::Global()->show_imgui = !AlphaRing::Global::Global()->show_imgui; + + AlphaRing::Global::Global()->show_imgui_mouse = AlphaRing::Global::Global()->show_imgui; + } debugKeyWasDown = debugKeyIsDown; diff --git a/src/mcc/module/CModule.cpp b/src/mcc/module/CModule.cpp index 19347e7..91d0cbd 100644 --- a/src/mcc/module/CModule.cpp +++ b/src/mcc/module/CModule.cpp @@ -51,33 +51,34 @@ static struct { {"splitscreen_patch4", "force making splitscreen works with more than 2 players", 0x5153E, "\x83\xF8\x01\x74\x04", true}, }}, {Halo3EntrySet(), { {"splitscreen_patch1", "", OFFSET_HALO3_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true}, - {"Remove Black Bar1", "remove black bar", 0x8AE150/*0x8AD160*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true}, - {"Remove Black Bar2", "remove black bar", 0x8AE164/*0x8AD174*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", true}, - {"Remove Black Bar3", "remove black bar", 0x8AE1A0/*0x8AD1B0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true}, + {"Remove Black Bar1", "remove black bar", 0x8AE150/*0x8AD160*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar2", "remove black bar", 0x8AE164/*0x8AD174*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar3", "remove black bar", 0x8AE1A0/*0x8AD1B0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, }}, {nullptr, { {"splitscreen_patch1", "", OFFSET_HALO4_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true}, {"splitscreen_patch2", "", OFFSET_HALO4_PF_COOP_REJOIN, "\xEB", true}, {"splitscreen_patch3", "", OFFSET_HALO4_PF_COOP_PLAYER_LIMIT, "\x90\x90\x90\x90\x90\x90", true}, - {"Remove Black Bar1", "remove black bar", 0xE84E50/*0xE84E40*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false}, - {"Remove Black Bar2", "remove black bar", 0xE84E64/*0xE84E54*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", false}, - {"Remove Black Bar3", "remove black bar", 0xE84EA0/*0xE84E90*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false}, + {"Remove Black Bar1", "remove black bar", 0xE84E50/*0xE84E40*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar2", "remove black bar", 0xE84E64/*0xE84E54*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar3", "remove black bar", 0xE84EA0/*0xE84E90*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, }}, {nullptr, { {"splitscreen_patch1", "", OFFSET_GROUNDHOG_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true}, {"splitscreen_patch2", "", OFFSET_GROUNDHOG_PF_REJOIN, "\xEB", true}, - {"Remove Black Bar1", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_1 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false}, - {"Remove Black Bar2", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_2 , "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", false}, - {"Remove Black Bar3", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_3 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", false}, + {"Remove Black Bar1", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_1 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar2", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_2 , "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar3", "remove black bar", OFFSET_GROUNDHOG_BLACKBAR_3 , "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, }}, {nullptr, { {"splitscreen_patch1", "", OFFSET_HALO3ODST_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true}, - {"Remove Black Bar1", "remove black bar", 0x8F1FB0/*0x8F1FC0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true}, - {"Remove Black Bar2", "remove black bar", 0x8F1FC4/*0x8F1FD4*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", true}, - {"Remove Black Bar3", "remove black bar", 0x8F2000/*0x8F2010*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true}, -}}, {nullptr, { + {"Remove Black Bar1", "remove black bar", 0x8F1FB0/*0x8F1FC0*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar2", "remove black bar", 0x8F1FC4/*0x8F1FD4*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar3", "remove black bar", 0x8F2000/*0x8F2010*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, +}}, {HaloReachEntrySet(), { {"splitscreen_patch1", "", OFFSET_HALOREACH_PF_COOP_JOIN, "\x31\xC0\xC3\x90", true}, {"splitscreen_patch2", "", OFFSET_HALOREACH_PF_COOP_REJOIN, "\xEB", true}, - {"Remove Black Bar1", "remove black bar", 0xB43CE0/*0xB43D10*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true}, - {"Remove Black Bar2", "remove black bar", 0xB43CF4/*0xB43D24*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01", true}, - {"Remove Black Bar3", "remove black bar", 0xB43D30/*0xB43D60*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01", true}, + {"Remove Black Bar1", "remove black bar", 0xB43CE0/*0xB43D10*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar2", "remove black bar", 0xB43CF4/*0xB43D24*/, "\x00\x00\x00\x00\x00\x00\x00\x3F\x00\x00\x80\x3F\x00\x00\x80\x3F\x01\x00\x00\x00", false}, + {"Remove Black Bar3", "remove black bar", 0xB43D30/*0xB43D60*/, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x3F\x00\x00\x00\x3F\x01\x00\x00\x00", false}, + {"Disable Black Bar Overlay", "neuters the function that paints black-bar/divider overlays for 2p/3p/4p splitscreen (uses only slot 0's x0/x1 for pillarbox bars, spanning full height - assumes horizontal top/bottom splits only, paints over anything past slot 0's x1 for vertical/custom splits)", 0x2C6D84, "\x31\xC0\xC3\x90", false}, }}}; static std::unordered_map map_modules { diff --git a/src/mcc/module/Module.cpp b/src/mcc/module/Module.cpp index 6d3da9f..8343545 100644 --- a/src/mcc/module/Module.cpp +++ b/src/mcc/module/Module.cpp @@ -1,6 +1,7 @@ #include "Module.h" #include +#include #include "tinyxml2.h" @@ -120,25 +121,25 @@ bool MCC::Module::ReloadPatch(const char *xml_path) { #include "global/Global.h" namespace MCC::Module { - void ContextPatch(); + void ContextDevTools(); void ContextEngine(); void ImGuiContext() { - static bool show_patch; + static bool show_devtools; static bool show_engine; if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMenu("Game")) { ImGui::MenuItem("Engine", nullptr, &show_engine); - ImGui::MenuItem("Patch", nullptr, &show_patch); ImGui::EndMenu(); } + ImGui::MenuItem("Dev Tools", nullptr, &show_devtools); ImGui::EndMainMenuBar(); } - if (show_patch) { - if (ImGui::Begin("Patch", &show_patch, ImGuiWindowFlags_MenuBar)) - ContextPatch(); + if (show_devtools) { + if (ImGui::Begin("Dev Tools", &show_devtools, ImGuiWindowFlags_MenuBar)) + ContextDevTools(); ImGui::End(); } @@ -196,7 +197,7 @@ namespace MCC::Module { #pragma endregion } - void ContextPatch() { + void ContextDevTools() { static int counter; auto p_print = [](CPatch* patch) { bool enabled = patch->enabled(); @@ -209,6 +210,12 @@ namespace MCC::Module { ImGui::SetTooltip("%s", patch->desc()); }; + auto find_patch = [](CPatchSet* p_patches, const char* name) -> CPatch* { + for (auto patch : p_patches->embed_patches()) + if (strcmp(patch->name(), name) == 0) return patch; + return nullptr; + }; + if (ImGui::BeginMenuBar()) { if (ImGui::MenuItem("Reload Patch")) ReloadPatch(); @@ -224,13 +231,188 @@ namespace MCC::Module { if (ImGui::BeginTabItem(cModuleName[i])) { ImGui::Text("Embed Patches"); - for (auto patch : p_patches->embed_patches()) + for (auto patch : p_patches->embed_patches()) { + // HaloReach's black-bar patches get replaced below with two + // controls scoped by which player/slot they actually affect, + // instead of three raw, easy-to-misread checkboxes. + if (i == MODULE_HALOREACH && + (strcmp(patch->name(), "Remove Black Bar1") == 0 || + strcmp(patch->name(), "Remove Black Bar2") == 0 || + strcmp(patch->name(), "Remove Black Bar3") == 0)) + continue; + p_print(patch); + } + + if (i == MODULE_HALOREACH) { + auto p_bar1 = find_patch(p_patches, "Remove Black Bar1"); + auto p_bar2 = find_patch(p_patches, "Remove Black Bar2"); + auto p_bar3 = find_patch(p_patches, "Remove Black Bar3"); + + ImGui::Separator(); + ImGui::Text("Splitscreen Display"); + + if (p_bar1 != nullptr && p_bar3 != nullptr) { + bool top = p_bar1->enabled() && p_bar3->enabled(); + ImGui::PushID(counter++); + if (ImGui::Checkbox("Remove Black Bars - Player 1 (Top, 2 or 3 Player)", &top)) { + p_bar1->setState(top); + p_bar3->setState(top); + + // The game's black-bar overlay is a single shared + // painter that only ever reads Player 1's bounds, + // so Player 2's bar physically cannot disappear + // while Player 1 still has one - turning Player 1 + // back on makes that combination broken again. + if (!top && p_bar2 != nullptr && p_bar2->enabled()) + p_bar2->setState(false); + } + ImGui::PopID(); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Removes the black bar for whichever player occupies the top half of the screen. Applies to both 2-player and 3-player splitscreen - that slot is the same shape in either mode."); + } + + if (p_bar2 != nullptr) { + bool player1_on = p_bar1 != nullptr && p_bar1->enabled(); + bool bottom = p_bar2->enabled(); + + ImGui::BeginDisabled(!player1_on); + ImGui::PushID(counter++); + if (ImGui::Checkbox("Remove Black Bars - Player 2 (Bottom, 2 Player Only)", &bottom)) + p_bar2->setState(bottom); + ImGui::PopID(); + ImGui::EndDisabled(); + + // ImGui suppresses IsItemHovered() by default for items inside + // BeginDisabled()/EndDisabled() - AllowWhenDisabled is required + // so the explanation tooltip still shows while greyed out. + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + if (player1_on) + ImGui::SetTooltip("Removes the black bar for player 2's bottom half of the screen. 2-player only - in 3-player mode, players 2 and 3 already fill their quarter of the screen with no black bars."); + else + ImGui::SetTooltip("Requires Player 1's black bar removed too - the game's bar-painting logic is shared and only reads Player 1's bounds, so Player 2's bar can't disappear on its own."); + } + } + + ImGui::Separator(); + } ImGui::Text("Patches"); for (auto patch : p_patches->patches()) p_print(patch); + if (i == MODULE_HALOREACH) { + auto hModule = GetSubModule((eModule)i)->info().hModule; + + constexpr __int64 base_offset = 0xB43C40; + constexpr int entry_size = 20; + constexpr int block_count = 5; + constexpr int slot_count = 4; + + if (ImGui::CollapsingHeader("Splitscreen Config Editor")) { + if (hModule == 0) { + ImGui::TextColored(ImVec4(1, 0.3f, 0.3f, 1), "haloreach.dll not loaded"); + } else { + static const char* block_labels[block_count] = { + "0: alias of 4p", "1: 1 player", "2: 2 players", + "3: 3 players", "4: 4 players" + }; + + auto write_bytes = [](void* dst, const void* src, size_t size) { + DWORD oldProtect; + if (VirtualProtect(dst, size, PAGE_EXECUTE_READWRITE, &oldProtect)) { + memcpy(dst, src, size); + VirtualProtect(dst, size, oldProtect, &oldProtect); + } + }; + + ImGui::TextDisabled("Edits write directly to live game memory. No rebuild needed;"); + ImGui::TextDisabled("changes are lost on game restart unless also set as a patch."); + + if (ImGui::BeginTable("splitscreen_config", 6, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_SizingFixedFit)) { + ImGui::TableSetupColumn("Slot", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("x0", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("y0", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("x1", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("y1", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableSetupColumn("resolution", ImGuiTableColumnFlags_WidthFixed, 80.0f); + ImGui::TableHeadersRow(); + + for (int block = 0; block < block_count; ++block) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextColored(ImVec4(0.6f, 0.8f, 1.0f, 1.0f), "%s", block_labels[block]); + + for (int slot = 0; slot < slot_count; ++slot) { + int index = block * slot_count + slot; + auto p_entry = (unsigned char*)(hModule + base_offset + (__int64)index * entry_size); + + float vals[4]; + __int32 res; + memcpy(vals, p_entry, 16); + memcpy(&res, p_entry + 16, 4); + + ImGui::TableNextRow(); + ImGui::PushID(index); + + ImGui::TableSetColumnIndex(0); + ImGui::Text(" slot %d", slot); + + const char* field_labels[4] = {"##x0", "##y0", "##x1", "##y1"}; + for (int f = 0; f < 4; ++f) { + ImGui::TableSetColumnIndex(1 + f); + ImGui::SetNextItemWidth(-1); + if (ImGui::InputFloat(field_labels[f], &vals[f], 0.0f, 0.0f, "%.4f")) + write_bytes(p_entry + f * 4, &vals[f], 4); + } + + ImGui::TableSetColumnIndex(5); + ImGui::SetNextItemWidth(-1); + if (ImGui::InputInt("##res", &res, 0, 0)) + write_bytes(p_entry + 16, &res, 4); + + ImGui::PopID(); + } + } + ImGui::EndTable(); + } + } + } + + if (ImGui::Button("Dump Splitscreen Config Table to Log")) { + if (hModule == 0) { + LOG_ERROR("Dump Splitscreen Config Table: haloreach.dll not loaded"); + } else { + LOG_INFO("=== c_splitscreen_config::m_config_table @ base+0x{:X} ===", base_offset); + + for (int block = 0; block < block_count; ++block) { + for (int slot = 0; slot < slot_count; ++slot) { + int index = block * slot_count + slot; + auto p_entry = (unsigned char*)(hModule + base_offset + (__int64)index * entry_size); + + float f0, f1, f2, f3; + __int32 res; + memcpy(&f0, p_entry + 0, 4); + memcpy(&f1, p_entry + 4, 4); + memcpy(&f2, p_entry + 8, 4); + memcpy(&f3, p_entry + 12, 4); + memcpy(&res, p_entry + 16, 4); + + LOG_INFO("block {} slot {} (entry {}, offset 0x{:X}): raw=({:.4f}, {:.4f}, {:.4f}, {:.4f}) res={}", + block, slot, index, base_offset + (__int64)index * entry_size, f0, f1, f2, f3, res); + } + } + + LOG_INFO("=== end dump ==="); + } + } + + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Reads the live, unpatched-if-not-toggled bytes directly from haloreach.dll and logs them. Run this BEFORE toggling any Remove Black Bar checkbox to capture true defaults."); + } + ImGui::EndTabItem(); } } diff --git a/src/mcc/module/entry/haloreach/loadout.cpp b/src/mcc/module/entry/haloreach/loadout.cpp new file mode 100644 index 0000000..08672d1 --- /dev/null +++ b/src/mcc/module/entry/haloreach/loadout.cpp @@ -0,0 +1,28 @@ +#include "haloreach.h" + +#include "common.h" + +namespace HaloReach::Entry::Loadout { + typedef unsigned __int64 (*GetSplitscreenPlayerCount_t)(); + + // The game's own resolution->resource-ID lookup only recognizes resolution + // values 0, 2 and 3 (each maps to a registered loadout layout template); any + // other value, including 1, falls through to a "not found" default and the + // loadout screen never gets built. resolution=1 is otherwise the cleanest + // splitscreen mode (no HUD/crosshair artifacts), so we reuse resolution=3's + // already-working template ID for it instead of patching the game's own + // (space-constrained) lookup function directly. + HaloReachEntry(entry, OFFSET_HALOREACH_PF_SPLITSCREEN_RESOLUTION_RESOURCE, int, detour, int slot_index) { + __int64 hModule = entry.m_target - entry.m_offset; + + if (slot_index >= 0 && slot_index < 4) { + auto GetSplitscreenPlayerCount = (GetSplitscreenPlayerCount_t)(hModule + OFFSET_HALOREACH_PF_GET_SPLITSCREEN_PLAYER_COUNT); + int player_count = (int)GetSplitscreenPlayerCount(); + int* p_resolution = (int*)(hModule + 0xB43C40 + 0x10 + (size_t)(slot_index + player_count * 4) * 20); + + if (*p_resolution == 1) return 0x80046; + } + + return ((detour_t)entry.m_pOriginal)(slot_index); + } +} diff --git a/src/mcc/splitscreen/Splitscreen.cpp b/src/mcc/splitscreen/Splitscreen.cpp index c47fc36..d7bc352 100644 --- a/src/mcc/splitscreen/Splitscreen.cpp +++ b/src/mcc/splitscreen/Splitscreen.cpp @@ -186,20 +186,7 @@ namespace MCC::Splitscreen { ); ImGui::EndMenu(); } -#pragma region player count - ImGui::PushItemWidth(200); - // int count = p_setting->player_count; - // if (ImGui::InputInt("Players", &count) && count >= 1 && count <=4) { - // p_setting->player_count = count; - // } - int count = p_setting->player_count; - if (ImGui::InputInt("Players", &count) && count >= 1 && count <= 4) { - p_setting->player_count = count; - dirty = true; - } - ImGui::PopItemWidth(); ImGui::EndMenuBar(); -#pragma endregion } if (ImGui::BeginTabBar("Players")) { diff --git a/src/render/window/Window.cpp b/src/render/window/Window.cpp index a2baa84..30b516a 100644 --- a/src/render/window/Window.cpp +++ b/src/render/window/Window.cpp @@ -48,7 +48,13 @@ namespace AlphaRing::Render::Window { } } - if (ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) + // Only feed ImGui real WndProc input while one of our menus is actually + // visible — otherwise ImGui silently queues mouse/keyboard events for the + // entire play session with nothing ever consuming them (NewFrame() is + // also gated on menu visibility), which causes a glitch/jump once the + // menu is finally opened again after a long session. + bool menuActive = xboxOpen || AlphaRing::Global::Global()->show_imgui; + if (menuActive && ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) return true; // Keyboard trigger to open the menu (only reached when menu is closed)