Files
NX2navi/runtime/ui/menu_win.cpp
epicmeme546 5a69bdc91f Add a debug menu for testing Ocarina of Time: the game's flags by name, the game's speed, and a memory search, on F9.
With debug_menu = 1 (off by default; the Game tab has the switch) F9 opens a panel on the left edge in the
performance overlay's look, with the game in view beside it. ui/menu.cpp lays it out in the settings menu's place,
so its input and the game's blocked pad are the menu's own. Typed characters now reach the menu for its text fields.

Flags: the game's 11,292 on/off flags are read from its game data manager, and a click changes one through the
game's own function on the game's thread at its pad read (runtime/debug_tools.c dbg_tick), so the game applies it
at its next update as it does its own changes. The game knows a flag by the hash of its name only; the names shown
are found in the player's own event flow files, hashed and matched when the panel first opens (2,250 of them from
915 files in 0.2 s), and none is kept in the code. World: Link's position, and a speed factor from 0.05 to 8 that
multiplies the step the runtime gives the game each frame (measured 0.50 at 0.5x). Memory: a search of the heap
for a value of 1, 2 or 4 bytes or a float (about 2 s), narrowed by equals, changed, unchanged, went up or went
down, with a value to set at a place or to hold there every frame.

Tested through the window's own messages: F9 opened it, a click on a flag set that flag in the game, text typed
into the filter narrowed the list, and F9 closed it; a scripted run set and held a value and put it back.

Teleporting, a free camera and starting cutscenes are not in it: writing Link's position, or every copy of it in
the heap, moved him back within three frames, so they need the game's own warp, scene-change and event functions.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
2026-11-11 06:19:47 -05:00

118 lines
4.9 KiB
C++

// Win32 adapter of the in-game menu: window messages to menu events (menu.h). The only Win32 code of the menu; the SDL3
// window has its own (ui/menu_sdl.cpp). Window thread.
//
// F1 opens and closes the menu, Esc opens it (with the menu open, Esc goes to the menu, which closes). Esc also releases
// the cursor (input_window_msg runs first; closing the menu gives the capture back). While the menu is open, its keys,
// the mouse and focus changes go to it; the game polls the keyboard on its own (input_win.c) and gets nothing while the
// menu is open (input_menu).
#include <windows.h>
#include <windowsx.h>
#include "imgui.h"
extern "C" {
#include "input.h"
#include "input_win.h"
#include "ui/menu.h"
}
static ImGuiKey key_of(WPARAM vk) {
switch (vk) {
case VK_UP: return ImGuiKey_UpArrow;
case VK_DOWN: return ImGuiKey_DownArrow;
case VK_LEFT: return ImGuiKey_LeftArrow;
case VK_RIGHT: return ImGuiKey_RightArrow;
case VK_RETURN: return ImGuiKey_Enter;
case VK_SPACE: return ImGuiKey_Space;
case VK_ESCAPE: return ImGuiKey_Escape;
case VK_TAB: return ImGuiKey_Tab;
case VK_BACK: return ImGuiKey_Backspace;
case VK_DELETE: return ImGuiKey_Delete; // clears a cell of the remapping rows
case VK_HOME: return ImGuiKey_Home;
case VK_END: return ImGuiKey_End;
case VK_PRIOR: return ImGuiKey_PageUp;
case VK_NEXT: return ImGuiKey_PageDown;
case VK_SHIFT: return ImGuiKey_LeftShift;
case VK_CONTROL: return ImGuiKey_LeftCtrl;
case 'Q': return ImGuiKey_Q; // previous tab
case 'E': return ImGuiKey_E; // next tab
default: return ImGuiKey_None;
}
}
// The mouse buttons pressed on the open menu and not yet released, one bit each. While any is held the window has the
// mouse capture, so the release reaches the menu where the pointer went outside the window (else ImGui would hold the
// button down and miss the next click). The game itself only clips the cursor (input_win.c), it takes no capture.
static unsigned g_held;
extern "C" int menu_window_msg(HWND h, UINT m, WPARAM w, LPARAM l, LRESULT *res) {
switch (m) {
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
// bit 30: auto-repeat
// Esc with the menu open (opened by F1 too) is not a toggle here: it goes to the menu, which closes itself
if (!(l & (1 << 30)) && (w == VK_F1 || (w == VK_ESCAPE && !menu_is_open()))) {
menu_toggle();
*res = 0;
return 1;
}
if (w == VK_F9 && menu_debug_enabled()) { // the debug menu, while its setting is on
if (!(l & (1 << 30))) menu_debug_toggle();
*res = 0;
return 1;
}
if (w == VK_F10) { // also keeps F10 from opening the window menu
if (!(l & (1 << 30))) menu_perf_cycle();
*res = 0;
return 1;
}
// a held Esc's repeats: the press that opened the menu is not queued, so its first repeat would close it
if (w == VK_ESCAPE && (l & (1 << 30))) return 0;
[[fallthrough]];
case WM_KEYUP:
case WM_SYSKEYUP:
if (!menu_is_open()) return 0;
if (ImGuiKey k = key_of(w)) menu_event_key(k, m == WM_KEYDOWN || m == WM_SYSKEYDOWN);
return 0; // F11 and the system keys keep working
case WM_CHAR: // typed text, for the debug menu's fields
if (menu_is_open() && w >= 0x20 && w < 0xd800) menu_event_char((unsigned)w);
return 0;
case WM_MOUSEMOVE:
if (menu_is_open()) menu_event_mouse_pos((float)GET_X_LPARAM(l), (float)GET_Y_LPARAM(l));
return 0;
case WM_LBUTTONDOWN: case WM_LBUTTONUP:
case WM_RBUTTONDOWN: case WM_RBUTTONUP:
case WM_MBUTTONDOWN: case WM_MBUTTONUP: {
int b = m == WM_LBUTTONDOWN || m == WM_LBUTTONUP ? 0 : m == WM_RBUTTONDOWN || m == WM_RBUTTONUP ? 1 : 2;
int down = m == WM_LBUTTONDOWN || m == WM_RBUTTONDOWN || m == WM_MBUTTONDOWN;
if (!down && (g_held & 1u << b)) { // also when the menu closed meanwhile: the capture goes either way
g_held &= ~(1u << b);
if (!g_held && GetCapture() == h) ReleaseCapture();
}
if (!menu_is_open()) return 0;
if (down) {
if (!g_held) SetCapture(h);
g_held |= 1u << b;
}
menu_event_mouse_pos((float)GET_X_LPARAM(l), (float)GET_Y_LPARAM(l));
menu_event_mouse_button(b, down);
return 0;
}
case WM_MOUSEWHEEL:
if (menu_is_open()) menu_event_wheel(0, (float)GET_WHEEL_DELTA_WPARAM(w) / WHEEL_DELTA);
return 0;
case WM_CAPTURECHANGED: // another window took the capture (ReleaseCapture above finds g_held empty)
if ((HWND)l != h && g_held) {
for (int b = 0; b < 3; b++)
if (g_held & 1u << b) menu_event_mouse_button(b, 0);
g_held = 0;
}
return 0;
case WM_SETFOCUS:
case WM_KILLFOCUS:
menu_event_focus(m == WM_SETFOCUS);
return 0;
}
return 0;
}