From 1be4c7a200142eec59ffe53a35323f8002188549 Mon Sep 17 00:00:00 2001 From: Mimic Date: Tue, 10 Nov 2026 17:36:08 -0500 Subject: [PATCH 01/10] Update test_env_file.py --- tests/test_env_file.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_env_file.py b/tests/test_env_file.py index 9d6b45c57..e7adcb2b0 100644 --- a/tests/test_env_file.py +++ b/tests/test_env_file.py @@ -40,7 +40,7 @@ def main(): "HOS_T_BESIDE=1\nHOS_T_SHARED=beside\nVK_ADD_LAYER_PATH=/x\nLD_PRELOAD=/x.so\nPATH=C:\\x\nSDL_DYNAMIC_API=/x.so\n" "HOS_ENV_FILE=other.txt\nHOS_SAMPLE_FILE=/home/x/.bashrc\nRX_VK_DUMP_DIR=C:\\Users\\x\nRX_VK_SKIP_CODE=2c56,ab\n" "RX_VK_DLSS_DIR=/opt/ngx\nHOS_T_HOME=~\nSDL_VULKAN_LIBRARY=x.dll\nSDL_VIDEO_EGL_DRIVER=x.so\n" - "SDL_AUDIODRIVER=pulseaudio\nHOS_T_UP=..\nHOS_SAMPLE_FILE=hookshot_vk.dll\nRX_VK_FRAME_LOG=x.txt\n" + "SDL_AUDIO_DRIVER=pulseaudio\nSDL_AUDIODRIVER=pulseaudio\nHOS_T_UP=..\nHOS_SAMPLE_FILE=hookshot_vk.dll\nRX_VK_FRAME_LOG=x.txt\n" "RX_VK_DUMP_DIR=dumps\nHOS_PRESENT_TRACE=t.txt\nRX_VK_DLSS_DIR=\\\\server\\share\n" "HOS_AUDIO_DUMP=image.bin\nRX_VK_HANG_DUMP=hookshot_options.ini\nRX_VK_SHOTS=user\n" "HOS_INPUT_SCRIPT=1:A,2:A\nRX_VK_MENU_SCRIPT=1:INS\n", encoding="utf-8") @@ -83,7 +83,8 @@ def main(): CHECK(is("HOS_INPUT_SCRIPT", NULL) && is("RX_VK_MENU_SCRIPT", NULL)); CHECK(is("RX_VK_SKIP_CODE", "2c56,ab") && is("RX_VK_DLSS_DIR", "/opt/ngx")); CHECK(is("SDL_VULKAN_LIBRARY", NULL) && is("SDL_VIDEO_EGL_DRIVER", NULL) && is("HOS_T_UP", NULL)); - CHECK(is("SDL_AUDIODRIVER", "pulseaudio") && is("HOS_SAMPLE_FILE", NULL) && is("RX_VK_FRAME_LOG", NULL)); + CHECK(is("SDL_AUDIO_DRIVER", "pulseaudio") && is("SDL_AUDIODRIVER", "pulseaudio")); + CHECK(is("HOS_SAMPLE_FILE", NULL) && is("RX_VK_FRAME_LOG", NULL)); CHECK(is("RX_VK_DUMP_DIR", NULL) && is("HOS_PRESENT_TRACE", NULL)); CHECK(getenv("LD_PRELOAD") == NULL || strcmp(getenv("LD_PRELOAD"), "/x.so")); CHECK(getenv("PATH") == NULL || strcmp(getenv("PATH"), "C:\\x")); @@ -119,3 +120,4 @@ def main(): if __name__ == "__main__": main() + From cbb030a2352b2a628f9d1b300c80b2804da6e532 Mon Sep 17 00:00:00 2001 From: Mimic Date: Tue, 10 Nov 2026 17:38:38 -0500 Subject: [PATCH 02/10] Create hookshot_env.txt --- hookshot_env.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 hookshot_env.txt diff --git a/hookshot_env.txt b/hookshot_env.txt new file mode 100644 index 000000000..2405d6acb --- /dev/null +++ b/hookshot_env.txt @@ -0,0 +1,2 @@ +RX_VK_NOOPT=1 +RX_VK_CHECKPOINTS=1 From 0580e192bd95a59cfe1ecfa98a92cf4ba5949c42 Mon Sep 17 00:00:00 2001 From: Mimic Date: Tue, 10 Nov 2026 17:39:05 -0500 Subject: [PATCH 03/10] Create loader.bat --- loader.bat | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 loader.bat diff --git a/loader.bat b/loader.bat new file mode 100644 index 000000000..8ed93ae88 --- /dev/null +++ b/loader.bat @@ -0,0 +1,4 @@ +@echo off +set RX_VK_GPU_UNSWIZZLE=1 +set RX_VK_BATCH=1 +hookshot.exe From db4be99b166246445fa188d733e42b681933defb Mon Sep 17 00:00:00 2001 From: Mimic Date: Tue, 10 Nov 2026 18:20:50 -0500 Subject: [PATCH 04/10] Delete gpu/vk/vk_menu.h --- gpu/vk/vk_menu.h | 109 ----------------------------------------------- 1 file changed, 109 deletions(-) delete mode 100644 gpu/vk/vk_menu.h diff --git a/gpu/vk/vk_menu.h b/gpu/vk/vk_menu.h deleted file mode 100644 index 64bcaa25e..000000000 --- a/gpu/vk/vk_menu.h +++ /dev/null @@ -1,109 +0,0 @@ -// The Insert menu (vk_menu.cpp): options that apply while the game runs, kept in hookshot_options.ini beside the executable. -#pragma once - -#include "vk_internal.h" - -namespace mkwvk { - -// (out of the box every feature is off: what the game does on its own. The ini beside the executable keeps a player's choices) -struct Options { - int framegen = 0; // made frames: 0 off, 1 Auto (to the display's refresh), or 2 to 8 times the game's frames - int pace = 0; // frames shown on an even line (vk_framegen.cpp) - int hdr = 0; // (hookshot) HDR10 out (vk_framegen.cpp framegen_hdr): 0 off, 1 the SDR picture expanded, 2 the game's own (native) HDR; when the display has it (the swapchain made again at once) - int hdr_white = 200; // HDR paper white, nits (the picture's white) - int hdr_peak = 1000; // HDR peak brightness, nits (its brightest highlights) - int fps_cap = 0; // (hookshot) Frame rate cap: 0 60 (the console's), 90, 120, 144, 165, 240 (host/nn_vi.c reads frame_rate_cap= at the start) - int resolution = 0; // the 3D's (Render resolution): 0 Auto (the game's dynamic resolution), else a share of the output, - // 1..6 = 100%, 75%, 67%, 50%, 58%, 33% (upscaler_rows.h; nx2/oot_patches.py) - int output_res = 0; // the game's whole frame, docked: 0 its 2048x1152, 1 2560x1440, 2 3840x2160, 3 1920x1080, 4 5120x1440, 5 5120x2880, - // 6 the player's own (vk_menu.cpp custom_output); saved as the size (output_res=3840x2160) and read once, at the start - // (host/nn_applet.c) - int upscale = 0; // to the window (Window scaling filter, saved as upscaling): 0 off (the plain copy), 1 Catmull-Rom, 2 Lanczos - int reflex = 0; // NVIDIA Reflex: 0 off, 1 on, 2 on + boost (vk_reflex.cpp; NVIDIA GPUs with VK_NV_low_latency2) - int antilag = 0; // AMD Anti-Lag 2: 0 off, 1 on (vk_antilag.cpp; AMD GPUs with VK_AMD_anti_lag) - int dlss = 0; // the Upscaler (its name from before FSR: options files and the tests' stand-ins keep it): 0 the game's - // own network, 1 NVIDIA DLSS, 2 NVIDIA DLAA (a build with NVIDIA's SDK only), 3 AMD FSR 3.1 (vk_fsr3.cpp), - // 4 AMD FSR 4, 5 its native AA (a build with FSR 4 only). upscaler_rows.h; vk_temporal.cpp. Saved as dlss - int dlss_model = 0; // DLSS's network, by its preset: 0 K (transformer), 1 M, 2 L (second-generation transformer), 3 NVIDIA's - // default (upscaler_rows.h model_preset; vk_dlss.cpp). RX_VK_DLSS_PRESET over it - int sharpness = 0; // 0 to 100 - int anisotropy = 4; // (hookshot) Anisotropic filtering: 0 the game's own (1x or 2x), 1 2x, 2 4x, 3 8x, 4 16x (the - // device's limit at most; gpu/vk/vk_draw.cpp aniso_override, RX_VK_ANISO over it) - int realtime = 1; // game time follows the clock (host/nn_vi.c); 0: per frame (slower frames, slower game) - int handheld = 0; // the game's handheld profile: ~4 fps, softer; read once, at the start (host/nn_applet.c) - int ultrawide = 0; // the camera at the monitor's shape when it is wider than 16:9; read once, at the start (host/hos_main.c) - int wide_menus = 1; // ultrawide: the pause, item and map menus 0 at full width (stretched), 1 in the middle 16:9 with the sides - // blurred, 2 with black bars (nx2/oot_wide.py, vk_framegen.cpp) - int show_fps = 0; - int menu_shortcut = 0; // 0 Insert or F1, 1 F1 only (F1 always opens the menu) - int menu_size = 0; // (hookshot) the overlays' size: 0 Auto, 1 Small, 2 Medium, 3 Large (gpu/vk/vk_ui.h unit) - int fullscreen = 0; // borderless, at the monitor's size: F11 is the same setting - int gpu_device = 0; // graphics card index: 0 Auto, 1..N device 0..N-1 (gpu/vk/vk_main.cpp; RX_VK_DEVICE) - int pad_background = 0; // the controllers count while another window has the focus (host/nn_hid.c; HOS_INPUT_BACKGROUND) - int mouse_native = 0; // the mouse turns the camera's own angles (host/mouse_native.c); 0: as the right stick - int disable_camera_centering = 0; // 0: stock movement-follow turning; 1: keep the player's horizontal direction - int mouse_sens = 100; // the mouse's sensitivity in percent: 0.1 degrees a count at 100 with native look (host/nn_hid.c) - int mouse_invert = 0; // the mouse's up and down swapped - int text_speed = 0; // how fast dialogue prints: 0 the game's, 1 2x, 2 4x, 3 instant (host/nn_vi.c, nx2/oot_patches.py; HOS_TEXT_SPEED) - int text_size = 0; // dialogue and signs' text size: 0 the game's, 1 110%, 2 125%, 3 150%, 4 175% (host/oot_text_size.c, - // nx2/oot_patches.py; HOS_TEXT_SIZE) - int invert_cam_x = 0; // right-stick camera horizontal inversion: 0 normal, 1 inverted - int invert_cam_y = 0; // right-stick camera vertical inversion: 0 normal, 1 inverted - int rumble = 100; // the controllers' rumble in percent of the game's: 0 Off, 25 to 150 (host/pad_sdl.c) - int stick_dead_zone = 0; // a stick axis below it is 0: 0 XInput's 7849 (~24%), 1..5 5% to 30% (host/nn_hid.c) - int pad_layout = 0; // 0 Nintendo (default), 1 Xbox / PC, 2 Swap Face Buttons, 3 Custom - int gyro = 1; // motion controls from a pad's gyroscope (host/pad_sdl.c, host/hos_motion.h); on by default - int gyro_sens_x = 100; // the motion aim's horizontal sensitivity in percent - int gyro_sens_y = 100; // the motion aim's vertical sensitivity in percent - int gyro_inv_x = 0; // the motion aim's horizontal turned around - int gyro_inv_y = 0; // the motion aim's vertical turned around - int gyro_deadzone = 1; // the motion's deadzone: 0 tight, 1 standard (the console's default), 2 loose - int prompts = 0; // the button icons: 0 Auto (the controller), 1 Switch, 2 Xbox, 3 PS4, 4 PS5, 5 Steam Deck (host/prompts.c) - int robust = 0; // bounds-checked buffer access: 0 Auto (on), 1 Off, 2 On (gpu/vk/vk_main.cpp; RX_VK_ROBUST) - int descriptor_reuse = 1; // (on by default since options_version 4) a draw keeps its descriptors through other slots' registrations (gpu/vk/vk_draw.cpp; RX_VK_DESC_SLOTGEN) - int sampler_cache = 0; // sampler ids answer their parameters without the registrations' lock (gpu/vk/vk_draw.cpp; RX_VK_SAMPLER_CACHE) - int preset = 0; // quality preset: 0 Custom, 1 Minimum (4GB), 2 Low (6GB), 3 Balanced (8GB), 4 High (12GB), 5 Ultra (16GB+) - int vram_mb = 0; // dynamic texture cache budget limit in MB - int mirror_mb = 0; // staging mirror limit in MB - int audio_output = 0; // (hookshot) the speakers the game mixes for: 0 Auto (the default output's), 1 Stereo, 2 5.1; read once, at the start (host/nn_audio.c; HOS_AUDIO_CHANNELS) - int discord = 1; // (hookshot) the Discord status (host/discord_rpc.c): 1 on, shown while a Discord client runs on this PC - int debug_tools = 0; // (hookshot) the Debug tab's tools (#785: flight, free camera, teleport, inventory): 0 off; HOS_OOT_CHEATS=1 also turns them on - int volume = 100; // (hookshot) the game's sound in percent, 0 to 100 (host/nn_audio.c: HOS_OPT_VOLUME, 27) - int mute_background = 0; // (hookshot) no sound while another window has the focus (host/nn_audio.c: HOS_OPT_MUTE_BACKGROUND, 28) - int mic = 1; // (hookshot) the ocarina's microphone: 0 Off, 1 the system's default, 2.. a device as host->mic_name lists it (host/nn_audio_in.c) -}; -extern Options opts; -/// (vk_memory.cpp) true when the quality preset's memory budgets (vram_mb, mirror_mb) are to be used: the card has no more -/// video memory than the preset is for. A 4GB preset on an 8 GB card kept 256 MB of mirrors and 2.2 GB of textures: the -/// game's memory was read over PCIe and the textures made again every frame, 5 fps with the CPU idle (Discord, 11-08) -bool preset_caps_memory(); - -void menu_init(); // (the options from the file, once) -void menu_set_gpu_count(int count); -void menu_tick(); // (each frame shown: the keys, while the game's window has the focus) -bool menu_is_open(); -bool resolution_auto(); // (the game's dynamic resolution: Resolution Auto) -int resolution_scale(); // (the fixed path's scale in 1/10000: the Resolution row's, as a twelfth of the output) -void menu_option_set(int id, int value); // (from the game's side: 7 the mouse's sensitivity, the - and = keys) -int menu_heart_protection(); -int menu_player_flight(); -int menu_free_camera(); -int menu_take_cheat_action(); -void menu_note_frame(bool game_frame, int shown = 1); // (the frame rates the FPS counter shows) -/// The game's picture's size (the Output resolution, its crop) and the window's, at each present: the Window scaling filter -/// row says when it is not needed -void menu_note_present_sizes(uint32_t picture_w, uint32_t picture_h, uint32_t window_w, uint32_t window_h); -/// Over the window's image `index` (TRANSFER_DST_OPTIMAL before and after), w x h: the start-up screen, the menu and the FPS -/// counter, drawn with Dear ImGui (vk_imgui.h, vk_ui.h). Nothing recorded while none of them shows -void menu_record(VkCommandBuffer cmd, uint32_t index, uint32_t w, uint32_t h); -/// (headless shots, RX_VK_SHOT_MENU) The menu and the counter over another image: TRANSFER_DST_OPTIMAL before and after, -/// R8G8B8A8_UNORM with colour-attachment use, view a view of it -void menu_record_image(VkCommandBuffer cmd, VkImage image, VkImageView view, uint32_t w, uint32_t h); -/// (hookshot) The start-up screen while the shaders are prepared (vk_main.cpp, vk_progress.h), drawn by menu_record under the -/// counter and the menu. Null takes it away. -struct StartupView; -void startup_screen(const StartupView* view); -/// The start-up screen's fade to black as it ends: 0 none, 1 black (vk_main.cpp prepare_with_screen) -void startup_fade_to(float f); - -} // namespace mkwvk From aa6a25b875f20ee7baca3f0e5b0f2294f05af44c Mon Sep 17 00:00:00 2001 From: Mimic Date: Tue, 10 Nov 2026 18:21:25 -0500 Subject: [PATCH 05/10] Delete gpu/vk/vk_menu.cpp --- gpu/vk/vk_menu.cpp | 3145 -------------------------------------------- 1 file changed, 3145 deletions(-) delete mode 100644 gpu/vk/vk_menu.cpp diff --git a/gpu/vk/vk_menu.cpp b/gpu/vk/vk_menu.cpp deleted file mode 100644 index 9cb3ead0f..000000000 --- a/gpu/vk/vk_menu.cpp +++ /dev/null @@ -1,3145 +0,0 @@ -// The Insert menu. Drawn with Dear ImGui over the window's image at the last step of a present (menu_record; vk_imgui.h, in the -// game's style: vk_ui.h), so it costs nothing while closed and it never touches the game's picture, nor what frame interpolation -// sees. The FPS counter and the start-up screen are drawn the same way. F1 always opens and closes it, Insert too by default. -// Up and Down choose, Left and Right change, Esc closes; the game gets no keyboard input while it is open. -// (hookshot) A gamepad's Home button, or Select and Start together, opens it too; the D-pad chooses and changes, the button -// the Switch's A is bound to changes too (and opens a page), the one B is bound to goes back (vk_menu_keys.h). The game gets no gamepad input while it is open either. -// Its Controls page binds the Switch's buttons to gamepad inputs and keys. -// (hookshot) The mouse is free while it is open (the window lets it go: WM_APP + 8, host/display.c and -// host/posix/display_sdl.c): the row under the cursor is selected, a click on a tab shows it, on a value's left or right -// half changes it down or up, on a toggle flips it, on a slider's track sets it (or drag it); the wheel scrolls the rows -// (never a value, wherever the cursor is); a right click is Esc. On the Cheats page and its lists a click is Enter (on a -// choice's or an amount's left or right half, Left or Right) and a right click goes back. The drawing records each control's -// place and the mouse is tested against that (vk_menu_hit.h). -// (hookshot) The rows that take a restart (Row::restart) are tagged "After restart" while their value is not the one the game -// started with, and the Graphics tab's help lists them. Closing the menu then asks whether to restart now (menu_restart.h): -// Later keeps the saved settings for the next start and is not asked again until they change; Restart now saves and -// closes the window as the player would, and the game starts again (WM_APP + 9: host/hos_relaunch.c). -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "vk_paths.h" -#include "vk_menu.h" -#include "vk_menu_hit.h" -#include "vk_draw.h" // (pipeline_cache_save: Restart now keeps what was built) -#include "oot_cheat_items.h" -#include "vk_progress.h" // (StartupView, version_text: the start-up screen) -#include "vk_ui.h" -#include "vk_menu_keys.h" // (which gamepad buttons confirm and go back: those A and B are bound to) // (how it looks: Dear ImGui, vk_imgui.h) -#include "vk_dlss.h" -#include "vk_fsr3.h" // (the Upscaler row: whether AMD FSR 3.1 runs on this GPU) -#include "vk_fsr3fi.h" // (the Frame generation row: whether FSR 3.1 makes the frame) -#include "vk_fsr4.h" -#include "vk_temporal.h" -#include "vk_reflex.h" -#include "vk_antilag.h" -#include "upscaler_rows.h" -#include "menu_restart.h" -#include "vk_work.h" // (after the others: counts the commands that do GPU work, for full_barrier) -#ifndef HOOKSHOT_VERSION // (nx2/build.py gives it to this file: nx2/version.py) -#define HOOKSHOT_VERSION "unknown" -#endif - -namespace mkwvk { -HWND app_window(); - -Options opts; - -namespace { -enum MenuTab { - TAB_GRAPHICS = 0, - TAB_AUDIO = 1, - TAB_GAMEPLAY = 2, - TAB_CONTROLS = 3, - TAB_DEBUG = 4, // (the OoT debug tools of #785, a tab of their own) - TAB_COUNT = 5, - TAB_MOTION = TAB_COUNT, // (hookshot: not a tab of the strip, the Motion page that the Controls tab's Motion row - // opens: one past the strip's tabs, so a tab added before TAB_COUNT moves it along) - TAB_PAGE = TAB_MOTION + 1 // (hookshot: the Controls page's rows, above its grid; no tab shows them) -}; - -struct Row { - MenuTab tab; - const char* name; - int* value; - int lo, hi, step; - const char* (*text)(int v); - const char* help; // (shown under the list while the row is selected: what the row is, the same whatever its value) - // (a line about the value chosen, under the help, or nullptr: hint(v, -1) is the one to show now for v, hint(v, 0), - // hint(v, 1), ... every one v can have (those that depend on the other rows or on the GPU), until nullptr, so the - // menu keeps the height of the tallest: measure_help) - const char* (*hint)(int v, int variant) = nullptr; - bool restart = false; // (read once, at the start: a change applies after a restart) - bool preset_exempt = false; // (a change here leaves the Quality preset alone: the controls, the window, the counter, - // frame generation) - signed char cheat = -1; // (a debug tool, on the Debug tab with the tools on: its index in the cheat_ tables, -1 none) - bool heading = false; // (a section's title on its tab, not a row: the selection steps over it) -}; -/// A row's hints from a table, the value lo first (nullptr: none for that value) -const char* hint_in(const char* const* t, int n, int lo, int v, int variant) { - return variant <= 0 && v - lo >= 0 && v - lo < n ? t[v - lo] : nullptr; -} -#define MENU_HINTS(fn, lo, ...) \ - const char* fn(int v, int variant) { \ - static const char* const t[] = {__VA_ARGS__}; \ - return hint_in(t, (int)(sizeof t / sizeof t[0]), lo, v, variant); \ - } -const char* tx_framegen(int v) { - static const char* t[] = {"Off", "Auto", "2x", "3x", "4x", "5x", "6x", "7x", "8x"}; - return t[v]; -} -const char* tx_onoff(int v) { return v ? "On" : "Off"; } -const char* tx_profile(int v) { return v ? "Handheld" : "Docked"; } // (opts.handheld: the Profile row) -const char* tx_menu_shortcut(int v) { - static const char* const names[] = {"Insert or F1", "F1 only"}; // (F1 is never turned off: the menu is always one key away) - return names[v ? 1 : 0]; -} -const char* tx_hdr(int v) { return v == 1 ? "Expanded" : v == 2 ? "Native (the game's)" : "Off"; } -const char* tx_nits(int v) { - static char b[24]; - snprintf(b, sizeof b, "%d nits", v); - return b; -} -const char* tx_fps_cap(int v) { - static const char* const t[] = {"60 (the console's)", "90", "120", "144", "165", "240"}; - return t[v < 0 ? 0 : v > 5 ? 5 : v]; -} -// (hookshot) Motion controls: the choice, and whether a connected controller has a gyroscope (host/pad_sdl.c). Either can be -// chosen whatever is connected: a pad plugged in later is read when it comes -const char* tx_gyro(int v) { - const int status = host && host->gyro_status ? host->gyro_status() : -1; - if (status == 2) return v ? "On (gyro found)" : "Off (gyro found)"; - if (status == 1) return v ? "On (no gyro on this pad)" : "Off (no gyro on this pad)"; - if (status == 0) return v ? "On (no controller)" : "Off (no controller)"; - return v ? "On" : "Off"; -} -const char* tx_mousecam(int v) { return v ? "Native" : "As the stick"; } -const char* tx_sens(int v) { - static char b[12]; - snprintf(b, sizeof b, "%.2f", v / 100.0); - return b; -} -// (the game's picture and the window it is shown in, as the last present had them: menu_note_present_sizes) -std::atomic present_sizes{0}; -bool filter_needed() { - const uint64_t v = present_sizes.load(std::memory_order_relaxed); - return upscaler_rows::filter_needed((unsigned)(v >> 48), (unsigned)(v >> 32) & 0xffff, (unsigned)(v >> 16) & 0xffff, - (unsigned)v & 0xffff); -} -const char* tx_upscale(int v) { - static const char* t[] = {"Off", "Catmull-Rom", "Lanczos"}; - if ((unsigned)v >= sizeof t / sizeof t[0]) return t[0]; // (a setting past the row: the label, not the next bytes) - return t[v]; -} -/// The Window scaling filter row's value: its filter, or that none is needed (the saved value stays, for another window size) -const char* tx_filter(int v) { return filter_needed() ? tx_upscale(v) : "Not needed (window = output)"; } -const char* tx_reflex(int v) { - static const char* t[] = {"Off", "On", "On + Boost"}; - return t[reflex_available() ? std::clamp(v, 0, 2) : 0]; -} -/// Reflex: on the value (the row is shown only where Reflex runs: row_shown) -const char* hn_reflex(int v, int variant) { - static const char* const t[] = {"The game as without it.", "The driver paces the frames for less latency.", - "As On, and the GPU's clocks are kept up as well."}; - return variant <= 0 ? t[std::clamp(v, 0, 2)] : nullptr; -} -const char* tx_antilag(int v) { return antilag_available() && v ? "On" : "Off"; } -/// Anti-Lag 2: on the value (the row is shown only where Anti-Lag 2 runs: row_shown) -const char* hn_antilag(int v, int variant) { - static const char* const t[] = {"The game as without it.", "The driver delays each frame's start for less latency."}; - return variant <= 0 ? t[v ? 1 : 0] : nullptr; -} -// The Upscaler (opts.dlss): what scales the 3D up to the output. DLSS and DLAA only where dlss_why() lets them run, FSR 3.1 -// only where vk_fsr3.cpp says it does, FSR 4 and its native AA only where fsr4_why() does; the menu shows (and steps from) -// what runs, and keeps the saved choice for a PC where it can -namespace ur = upscaler_rows; -/// One bit per upscaler that runs here (upscaler_rows.h bit). Each backend caches its own answer, so this costs nothing -unsigned upscalers_available() { - return ur::bit(ur::GAME) | ur::dlss_bits(dlss_why() == DLSS_OK) | (fsr3::supported() ? ur::bit(ur::FSR3) : 0u) | - ur::fsr4_bits(fsr4_why() == FSR_OK); -} -/// (FSR 3.1 has inputs of its own; DLSS and FSR 4 run on the frame chain they share, temporal_mode) -int upscaler() { return fsr3_wanted() ? (int)ur::FSR3 : temporal_mode(); } -const char* tx_upscaler(int) { - static const char* t[ur::kUpscalers] = {"Game's own", "NVIDIA DLSS", "NVIDIA DLAA", "AMD FSR 3.1", "AMD FSR 4", - "AMD FSR 4 Native AA"}; - return t[std::clamp(upscaler(), 0, ur::kUpscalers - 1)]; -} -/// The Upscaler: the one shown (what runs, upscaler()), and with the game's own, why DLSS and FSR are not offered. Variants -/// (every one): each value, then the game's own with each of DLSS's reasons, then with each of FSR's -const char* hn_upscaler(int v, int variant) { - static const char* const t[ur::kUpscalers] = { - "The game's own network.", - "NVIDIA's, from a smaller 3D. Softer than the game's own: set Sharpness to around 80%. Things that move by " - "themselves can trail for now.", - "NVIDIA's, the 3D at full size with its edges smoothed; the slowest. Softer than the game's own: set Sharpness to " - "around 80%. Things that move by themselves can trail for now.", - "AMD's, from a smaller 3D, on any GPU: built from AMD's own shaders. Things that move by themselves can trail for " - "now.", - "AMD's machine-learned upscaler, from a smaller 3D, with AMD's files beside the game. Things that move by themselves " - "can trail for now.", - "AMD's machine-learned upscaler, the 3D at full size with its edges smoothed; the slowest.", - }; - static const char* const dlss_no[DLSS_WHY_COUNT] = { - nullptr, - "DLSS and DLAA: not in this build (built without NVIDIA's DLSS SDK, see the readme).", - "DLSS and DLAA: off for this run (RX_VK_DLSS=0).", - "DLSS and DLAA: need an NVIDIA RTX GPU.", - "DLSS and DLAA: need a newer NVIDIA driver.", - "DLSS and DLAA: DLSS's runtime was not found (the game folder's ngx folder, see the readme).", - "DLSS and DLAA: NVIDIA's NGX on Linux loads DLSS's runtime only from a folder named in ASCII (see the readme).", - }; - static const char* const fsr_no[FSR_WHY_COUNT] = { - nullptr, - "AMD FSR 4: not in this build (see the readme).", - "AMD FSR 4: off for this run (RX_VK_FSR4=0).", - "AMD FSR 4: AMD's files were not found beside the game (see the readme).", - "AMD FSR 4: AMD's files have no FSR 4 for this GPU.", - }; - // (now: the value shown and what runs here, with both engines' reasons; every one: as said above) - int d = 0, f = 0; - if (variant < 0) { - v = std::clamp(upscaler(), 0, ur::kUpscalers - 1), d = (int)dlss_why(), f = (int)fsr4_why(); - } else if (v == ur::GAME) { - if (variant < DLSS_WHY_COUNT) d = variant; - else if (variant < DLSS_WHY_COUNT + FSR_WHY_COUNT - 1) f = variant - DLSS_WHY_COUNT + 1; - else return nullptr; - } else if (variant > 0) { - return nullptr; - } - v = std::clamp(v, 0, ur::kUpscalers - 1); - // (the reasons are said only where the engine is in the build: one without FSR does not tell an NVIDIA user why) - const char* dn = dlss_built() || variant >= 0 ? dlss_no[d] : nullptr; - const char* fn = fsr4_built() || variant >= 0 ? fsr_no[f] : nullptr; - if (v != ur::GAME || (!dn && !fn)) return t[v]; - static char b[DLSS_WHY_COUNT][FSR_WHY_COUNT][320]; - snprintf(b[d][f], sizeof b[d][f], "%s%s%s%s%s", t[0], dn ? " " : "", dn ? dn : "", fn ? " " : "", fn ? fn : ""); - return b[d][f]; -} -/// The DLSS model row (opts.dlss_model): NVIDIA's network by its preset, or RX_VK_DLSS_PRESET's for this run -const char* dlss_preset_env() { - static const char* const e = getenv("RX_VK_DLSS_PRESET"); - return ur::preset_env(e) ? e : nullptr; -} -const char* tx_dlss_model(int v) { - static const char* const t[] = {"Transformer (K)", "Transformer 2 (M)", "Transformer 2 (L)", "NVIDIA default"}; - if (const char* e = dlss_preset_env()) { - static char b[48]; - const unsigned p = ur::dlss_preset(v, e); - if (p) snprintf(b, sizeof b, "Preset %c (RX_VK_DLSS_PRESET)", ur::preset_letter(p)); - else snprintf(b, sizeof b, "NVIDIA default (RX_VK_DLSS_PRESET)"); - return b; - } - return t[std::clamp(v, 0, ur::kModels - 1)]; -} -/// The DLSS model: the value chosen; variant 1: the game's own upscales (the row waits for DLSS or DLAA); variant 2: set for -/// this run by RX_VK_DLSS_PRESET -const char* hn_dlss_model(int v, int variant) { - static const char* const t[] = { - "NVIDIA's transformer model: the one tested most here.", - "NVIDIA's second-generation transformer, its pick for Performance: sharper, at a little more GPU time.", - "The second-generation transformer as NVIDIA tuned it for Ultra Performance, the smallest Render resolutions.", - "Lets DLSS's runtime pick for each mode: what it picks can change with a runtime update.", - }; - static const char* const unused = "Used by NVIDIA DLSS and DLAA: choose one in the Upscaler row."; - static const char* const env = "Set for this run by RX_VK_DLSS_PRESET."; - if (variant < 0) variant = dlss_preset_env() ? 2 : upscaler() == ur::GAME ? 1 : 0; - if (variant == 1) return unused; - if (variant == 2) return env; - return variant == 0 ? t[std::clamp(v, 0, ur::kModels - 1)] : nullptr; -} -const char* tx_percent(int v) { - static char b[8]; - snprintf(b, sizeof b, "%d%%", v); - return b; -} -// The output the menu's sizes are shown for: the Output resolution row's (applied at the next start), so the rows below follow a change -// at once; the Handheld profile runs the game's own (1536x864) -struct Out { - uint32_t w, h; -}; -// The Output resolution's choices: the sizes offered (0 the game's own), then, last, a size of the player's own from -// hookshot_options.ini (output_res=x, host/nn_applet.c hos_output_parse: the same rules) when it is none of them. -// The ini keeps the size, not the choice's number: output_res=3840x2160 -constexpr Out kOutputs[] = {{2048, 1152}, {2560, 1440}, {3840, 2160}, {1920, 1080}, {5120, 1440}, {5120, 2880}}; -constexpr int kCustomOutput = std::size(kOutputs), kLastOutput = kCustomOutput; -Out custom_output{0, 0}; // (the player's own size; 0 x 0: none) -int output_choice(int v) { return v >= 0 && (v < kCustomOutput || (v == kCustomOutput && custom_output.w)) ? v : 0; } -Out output_at(int v) { - v = output_choice(v); - return v == kCustomOutput ? custom_output : kOutputs[v]; -} -/// output_res= (or HOS_OUTPUT_RES) to a choice: "x" (each side a multiple of 8, rounded down; at least 640 x 360, -/// at most 5120 x 2880's pixels, 5:4 to 32:9), one of the sizes offered or the player's own (custom_output); the numbers saved -/// before (0 to 5) as theirs; anything else the game's own (host/nn_applet.c hos_output_parse: the same rules) -int parse_output(const char* text) { - unsigned w = 0, h = 0; - if (text && sscanf(text, "%u%*[xX]%u", &w, &h) == 2) { - w &= ~7u, h &= ~7u; - if (w < 640 || h < 360 || (uint64_t)w * h > 5120ULL * 2880 || (uint64_t)w * 4 < (uint64_t)h * 5 || (uint64_t)w * 9 > (uint64_t)h * 32) - return 0; - for (int i = 0; i < kCustomOutput; i++) - if (kOutputs[i].w == w && kOutputs[i].h == h) return i; - custom_output = {w, h}; - return kCustomOutput; - } - const int v = text ? atoi(text) : 0; - return v >= 0 && v < kCustomOutput ? v : 0; -} -// (the choices from smallest to largest, by width then height: 1920x1080, 2048x1152, 2560x1440, 3840x2160, 5120x1440, -// 5120x2880, and the player's own in its place) -int step_output_choice(int id, int dir) { - int order[kLastOutput + 1], n = 0; - for (int i = 0; i <= kLastOutput; i++) - if (output_choice(i) == i) order[n++] = i; - std::sort(order, order + n, [](int a, int b) { - const Out x = output_at(a), y = output_at(b); - return x.w != y.w ? x.w < y.w : x.h < y.h; - }); - const int at = (int)(std::find(order, order + n, output_choice(id)) - order); - return order[std::clamp(at + dir, 0, n - 1)]; -} -Out shown_output() { - return opts.handheld ? Out{1536, 864} : output_at(opts.output_res); -} -// The size the game draws the 3D at for a fixed scale of its output, a twelfth of it: every twelfth asked came out exact at the three -// outputs (2048x1152: 0.58 drew 1195x672, 0.6667 1366x768). So the scale the game is given is the twelfth too: 0.67 drew 2615x1471 at -// 3840x2160, not the 2560x1440 of 8/12. -int twelfths(float scale) { return std::min(std::max((int)(scale * 12.0f + 0.5f), 1), 12); } -void game_size(float scale, uint32_t& w, uint32_t& h) { - const Out o = shown_output(); - const int k = twelfths(scale); - w = (o.w * k + 11) / 12, h = o.h * k / 12; -} -const char* tx_resolution(int v) { - static char b[48]; - const int up = upscaler(); - if (up == ur::DLAA) return "Native (DLAA)"; - if (up == ur::FSR4_AA) return "Native (FSR 4)"; - if (!ur::percent(v)) return "Auto (dynamic)"; - if (const char* mode = ur::scales_by_mode(up) ? ur::dlss_mode_name(v) : nullptr) { - snprintf(b, sizeof b, "%s (%d%%)", mode, ur::percent(v)); - return b; - } - uint32_t w, h; - game_size(ur::percent(v) / 100.0f, w, h); - snprintf(b, sizeof b, "%d%% (%ux%u)", ur::percent(v), w, h); - return b; -} -/// The Render resolution: the value chosen, for the Upscaler shown (every one: each value with each Upscaler) -const char* hn_resolution(int v, int variant) { - const int up = variant < 0 ? std::clamp(upscaler(), 0, ur::kUpscalers - 1) : variant; - if (up >= ur::kUpscalers) return nullptr; - if (up == ur::DLAA) - return "DLAA draws the 3D at full size: nothing to set here. Choose DLSS or the game's own upscaler to draw it " - "smaller."; - if (up == ur::FSR4_AA) - return "FSR 4's native AA draws the 3D at full size: nothing to set here. Choose FSR 4 or the game's own upscaler " - "to draw it smaller."; - const int p = ur::percent(v); - if (up == ur::DLSS) { - switch (p) { - case 0: return "The game lowers it when the GPU is busy, and DLSS follows. Set Sharpness to around 80%."; - case 67: return "DLSS's sharpest mode. Set Sharpness to around 80%."; - case 58: return "Between Quality and Performance. Set Sharpness to around 80%."; - case 50: return "Faster, and softer. Set Sharpness to around 80%."; - case 33: return "DLSS's fastest mode, and its softest. Set Sharpness to around 80%."; - default: return nullptr; // (not one of DLSS's steps: the row moves to one) - } - } - if (up == ur::FSR3 || up == ur::FSR4) { // (the same modes, AMD's ratios) - switch (p) { - case 0: return "The game lowers it when the GPU is busy, and FSR follows."; - case 67: return "FSR's Quality mode, 1.5x: its sharpest."; - case 58: return "FSR's Balanced mode, 1.7x."; - case 50: return "FSR's Performance mode, 2x: faster, and softer."; - case 33: return "FSR's Ultra Performance mode, 3x: its fastest, and its softest."; - default: return nullptr; // (not one of FSR's steps: the row moves to one) - } - } - if (!p) return "The game lowers it when the GPU is busy."; - if (p == 100) return "The 3D at the output's full size: the sharpest, and the slowest."; - return "A steady size: lower is faster, and softer."; -} -/// A row with nothing else to choose here: shown without its arrows, Left and Right leave it (and the saved value) alone -bool row_fixed(const int* value) { - return (value == &opts.dlss && ur::upscaler_fixed(upscalers_available())) || - (value == &opts.resolution && ur::resolution_fixed(upscaler())) || (value == &opts.upscale && !filter_needed()) || - (value == &opts.dlss_model && ur::model_fixed(upscaler(), dlss_preset_env() != nullptr)); -} -const char* tx_output_res(int v) { - static char b[32]; - v = output_choice(v); - const Out o = output_at(v); - snprintf(b, sizeof b, "%ux%u%s", o.w, o.h, v ? "" : " (game's)"); - return b; -} -/// Anisotropic filtering: the game's own, or 2x to 16x (vk_draw.cpp aniso_override) -const char* tx_anisotropy(int v) { - static const char* t[] = {"Game's", "2x", "4x", "8x", "16x"}; - return (unsigned)v < 5 ? t[v] : t[4]; -} -const char* tx_speed(int v) { return v ? "Real time" : "Per frame"; } -const char* tx_text_speed(int v) { - static const char* t[] = {"Normal", "2x", "4x", "Instant"}; - if ((unsigned)v >= sizeof t / sizeof t[0]) return t[0]; - return t[v]; -} -const char* tx_text_size(int v) { - static const char* t[] = {"Original", "110%", "125%", "150%", "175%"}; - if ((unsigned)v >= sizeof t / sizeof t[0]) return t[0]; - return t[v]; -} -const char* tx_ultrawide(int v) { return v ? "Monitor's shape" : "16:9"; } -const char* tx_prompts(int v) { - static const char* t[HOS_PROMPT_N + 1] = {"Auto", "Switch", "Xbox", "PlayStation 4", "PlayStation 5", "Steam Deck"}; - return t[std::min(std::max(v, 0), (int)HOS_PROMPT_N)]; -} -const char* tx_gpu(int v) { - static char buf[300]; - const int idx = v - 1; - if (v <= 0) snprintf(buf, sizeof buf, "Auto"); - else if (idx < (int)gpu_device_names.size()) snprintf(buf, sizeof buf, "%d: %s", idx, gpu_device_names[idx].c_str()); - else snprintf(buf, sizeof buf, "%d: Device %d", idx, idx); - // (the whole name shows: the selected row slides through it, see marquee_pos) - return buf; -} -const char* tx_pad_layout(int v); -// (hookshot) the Microphone row: Off, the system's default, or one of the host's microphones (host/nn_audio_in.c: the -// ocarina's input streams), told to the host by its name (host->mic_select) whenever it changes. The chosen one is kept by -// its name, mic_want, also while it is unplugged: the row then has one more entry for it, " (not connected)", the -// host lets the default stand in and opens it again once it is back, and the name stays in the ini. In the ini the name is -// cut to kMicNameKept bytes (at a character's start), so the line fits the 80-byte readers of the file (host/nn_hid.c, -// nn_applet.c, hos_main.c), and a name is matched by that much of it -constexpr int kMaxMics = 16; -constexpr size_t kMicNameKept = 60; -std::string mic_want; // (the chosen microphone's name, cut to kMicNameKept; empty: Off or the default chosen) -bool mic_missing = false; // (mic_want is not plugged in: the row's last entry, 2 + mic_listed, stands for it) -int mic_listed = 0; // (the microphones as the host last listed them) -bool can_mic() { return host && host->mic_count && host->mic_name && host->mic_select; } -size_t text_char_start(const std::string& text, size_t at) { - while (at > 0 && at < text.size() && ((unsigned char)text[at] & 0xc0) == 0x80) at--; - return at; -} -std::string mic_key(const char* name) { - std::string k(name ? name : ""); - for (char& c : k) // (a device's name goes into hookshot_options.ini: a line break in it would add lines there) - if ((unsigned char)c < 0x20 || c == 0x7f) c = ' '; - if (k.size() <= kMicNameKept) return k; - return k.substr(0, text_char_start(k, kMicNameKept)); -} -/// buf cut back to whole UTF-8 characters (snprintf may have cut one) -void utf8_whole(char* buf) { - size_t n = strlen(buf), i = n; - while (i > 0 && ((unsigned char)buf[i - 1] & 0xC0) == 0x80) i--; // (the last character's continuation bytes) - if (i == 0) return; - const unsigned char lead = (unsigned char)buf[i - 1]; - const size_t want = lead >= 0xF0 ? 4 : lead >= 0xE0 ? 3 : lead >= 0xC0 ? 2 : 1; - if (n - (i - 1) < want) buf[i - 1] = 0; -} -const char* tx_mic(int v) { - static char buf[160]; - if (v <= 0) return "Off"; - if (v == 1) return "Default"; - if (mic_missing && v == 2 + mic_listed) { - const int room = (int)sizeof buf - (int)sizeof " (not connected)"; - snprintf(buf, (size_t)room + 1, "%s", mic_want.c_str()); - utf8_whole(buf); - strcat(buf, " (not connected)"); - return buf; - } - const char* n = can_mic() ? host->mic_name(v - 2) : ""; - snprintf(buf, sizeof buf, "%s", n && *n ? n : "Microphone"); - utf8_whole(buf); - return buf; -} -/// The Microphone row: Off, the default, a microphone of the list, or the chosen one while it is unplugged (variant 1) -const char* hn_mic(int v, int variant) { - if (v <= 0) return variant <= 0 ? "The game hears silence." : nullptr; - if (v == 1) - return variant <= 0 ? "The system's default recording device. On Windows, desktop apps must be allowed to use the " - "microphone (Settings, Privacy & security)." - : nullptr; - const bool missing = variant < 0 ? mic_missing && v == 2 + mic_listed : variant == 1; - if (variant > 1) return nullptr; - return missing ? "Not plugged in: Default stands in until it is back." : "This microphone, by its name. A change applies at once."; -} -const char* tx_rumble(int v) { return v ? tx_percent(v) : "Off"; } -// (hookshot) Menu size: how large the overlays are drawn (vk_ui.h unit); Auto is Large in fullscreen at 1080 lines and up while -// a controller is the menu's input, else Medium -bool pad_last = false; // (a controller was the menu's last input: its buttons in the prompts) -// (Auto's choice: the controller or not, decided as the menu opens by the input that opened it. A controller's input makes it -// Large at any time; the keys and the mouse never make it smaller while the menu is open, so it never changes size under the -// cursor) -bool auto_pad = false; -int pad_confirm = 0, pad_back = 1; // (the face buttons that confirm and go back, HOS_PAD_*: menu_keys::confirm_back, each tick) -bool auto_large(uint32_t h) { return opts.fullscreen && h >= 1080 && auto_pad; } -const char* tx_menu_size(int v) { - static const char* t[] = {"Auto", "Small", "Medium", "Large"}; - if (v == 0) return auto_large(dev.swap_extent.height) ? "Auto (Large)" : "Auto (Medium)"; - return (unsigned)v < 4 ? t[v] : t[0]; -} -const char* tx_stick_dead_zone(int v) { - static const char* t[] = {"Default (24%)", "5%", "10%", "15%", "20%", "30%"}; - return (unsigned)v < 6 ? t[v] : t[0]; -} -// (hookshot) Audio output: the speakers the game mixes for, read once at its start (host/nn_audio.c au_menu_output) -const char* tx_audio_output(int v) { - static const char* t[] = {"Auto", "Stereo", "5.1"}; - return (unsigned)v < 3 ? t[v] : t[0]; -} -// (hookshot) the motion's deadzone: the console's three zero-drift modes, the rate under which the pad counts as still -const char* tx_deadzone(int v) { - static const char* t[] = {"Low", "Standard", "High"}; - return t[v < 0 ? 0 : v > 2 ? 2 : v]; -} -const char* tx_robust(int v) { - static const char* t[] = {"Auto", "Off", "On"}; - return (unsigned)v < 3 ? t[v] : t[0]; -} -const char* tx_preset(int v) { - static const char* t[] = {"Custom", "Minimum (4GB)", "Low (6GB)", "Balanced (8GB)", "High (12GB)", "Ultra (16GB+)"}; - return t[std::min(std::max(v, 0), 5)]; -} -void sync_preset_budgets(int p) { - switch (p) { - case 1: opts.vram_mb = 2200; opts.mirror_mb = 256; break; - case 2: opts.vram_mb = 3600; opts.mirror_mb = 512; break; - case 3: opts.vram_mb = 4800; opts.mirror_mb = 1024; break; - case 4: opts.vram_mb = 7500; opts.mirror_mb = 2048; break; - case 5: opts.vram_mb = 11000; opts.mirror_mb = 3072; break; - default: opts.vram_mb = 0; opts.mirror_mb = 0; break; - } -} -// (Sharpness: tuned for the window pass's neighbours one picture pixel apart, each about the old setting's sharpening on a -// 3840 x 2160 window at that preset's picture, 2048 x 1152 or the handheld profile's 1536 x 864: same frame, mean |Laplacian| -// of the window and of the picture, on the slider's steps) -void apply_preset(int p) { - switch (p) { - case 1: // Minimum (4GB VRAM / iGPU) - opts.resolution = 4; opts.upscale = 2; opts.sharpness = 30; - opts.pace = 1; opts.realtime = 1; opts.handheld = 1; - break; - case 2: // Low (6GB VRAM) - opts.resolution = 2; opts.upscale = 2; opts.sharpness = 30; - opts.pace = 1; opts.realtime = 1; opts.handheld = 1; - break; - case 3: // Balanced (8GB VRAM) - opts.resolution = 1; opts.upscale = 1; opts.sharpness = 30; - opts.pace = 1; opts.realtime = 1; opts.handheld = 0; - break; - case 4: // High (12GB VRAM) - opts.resolution = 1; opts.upscale = 2; opts.sharpness = 40; - opts.pace = 1; opts.realtime = 1; opts.handheld = 0; - break; - case 5: // Ultra (16GB+ VRAM) - opts.resolution = 1; opts.upscale = 2; opts.sharpness = 50; - opts.pace = 1; opts.realtime = 1; opts.handheld = 0; - break; - default: - break; - } - // (the Upscaler and Frame generation are the player's: a preset never turns DLSS or made frames on or off. With DLSS - // its resolution is DLSS's nearest mode) - if (p > 0) opts.resolution = ur::snap(upscaler(), opts.resolution); - sync_preset_budgets(p); -} - -MENU_HINTS(hn_preset, 0, "Each row as you set it. Changing most rows yourself comes back to Custom.", - "Resolution 50% (with DLSS its nearest mode), Lanczos, Sharpness 30%, the Handheld Profile.", - "Resolution 75% (with DLSS its nearest mode), Lanczos, Sharpness 30%, the Handheld Profile.", - "Resolution 100% (with DLSS its nearest mode), Catmull-Rom, Sharpness 30%.", - "Resolution 100% (with DLSS its nearest mode), Lanczos, Sharpness 40%.", - "Resolution 100% (with DLSS its nearest mode), Lanczos, Sharpness 50%.") -MENU_HINTS(hn_gpu, 0, "The best dedicated GPU.") -/// Frame generation: how many frames are made for each of the game's -const char* hn_framegen(int v, int variant) { - // (2x: by AMD FSR 3.1 where it runs, from the scene's depth and motion: vk_framegen.cpp. Variant 1 is that text) - if (v == 2 && (variant < 0 ? fsr3fi::frames_wanted() != 0 : variant == 1)) - return "One made frame between each two of the game's, by AMD FSR 3.1 from the scene's depth and motion."; - if (variant > 0) return nullptr; - if (v <= 0) return "Only the game's own frames: no added delay."; - if (v == 1) return "A frame for each of the display's refreshes: none made when the game keeps up. Frame pacing does not apply."; - static char b[9][96]; - snprintf(b[v], sizeof b[v], "%s between each two of the game's.", v == 2 ? "One made frame" : v == 3 ? "Two made frames" : v == 4 ? "Three made frames" - : v == 5 ? "Four made frames" : v == 6 ? "Five made frames" : v == 7 ? "Six made frames" : "Seven made frames"); - return b[std::clamp(v, 2, 8)]; -} -MENU_HINTS(hn_pace, 0, "As soon as it is ready.", "At an even interval: less stutter, at up to one frame of delay.") -MENU_HINTS(hn_output_res, 0, "The game's own size.", - "The sharpest on a 2560x1440 screen: nothing to scale to the window.", - "The sharpest on a 3840x2160 screen. The game's render targets get a bigger GPU pool, so a full-size 3D fits.", - "The sharpest on a 1920x1080 screen: the HUD and text pixel for pixel, nothing to scale to the window.", - "The sharpest on a 5120x1440 ultrawide screen: the whole frame pixel for pixel. The game's render targets get a bigger GPU pool, so a full-size 3D fits.", - "The sharpest on a 5120x2880 (5K) screen: the whole frame pixel for pixel. The game's render targets get a bigger GPU pool, so a full-size 3D fits.", - "Your own size, from output_res= in hookshot_options.ini.") -/// The Window scaling filter: its filter, or that none is needed at this window's size -const char* hn_filter(int v, int variant) { - static const char* const t[] = {"The plain copy.", "Sharper.", "Sharper still; can ring on edges."}; - static const char* const none = "The window is the Output resolution's size: nothing to scale. The choice is kept for " - "another size."; - if (variant < 0) return filter_needed() ? t[std::clamp(v, 0, 2)] : none; - return variant == 0 ? t[std::clamp(v, 0, 2)] : variant == 1 ? none : nullptr; -} -/// Sharpness: with DLSS or DLAA, the amount that suits them -const char* hn_sharpness(int v, int variant) { - static const char* const dlss = "DLSS and DLAA look softer than the game's own upscaler: around 80% suits them."; - const bool with_dlss = variant < 0 ? upscaler() == ur::DLSS || upscaler() == ur::DLAA : variant == 1; - if (variant > 1) return nullptr; - return with_dlss ? dlss : v == 0 ? "Off: the picture as it is." : nullptr; -} -MENU_HINTS(hn_handheld, 0, "As on a TV: the game at the Output resolution's size.", - "As in the hands: the game at the console's handheld size (1536x864), lighter on the GPU.") -MENU_HINTS(hn_ultrawide, 0, "The game's own shape.", "The camera takes the screen's shape, wider or narrower. The HUD keeps its 16:9 shape in the middle.") -const char* tx_wide_menus(int v) { - static const char* t[] = {"Full width", "Centred, blurred sides", "Centred, black bars"}; - return t[std::min(std::max(v, 0), 2)]; -} -MENU_HINTS(hn_wide_menus, 0, "The menus stretched over the whole screen, as before.", - "The menus in the middle 16:9; beside them, what the game draws there, out of focus.", - "The menus in the middle 16:9; black beside them.") -MENU_HINTS(hn_robust, 0, "On, on every card.", "Can be faster on some NVIDIA cards.", "Always on.") -MENU_HINTS(hn_fps, 0, nullptr, "With frame generation on, the game's own rate too.") -MENU_HINTS(hn_menu_size, 0, "Large in fullscreen on a screen of 1080 lines or more while a controller is in use, else Medium.", - "Smaller, for a desk.", "The usual size.", "Larger, for a TV across the room.") -MENU_HINTS(hn_speed, 0, "A step each frame: the game slows down whenever it runs below 60 fps.", - "Normal speed down to 15 fps, by taking longer steps.") -MENU_HINTS(hn_text_speed, 0, "The game's own pace, with its pauses.", "Twice as fast.", "Four times as fast.", - "Each page at once, as pressing A does.") -MENU_HINTS(hn_audio_output, 0, "5.1 when the default output device has 5.1 or more speakers, else stereo.", - "Stereo, whatever the device has.", "5.1, whatever the device has. Mixed down on stereo speakers.") -MENU_HINTS(hn_text_size, 0, "The game's original size.", "A little larger.", "Larger.", "Much larger.", "Largest.") -MENU_HINTS(hn_background, 0, "Only while the game's window has the focus.", - "Also behind other windows, for streaming or other apps. The keyboard and mouse still need the game's window.") -MENU_HINTS(hn_mousecam, 0, "The mouse stands in for the right stick, with its curve and top speed.", - "The mouse turns the camera itself, one to one, as in a PC game.") -MENU_HINTS(hn_mouse_invert, 0, nullptr, "Moving the mouse forward tilts the camera down.") -MENU_HINTS(hn_gyro, 0, "The sticks alone aim.", - "Switch Pro controllers and pads in Switch mode, DualSense and DualShock 4. The value says whether the " - "connected pad has a gyroscope.") -MENU_HINTS(hn_pad_layout, 0, "The Switch's order: A on the right, B at the bottom, X at the top, Y on the left.", - "A at the bottom (action and talk), B on the right, X on the left, Y at the top.", - "A at the bottom, B on the right; X and Y as on the Switch.", - "Bindings of your own, from the Controls page.") -MENU_HINTS(hn_prompts, 0, - "Those of the first controller, from its name (SDL's). The Switch's when it has none, as with XInput alone.") - -Row rows[] = { - // Graphics (Reflex, Anti-Lag 2, the Upscaler and the DLSS model only where they run here; HDR's paper white and peak - // brightness only with HDR on: row_shown) - {TAB_GRAPHICS, "Quality preset", &opts.preset, 0, 5, 1, tx_preset, - "Sets the scaling rows and memory limits for a GPU with that much memory. The Upscaler and Frame generation stay yours.", - hn_preset}, - {TAB_GRAPHICS, "Fullscreen", &opts.fullscreen, 0, 1, 1, tx_onoff, - "Fills the whole screen with the game, without the window's border. The same as F11.", nullptr, false, true}, - {TAB_GRAPHICS, "GPU device", &opts.gpu_device, 0, 8, 1, tx_gpu, - "Which graphics card the game runs on. Applies after a restart.", hn_gpu, true, true}, - {TAB_GRAPHICS, "Frame generation", &opts.framegen, 0, 8, 1, tx_framegen, - "Makes frames between the game's own, for smoother motion, at a little input delay. Fast motion or camera cuts can " - "show artifacts.", hn_framegen, false, true}, - {TAB_GRAPHICS, "Frame pacing", &opts.pace, 0, 1, 1, tx_onoff, "When each frame is shown.", hn_pace}, - {TAB_GRAPHICS, "HDR", &opts.hdr, 0, 2, 1, tx_hdr, - "HDR10 out on an HDR display (Windows: Use HDR on in the display settings). Expanded: the game's SDR picture, its white at " - "the paper white and its highlights up to the peak. Native: the game's own HDR, told the display's peak and paper white.", - nullptr, false, true}, - {TAB_GRAPHICS, "HDR paper white", &opts.hdr_white, 80, 400, 10, tx_nits, - "How bright the picture's white is with HDR on. Around 200 for a normal room; higher in a bright one.", nullptr, false, true}, - {TAB_GRAPHICS, "HDR peak brightness", &opts.hdr_peak, 400, 4000, 100, tx_nits, - "The brightest highlights with HDR on: set it to your display's peak brightness.", nullptr, false, true}, - {TAB_GRAPHICS, "Game frame rate cap", &opts.fps_cap, 0, 5, 1, tx_fps_cap, - "Limits frames rendered by the game. Generated frames are additional. Takes a restart. " - "Past 60 is experimental: the game keeps real time, but some cutscenes and bosses can misbehave (Gohma, the intro's " - "narration). 60 is the safe choice.", - nullptr, true, true}, - {TAB_GRAPHICS, "NVIDIA Reflex", &opts.reflex, 0, 2, 1, tx_reflex, - "NVIDIA GPUs: each frame starts as late as it can, for less input delay. Not used with frame generation.", hn_reflex}, - {TAB_GRAPHICS, "AMD Anti-Lag 2", &opts.antilag, 0, 1, 1, tx_antilag, - "AMD GPUs: each frame starts as late as it can, for less input delay. Not used with frame generation.", hn_antilag}, - // (the picture's way, top to bottom: the output's size, what fills it from the 3D, the 3D's size, then the window) - {TAB_GRAPHICS, "Output resolution", &opts.output_res, 0, kLastOutput, 1, tx_output_res, - "The game's whole frame, HUD included, before it is scaled to the window. Docked only. Applies after a restart.", - hn_output_res, true}, - {TAB_GRAPHICS, "Upscaler", &opts.dlss, 0, ur::kUpscalers - 1, 1, tx_upscaler, "What scales the 3D up to the Output resolution.", - hn_upscaler}, - {TAB_GRAPHICS, "DLSS model", &opts.dlss_model, 0, ur::kModels - 1, 1, tx_dlss_model, - "Which of NVIDIA's DLSS networks draws the picture.", hn_dlss_model, false, true}, - {TAB_GRAPHICS, "Render resolution", &opts.resolution, 0, ur::kValues - 1, 1, tx_resolution, - "The share of the output the 3D is drawn at; the Upscaler fills in the rest.", hn_resolution}, - {TAB_GRAPHICS, "Window scaling filter", &opts.upscale, 0, 2, 1, tx_filter, - "How the finished picture is scaled to the window, when its size differs from the Output resolution.", hn_filter}, - {TAB_GRAPHICS, "Sharpness", &opts.sharpness, 0, 100, 10, tx_percent, - "Sharpens the picture as it goes to the window, with any filter. Too much shows halos around edges.", hn_sharpness}, - {TAB_GRAPHICS, "Anisotropic filtering", &opts.anisotropy, 0, 4, 1, tx_anisotropy, - "Keeps textures sharp on surfaces seen at a slant, such as the ground ahead: higher is sharper in the distance, for a " - "little GPU time. Game's is the console's own (1x or 2x).", nullptr}, - {TAB_GRAPHICS, "Profile", &opts.handheld, 0, 1, 1, tx_profile, - "Runs the game as the console docked or handheld. Applies after a restart.", hn_handheld, true}, - {TAB_GRAPHICS, "Ultrawide", &opts.ultrawide, 0, 1, 1, tx_ultrawide, - "The camera's shape on a screen wider or narrower than 16:9 (4:3, 16:10). Applies after a restart.", hn_ultrawide, true}, - {TAB_GRAPHICS, "Ultrawide menus", &opts.wide_menus, 0, 2, 1, tx_wide_menus, - "With Ultrawide: the pause, item and map menus, and what shows beside them. Loading screens and pre-rendered movies, " - "which draw no world either, get the same sides. Changes at once.", hn_wide_menus}, - {TAB_GRAPHICS, "Robust buffers", &opts.robust, 0, 2, 1, tx_robust, - "Bounds-checked buffer reads: a read past a buffer's end is dropped instead of stopping the GPU. Applies after a " - "restart.", hn_robust, true}, - - // Audio - {TAB_AUDIO, "Volume", &opts.volume, 0, 100, 5, tx_percent, - "How loud the game plays. The game's own sound settings and Windows' volume still apply.", nullptr, false, true}, - {TAB_AUDIO, "Mute in background", &opts.mute_background, 0, 1, 1, tx_onoff, - "Silences the game while another window has the focus. The sound comes back when you switch to the game.", nullptr, - false, true}, - {TAB_AUDIO, "Audio output", &opts.audio_output, 0, 2, 1, tx_audio_output, - "The speakers the game mixes its sound for. Applies after a restart.", hn_audio_output, true, true}, - - // Gameplay - {TAB_GAMEPLAY, "FPS counter", &opts.show_fps, 0, 1, 1, tx_onoff, "Frames per second in the top right corner.", hn_fps, - false, true}, - {TAB_GAMEPLAY, "Menu size", &opts.menu_size, 0, 3, 1, tx_menu_size, - "How large this menu, the FPS counter and the start-up screen are drawn.", hn_menu_size, false, true}, - {TAB_GAMEPLAY, "Game speed", &opts.realtime, 0, 1, 1, tx_speed, "How the game keeps time when it runs below 60 fps.", - hn_speed}, - {TAB_GAMEPLAY, "Text speed", &opts.text_speed, 0, 3, 1, tx_text_speed, - "How fast the letters of dialogue and other messages appear.", hn_text_speed}, - {TAB_GAMEPLAY, "Text size", &opts.text_size, 0, 4, 1, tx_text_size, - "How large the text of dialogue, Navi and signs is drawn.", hn_text_size}, - {TAB_GAMEPLAY, "Microphone", &opts.mic, 0, 2 + kMaxMics, 1, tx_mic, - "What the ocarina hears when the game's microphone option is on (playing an instrument, or humming).", hn_mic, false, - true}, - {TAB_GAMEPLAY, "Discord status", &opts.discord, 0, 1, 1, tx_onoff, - "Shows \"Playing Hookshot\" and the time played in your Discord status, while Discord runs on this PC.", nullptr, false, - true}, - - // Controls (12 rows; Motion opens the 6 TAB_MOTION rows, Controls its page with the 2 TAB_PAGE rows) - {TAB_CONTROLS, "Controller in background", &opts.pad_background, 0, 1, 1, tx_onoff, - "Whether the controller plays the game while another window has the focus.", hn_background}, - {TAB_CONTROLS, "Mouse camera", &opts.mouse_native, 0, 1, 1, tx_mousecam, "How the captured mouse turns the camera.", - hn_mousecam, false, true}, - {TAB_CONTROLS, "Disable camera centering", &opts.disable_camera_centering, 0, 1, 1, tx_onoff, - "Stops the camera gradually turning toward the direction you move, useful with native mouse look. Off keeps the " - "game's standard behavior. Manual camera controls and recentering still work.", nullptr, false, true}, - {TAB_CONTROLS, "Mouse sensitivity", &opts.mouse_sens, 10, 500, 10, tx_sens, - "How far the camera turns for a mouse movement, with either Mouse camera: at 1.00 native look turns 0.1 degree a " - "count. The - and = keys change it in game too.", nullptr, false, true}, - {TAB_CONTROLS, "Invert mouse vertical", &opts.mouse_invert, 0, 1, 1, tx_onoff, "Swaps up and down for the mouse.", - hn_mouse_invert, false, true}, - {TAB_CONTROLS, "Invert camera X", &opts.invert_cam_x, 0, 1, 1, tx_onoff, - "Inverts horizontal look (left/right) on the right analog stick.", nullptr, false, true}, - {TAB_CONTROLS, "Invert camera Y", &opts.invert_cam_y, 0, 1, 1, tx_onoff, - "Inverts vertical look (up/down) on the right analog stick.", nullptr, false, true}, - {TAB_CONTROLS, "Stick dead zone", &opts.stick_dead_zone, 0, 5, 1, tx_stick_dead_zone, - "How far a stick moves before the game sees it, on each axis. Lower for a precise stick, higher for one that drifts. " - "The game's own dead zone still follows.", nullptr, false, true}, - {TAB_CONTROLS, "Rumble strength", &opts.rumble, 0, 150, 25, tx_rumble, - "How strongly the controller rumbles, in percent of the game's rumble. Off turns it off.", nullptr, false, true}, - {TAB_MOTION, "Motion controls", &opts.gyro, 0, 1, 1, tx_gyro, - "Aims and looks with the controller's gyroscope where the game uses motion, as on the Switch. Works with Switch Pro " - "controllers and pads in Switch mode, DualSense and DualShock 4. Shown beside it: whether the connected pad has one.", hn_gyro, - false, true}, - {TAB_MOTION, "Motion sensitivity X", &opts.gyro_sens_x, 10, 600, 10, tx_percent, - "How far the aim moves left and right for a given turn of the controller. 100% is the console's own.", nullptr, false, - true}, - {TAB_MOTION, "Motion sensitivity Y", &opts.gyro_sens_y, 10, 300, 10, tx_percent, - "How far the aim moves up and down for a given tilt of the controller. 100% is the console's own.", nullptr, false, - true}, - {TAB_MOTION, "Invert motion X", &opts.gyro_inv_x, 0, 1, 1, tx_onoff, "Turns the motion aim's left and right around.", - nullptr, false, true}, - {TAB_MOTION, "Invert motion Y", &opts.gyro_inv_y, 0, 1, 1, tx_onoff, "Turns the motion aim's up and down around.", - nullptr, false, true}, - {TAB_MOTION, "Motion deadzone", &opts.gyro_deadzone, 0, 2, 1, tx_deadzone, - "How slow a turn counts as none, the console's three settings: Standard is its own. High steadies a pad that jitters " - "when held still; Low keeps the smallest movements.", nullptr, false, true}, - {TAB_CONTROLS, "Motion", nullptr, 0, 0, 0, nullptr, // (hookshot: opens the Motion page, the TAB_MOTION rows) - "Motion controls: aiming with the controller's gyroscope, its sensitivity and its inverts."}, - // (hookshot) the Controls page's own rows, above its grid (TAB_PAGE: no tab shows them) - {TAB_PAGE, "Layout preset", &opts.pad_layout, 0, 3, 1, tx_pad_layout, - "Which face button is which. Binding one yourself on the Controls page makes it Custom.", hn_pad_layout}, - {TAB_PAGE, "Button prompts", &opts.prompts, 0, HOS_PROMPT_N, 1, tx_prompts, - "The button icons the game shows, in its texts and menus: those of this controller, for the buttons your controls " - "put each action on.", hn_prompts, true, true}, - {TAB_CONTROLS, "Controls", nullptr, 0, 0, 0, nullptr, // (hookshot: opens its page) - "The layout preset, the button prompts, and which gamepad button and which keys press each of the Switch's " - "buttons."}, - {TAB_CONTROLS, "Menu shortcut", &opts.menu_shortcut, 0, 1, 1, tx_menu_shortcut, - "Which keys open and close this menu. F1 always does; F1 only leaves Insert free for an overlay.", - nullptr, false, true}, - // Debug: the tools' switch, the tools themselves below it while they are on (cheat: menu_tick does what they do), then the - // developer's rows - {TAB_DEBUG, "Debug tools", &opts.debug_tools, 0, 1, 1, tx_onoff, - "Testing tools: no-clip flight, a free camera, teleport, Link's age, the inventory, the clock and heart protection. Off " - "puts flight, the free camera and heart protection away.", nullptr, false, true}, - {TAB_DEBUG, "Heart protection", nullptr, 0, 0, 0, nullptr, - "Protects Link's hearts. Hits can still knock him back.", nullptr, false, true, 0}, - {TAB_DEBUG, "No-clip flight", nullptr, 0, 0, 0, nullptr, - "Enable double-tap Jump for takeoff and landing. Hold Jump to climb or L to descend.", nullptr, false, true, 1}, - {TAB_DEBUG, "Mark position", nullptr, 0, 0, 0, nullptr, - "Remember Link's position for this loaded scene.", nullptr, false, true, 2}, - {TAB_DEBUG, "Return to mark", nullptr, 0, 0, 0, nullptr, - "Teleport Link to the marked position in the current scene.", nullptr, false, true, 3}, - {TAB_DEBUG, "Set time", nullptr, 0, 0, 0, nullptr, - "Choose an hour with Left or Right. Enter sets the world clock.", nullptr, false, true, 4}, - {TAB_DEBUG, "Inventory item", nullptr, 0, 0, 0, nullptr, - "Choose a consumable with Left or Right.", nullptr, false, true, 5}, - {TAB_DEBUG, "Change quantity", nullptr, 0, 0, 0, nullptr, - "Choose how many to add or remove. Enter applies the change.", nullptr, false, true, 6}, - {TAB_DEBUG, "Free camera", nullptr, 0, 0, 0, nullptr, - "Move and look with your usual controls. Page Up / Page Down or R / L changes height.", nullptr, false, true, 7}, - {TAB_DEBUG, "Teleport", nullptr, 0, 0, 0, nullptr, - "Opens the map list. Link travels through the game's own map change, in his current age.", nullptr, false, true, 8}, - {TAB_DEBUG, "Switch age", nullptr, 0, 0, 0, nullptr, - "Swaps Link between child and adult and reloads this map in the other age (the Market when this map has no match).", nullptr, false, true, 9}, - {TAB_DEBUG, "Inventory", nullptr, 0, 0, 0, nullptr, - "Enter opens the inventory editor: unlock or relock equipment, items, upgrades and quest items, and change amounts.", nullptr, false, true, 10}, - {TAB_DEBUG, "Developer", nullptr, 0, 0, 0, nullptr, nullptr, nullptr, false, true, -1, true}, - {TAB_DEBUG, "Descriptor reuse", &opts.descriptor_reuse, 0, 1, 1, tx_onoff, - "A draw keeps its descriptors while the texture slots it samples are unchanged, so streaming elsewhere in the game no longer " - "rebuilds every set each frame. Applies after restart.", nullptr, true}, - {TAB_DEBUG, "Sampler cache", &opts.sampler_cache, 0, 1, 1, tx_onoff, - "What a sampler holds is kept beside it on the replay thread, so resolving a sampler that was not registered anew takes no " - "lock. Helps while the game streams textures. Applies after restart.", nullptr, true}, - // (Restart and Quit stay the table's last two: kRestartRow and kQuitRow count back from its end) - {TAB_GAMEPLAY, "Restart", nullptr, 0, 0, 0, nullptr, - "Starts the game again. Save your progress first. Press Enter or A twice to restart. Esc or B " - "cancels."}, - {TAB_GAMEPLAY, "Quit", nullptr, 0, 0, 0, nullptr, - "Save your progress first. Press Enter or A twice to quit. Esc or B cancels."}, -}; -constexpr int kQuitRow = sizeof rows / sizeof rows[0] - 1; -constexpr int kRestartRow = kQuitRow - 1; -const Row* pending = nullptr; // (Restart or Quit: shows Confirm until its second press) -bool cheats_env = false; // (HOS_OOT_CHEATS=1: on whatever the Debug tab says) -/// The debug tools: the Debug tab's row, or HOS_OOT_CHEATS=1 -bool cheats_on() { return cheats_env || opts.debug_tools; } -std::atomic protect_hearts{-1}; -// -1 disables the shortcut, 0 enables it on the ground, and 1 means flying. -std::atomic player_flight{-1}; -std::atomic free_camera{0}; -std::atomic cheat_action{0}; -std::atomic link_age{-1}; // (the game side's report, OOT_CHEAT_AGE_ID: 0 child, 1 adult, -1 no save loaded) -int clock_hour = 12; -int pouch_item = 0; -int pouch_delta = 1; -constexpr const char* pouch_items[] = {"Deku Seeds", "Deku Nuts", "Deku Sticks", "Bombs", "Arrows"}; -constexpr int kPouchItems = sizeof pouch_items / sizeof pouch_items[0]; -// (teleport list: generated by work/oot-teleport-tools/write_destinations.py) -// (the order of oot_destinations in host/hos_main.c; Enter sends 1000 + the index) -constexpr const char* teleport_names[] = { - "Kokiri Forest", - "Hyrule Field", - "Kakariko Village", - "Market", - "Hyrule Castle", - "Lost Woods", - "Sacred Forest Meadow", - "Lake Hylia", - "Graveyard", - "Death Mountain Trail", - "Death Mountain Crater", - "Goron City", - "Zora's River", - "Zora's Domain", - "Zora's Fountain", - "Gerudo Valley", - "Gerudo Fortress", - "Haunted Wasteland", - "Desert Colossus", - "Inside the Deku Tree", - "Dodongo's Cavern", - "Jabu-Jabu's Belly", - "Forest Temple", - "Fire Temple", - "Water Temple", - "Shadow Temple", - "Spirit Temple", - "Bottom of the Well", - "Ice Cavern", - "Gerudo Training Ground", - "Ganon's Castle"}; -constexpr const char* teleport_notes[] = {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, "adult only", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; -struct TeleportSection { const char* name; int first; }; -constexpr TeleportSection teleport_sections[] = {{"Overworld", 0}, {"Dungeons", 19}}; -constexpr int kTeleportSections = sizeof teleport_sections / sizeof teleport_sections[0]; -// (end of the generated teleport list) -constexpr int kTeleports = sizeof teleport_names / sizeof teleport_names[0]; -int teleport_choice = 0; -int teleport_columns = 1, teleport_page_rows = 1; // (as last drawn: the cards in a row, the rows that fit: Page Up / Down) -float teleport_scroll_px = 0, teleport_want_px = 0; // (the cards' scroll, eased toward teleport_want_px) -bool teleport_follow = true; // (the scroll follows the choice; the wheel lets go of it until the choice moves) -int teleport_wheel = 0; // (the wheel's notches since the last drawing: it scrolls by them) -int teleport_section_of(int i) { - int section = 0; - while (section + 1 < kTeleportSections && i >= teleport_sections[section + 1].first) ++section; - return section; -} -int teleport_section_end(int section) { - return section + 1 < kTeleportSections ? teleport_sections[section + 1].first : kTeleports; -} -/// The destination rows rows down (up when negative) from i, in the grid the Teleport page draws: each section's cards -/// teleport_columns to a row, under the section's heading; the column kept where the next row has it -int teleport_step_rows(int i, int rows) { - const int cols = std::max(1, teleport_columns); - for (; rows > 0; --rows) { - const int section = teleport_section_of(i), first = teleport_sections[section].first, end = teleport_section_end(section); - const int p = i - first; - if (p + cols < end - first) i += cols; - else if (p / cols < (end - first - 1) / cols) i = end - 1; // (the section's last row, a short one) - else if (section + 1 < kTeleportSections) i = std::min(end + p % cols, teleport_section_end(section + 1) - 1); - else break; - } - for (; rows < 0; ++rows) { - const int section = teleport_section_of(i), first = teleport_sections[section].first, p = i - first; - if (p >= cols) i -= cols; - else if (section > 0) { - const int prev = teleport_sections[section - 1].first, n = first - prev; - i = std::min(prev + (n - 1) / cols * cols + p, first - 1); - } else break; - } - return i; -} -// the Inventory page (gpu/vk/oot_cheat_items.h): items and their section headings, counts from the game side -#define OOT_MENU_ITEM_LABEL(label, name, kind) label, -#define OOT_MENU_ITEM_KIND(label, name, kind) kind, -#define OOT_MENU_NO_SECTION(name) -constexpr const char* item_names[] = {OOT_CHEAT_ITEMS(OOT_MENU_NO_SECTION, OOT_MENU_ITEM_LABEL)}; -constexpr unsigned char item_kinds[] = {OOT_CHEAT_ITEMS(OOT_MENU_NO_SECTION, OOT_MENU_ITEM_KIND)}; -constexpr int kItems = sizeof item_names / sizeof item_names[0]; -// (each section's first item: a counter that a section heading reads and an item bumps) -constexpr int item_section_count() { - int n = 0; -#define OOT_MENU_COUNT_SECTION(title) n++; -#define OOT_MENU_SKIP_ITEM(label, name, kind) - OOT_CHEAT_ITEMS(OOT_MENU_COUNT_SECTION, OOT_MENU_SKIP_ITEM) - return n; -} -constexpr int kItemSections = item_section_count(); -struct ItemSections { TeleportSection s[kItemSections]; }; -constexpr ItemSections make_item_sections() { - ItemSections r{}; - int section = 0, item = 0; -#define OOT_MENU_ADD_SECTION(title) r.s[section].name = title, r.s[section].first = item, section++; -#define OOT_MENU_ADD_ITEM(label, name, kind) item++; - OOT_CHEAT_ITEMS(OOT_MENU_ADD_SECTION, OOT_MENU_ADD_ITEM) - return r; -} -constexpr ItemSections item_sections = make_item_sections(); -std::atomic item_counts[kItems]; // (-1 until the game side reports: OOT_CHEAT_ITEM_BASE + i) -int item_choice = 0; -int inventory_columns = 1, inventory_capacity = 1; // (as last drawn: the cards in a row, on a page) -int inventory_filter = 0; // (All, then the item sections) -int inventory_page = 0; // (the page shown: the choice's, or another the wheel turned to) -bool inventory_amount = false; // (Left and Right change the chosen item's amount) -int inventory_begin() { return inventory_filter ? item_sections.s[inventory_filter - 1].first : 0; } -int inventory_end() { return inventory_filter && inventory_filter < kItemSections ? item_sections.s[inventory_filter].first : kItems; } -void inventory_reveal() { inventory_page = (item_choice - inventory_begin()) / std::max(1, inventory_capacity); } -void choose_inventory_filter(int filter) { - inventory_filter = filter; - item_choice = inventory_begin(); - inventory_amount = false; - inventory_reveal(); -} -constexpr int kCheatRows = 11; // (the debug tools' rows: Row::cheat 0 to 10) -/// A debug tool's value as its row shows it ("" an action: confirm does it) -std::string cheat_value(int i) { - char t[16]; - switch (i) { - case 0: return tx_onoff(protect_hearts.load() == 1); - case 1: return tx_onoff(player_flight.load() >= 0); - case 4: return snprintf(t, sizeof t, "%02d:00", clock_hour), t; - case 5: return pouch_items[pouch_item]; - case 6: return snprintf(t, sizeof t, "%+d", pouch_delta), t; - case 7: return tx_onoff(free_camera.load()); - default: return ""; - } -} -/// The tools that choose with Left and Right (arrows at their value's ends): Set time, Inventory item, Change quantity -bool cheat_chooses(int i) { return i == 4 || i == 5 || i == 6; } -/// Switch age as Kid | Adult, Link's age lit: once the game side has said it (a save loaded) -bool age_shown(int i) { return i == 9 && link_age.load(std::memory_order_relaxed) >= 0; } -/// What confirm does on a tool's row, for its prompt (nullptr: nothing) -const char* cheat_confirm(int i) { - static const char* const says[kCheatRows] = {"Toggle", "Toggle", "Apply", "Apply", "Set", nullptr, "Apply", "Toggle", "Open list", - "Switch", "Open"}; - return i >= 0 && i < kCheatRows ? says[i] : nullptr; -} -constexpr int kRows = sizeof rows / sizeof rows[0]; - -int cur_tab = TAB_GRAPHICS; -int tab_selected[TAB_COUNT + 1] = {}; // (and the Motion page's) -bool is_motion_row(const Row& r) { return r.tab == TAB_CONTROLS && !r.value && !strcmp(r.name, "Motion"); } - -// ---- the rows that take a restart: their values at the start (menu_init, the GPU's at menu_set_gpu_count) ---- -namespace mr = menu_restart; -mr::State restart_state; -int restart_slot[kRows]; // (a row's slot in restart_state, -1 none) -const char* restart_names[mr::kMax]; -void restart_init() { - static bool done = false; - if (done) return; - done = true; - for (int i = 0; i < kRows; i++) { - restart_slot[i] = rows[i].restart && rows[i].value ? restart_state.add(*rows[i].value) : -1; - if (restart_slot[i] >= 0) restart_names[restart_slot[i]] = rows[i].name; - } -} -int restart_slot_of(const Row& r) { return restart_slot[&r - rows]; } -/// A row that takes a restart, changed from what the game started with (a row past menu_restart.h's kMax has no slot: never) -bool restart_changed(const Row& r) { - const int slot = restart_slot_of(r); - return r.value && r.restart && slot >= 0 && *r.value != restart_state.start[slot]; -} -void restart_values(int* now) { - for (int i = 0; i < kRows; i++) - if (restart_slot[i] >= 0) now[restart_slot[i]] = *rows[i].value; -} -/// The rows whose value is not the one the game runs with (a bit a slot) -unsigned restart_pending() { - int now[mr::kMax]; - restart_values(now); - return restart_state.pending(now); -} -/// Whether closing the menu now asks to restart: something pending, not declined with Later, and a window to close -bool restart_ask() { - int now[mr::kMax]; - restart_values(now); - return app_window() && restart_state.ask(now); -} -std::string restart_list(const char* last) { - return mr::names(restart_names, restart_state.n, restart_pending(), ", ", last); -} -int ask_sel = 0; // (the restart question's choice: 0 Later, 1 Restart now) - -/// A row on its tab: the debug tools only with them on; a row for what this PC cannot run not at all (Reflex and Anti-Lag 2 -/// only where the driver has them, the Upscaler only where more than the game's own runs, the DLSS model only where DLSS -/// runs); HDR's paper white and peak brightness only with HDR on. A row that comes or goes moves those after it: the -/// selection is a place on its tab (tab_selected), so a row hidden after the selected one leaves the selection where it is, -/// and menu_tick brings back one past the end -bool row_shown(int i) { - const int* v = rows[i].value; - return (rows[i].cheat < 0 || cheats_on()) && (v != &opts.reflex || reflex_available()) && - (v != &opts.antilag || antilag_available()) && - (v != &opts.dlss || ur::upscaler_shown(upscalers_available())) && - (v != &opts.dlss_model || ur::model_shown(dlss_why() == DLSS_OK)) && - ((v != &opts.hdr_white && v != &opts.hdr_peak) || opts.hdr != 0); -} -int tab_row_count(int t) { - int c = 0; - for (int i = 0; i < kRows; i++) if (rows[i].tab == (MenuTab)t && row_shown(i)) c++; - return c; -} - -int tab_row_to_global(int t, int local_idx) { - int count = 0; - for (int i = 0; i < kRows; i++) { - if (rows[i].tab == (MenuTab)t && row_shown(i)) { - if (count == local_idx) return i; - count++; - } - } - return 0; -} - -std::atomic open_now{false}; -bool menu_refresh = false; -// (a value too long for its row, a GPU's name: the selected row's slides through it, a character a step, resting at -// each end; the other rows end it with "...". The drawing (fit) says how far it can slide, menu_tick moves it) -int marquee_pos = 0, marquee_max = 0; -int64_t marquee_next = 0; -constexpr int64_t kMarqueeStep = 150000000LL, kMarqueeRest = 1200000000LL; // (ns) -// (hookshot) the mouse (menu_tick): the tab strip's part under the cursor (a tab 0 to TAB_COUNT - 1, TAB_COUNT the previous, -// TAB_COUNT + 1 the next; -1 none); the slider held with the left button (its row in the tab, -1 none) -int hover_tab = -1; -int drag_row = -1; -bool mouse_read = false; // (the menu has read the mouse since it opened) -bool is_slider(const Row& r) { return r.value == &opts.sharpness || r.value == &opts.mouse_sens || r.value == &opts.gyro_sens_x || r.value == &opts.gyro_sens_y || r.value == &opts.volume; } -/// (a scale: the arrows and a click stop at its ends; Rumble strength would go from Off to its strongest) -bool stops_at_ends(const Row& r) { return r.value == &opts.mouse_sens || r.value == &opts.rumble || r.value == &opts.gyro_sens_x || r.value == &opts.gyro_sens_y || r.value == &opts.volume; } -/// (the ini's Rumble strength on the menu's steps: 0 to 150, the nearest 25) -int rumble_step(int v) { return (std::min(std::max(v, 0), 150) + 12) / 25 * 25; } -std::string ini_path() { - char exe[MAX_PATH]; - const DWORD n = game_module_path(exe, MAX_PATH); - std::string dir(exe, n); - return dir.substr(0, dir.find_last_of("\\/") + 1) + "hookshot_options.ini"; -} -const char* ini_key(const Row& r) { - if (r.value == &opts.output_res) return "output_res"; // (host/nn_applet.c reads it at the start) - // (rows renamed since their values were first saved keep their keys: an options file from before still loads) - if (r.value == &opts.resolution) return "resolution"; - if (r.value == &opts.fps_cap) return "frame_rate_cap"; // (host/nn_vi.c reads it at the start) - if (r.value == &opts.upscale) return "upscaling"; - if (r.value == &opts.dlss) return "dlss"; // (as opts.dlss and RX_VK_DLSS) - if (r.value == &opts.handheld) return "handheld_profile"; // (the Profile row; host/nn_applet.c reads it at the start) - static char k[32]; - size_t n = 0; - for (const char* p = r.name; *p && n + 1 < sizeof k; p++) k[n++] = *p == ' ' ? '_' : (char)tolower(*p); - k[n] = 0; - return k; -} -// (hookshot) the controls, kept by the host (host/nn_hid.c reads them back from here): pad_