mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
Add a free camera to the debug menu.
With it on, the game draws from a camera of the menu's instead of its own, in play and in cutscenes. The left stick moves it along its view and sideways, the right stick turns it, ZR and ZL raise and lower it, R makes it faster and L slower, and Link stands meanwhile; or the camera stays where it is and the pad plays the game. It works inside the game's own camera update: a new patch site sends the instruction at which the frame's position, target and up vector are final to a host function, which replaces them for the camera that follows Link and the one the events direct, so the game draws, culls and loads detail for what the free camera sees. The camera of the interface's 3D models and the one the sound follows stay the game's. The set is off unless the menu switches it on. A teleport's arrival starts the free camera again from the game's view. Tested in Hyrule Field with scripted stick input (turn, rise, strafe, with Link's position unchanged), with the pad playing while the camera stays, across a teleport to Kakariko Village, during the Kakariko arrival cutscene, and switched on by a click in the menu. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
This commit is contained in:
@@ -46,10 +46,13 @@ Draft notes for the release after 0.7.1. The version number is not decided yet.
|
||||
warp song does, so the area loads as in play, a first visit's cutscene plays and a boss room starts its fight.
|
||||
*Cutscenes*: the game's 653 event scripts by name (cutscenes, conversations, scripted moments), each started with a
|
||||
click as if its trigger had fired. The game takes a teleport or an event only while Link stands free: not during a
|
||||
cutscene or a conversation, and not while he swims. *World*: Link's position and the game's speed, from 0.05x to
|
||||
8x. *Memory*: a search of the game's memory for a number (1, 2 or 4 bytes, or a float), narrowed down by equals,
|
||||
changed, unchanged, went up or went down, with a value to set at a place or to hold there. A free camera is not in
|
||||
it yet. Changing flags or memory, teleporting and starting events can break a save, and the game saves by itself
|
||||
cutscene or a conversation, and not while he swims. *World*: Link's position, the game's speed from 0.05x to 8x,
|
||||
and a free camera. With it on, the game draws from a camera of your own, in play and in cutscenes: close the menu
|
||||
and the left stick moves it, the right stick turns it, ZR and ZL raise and lower it, R makes it faster and L slower,
|
||||
while Link stands; or leave it where it is and play on with the pad. It works inside the game's own camera
|
||||
update, so the game draws, culls and loads detail for what the camera sees. *Memory*: a search of the game's memory
|
||||
for a number (1, 2 or 4 bytes, or a float), narrowed down by equals, changed, unchanged, went up or went down, with
|
||||
a value to set at a place or to hold there. Changing flags or memory, teleporting and starting events can break a save, and the game saves by itself
|
||||
after a change of area, so keep a copy first.
|
||||
- **Add to Steam** in Customize Launcher (Windows): adds the game to your Steam library as *The Legend of Zelda
|
||||
Ocarina of Time*, with Navi as its icon and the title screen's artwork and logo. Starting it from Steam starts the
|
||||
|
||||
@@ -414,9 +414,9 @@
|
||||
# The debug menu, a tool for testing (Ocarina of Time 1.1.1): with 1, F9 opens a window with the game's on/off flags
|
||||
# by name (the names found in your own game's event files), a teleport to every area's start positions (the warp
|
||||
# songs' pads and both sides of the loading zones among them), the game's cutscenes and other events to start by name,
|
||||
# the game's speed, Link's position, and a search of the game's memory with values to set and to hold. Changing flags
|
||||
# or memory, teleporting and starting events can break a save, and the game saves by itself after a change of area:
|
||||
# keep a copy of the save first. Do not bind F9 to a game button while it is on. The menu's Game tab has the switch. Takes effect at once.
|
||||
# the game's speed, Link's position, a free camera flown with the sticks, and a search of the game's memory with
|
||||
# values to set and to hold. Changing flags or memory, teleporting and starting events can break a save, and the game
|
||||
# saves by itself after a change of area: keep a copy of the save first. Do not bind F9 to a game button while it is on. The menu's Game tab has the switch. Takes effect at once.
|
||||
# debug_menu = 0
|
||||
# The DLSS model: default (NVIDIA's advice per mode: K for dlaa, quality and balanced, M for performance, L for
|
||||
# ultra_performance), k (K in every mode) or j (the other transformer model: slightly less ghosting behind moving
|
||||
|
||||
@@ -19,6 +19,7 @@ set res_fixed NX2NAVI_PATCH_RES_FIXED 0
|
||||
set ostep NX2NAVI_OCARINA_REALTIME 1
|
||||
set zstd NX2NAVI_ZSTD_NATIVE 1
|
||||
set osolver NX2NAVI_PHYSICS_STEPS 1
|
||||
set freecam NX2NAVI_PATCH_FREECAM 0
|
||||
|
||||
# Real-time step: the framework's per-frame update (main+0x6e6be0) gives the time manager **[main+0x4c7e890] its
|
||||
# nominal frame through main+0x3b4e300 (+0x10 frames per update, +0x14 the inverse, +0x18 seconds, +0x1c the rate),
|
||||
@@ -122,3 +123,11 @@ site zstd main e910c 9400089d 9536dfb9 # bl ZSTD_decompressMultiFrame -> page
|
||||
site zstd main bf72e0 97d3d028 950aa744 # bl ZSTD_decompressMultiFrame -> page+0xff0
|
||||
site zstd main f50164 97c66c87 94fd43a3 # bl ZSTD_decompressMultiFrame -> page+0xff0
|
||||
site zstd main f5c2c 94000015 9536acd5 # bl the stream feed -> page+0xf80
|
||||
|
||||
# The debug menu's free camera (runtime/debug_tools.c dbg_freecam_host; switched on by the menu, never by a setting). The
|
||||
# camera update (main+0x4c48c0) chooses the frame's position and target (doubles) and up vector between the camera's
|
||||
# own and an override; every path of that choice joins at main+0x4c4e50 with them in d0-d2, d3-d5 and s7, s16, s6,
|
||||
# before they are stored into the look-at camera and its matrix is made. That instruction goes to the host function
|
||||
# on the patch page (+0xf70), which puts the free camera's vectors into those registers and does the instruction's
|
||||
# own work.
|
||||
site freecam main 4c4e50 1e624011 95277048 # fcvt s17, d0 -> bl page+0xf70
|
||||
|
||||
@@ -51,7 +51,7 @@ static const char *const known[] = {
|
||||
"MENU_CURSOR", "MENU_CURSOR_LOG", "MENU_TEST_CLICK", "MENU_TEST_DIR", "MENU_TEST_SET", "MINIMAL_LAYOUT", "MICROPHONE", "MIRROR_GAP", "MODS_DIR", "MODS_DISABLED", "MODS_ORDER", "MODS_REBUILD", "MONITOR", "MOTION", "MOUSE_MODE", "MOUSE_DIRECT_PATCH", "MOUSE_SENS", "MOVIE_HOST", "MOVIE_PACE",
|
||||
"MVOBJ_ALL", "MVOBJ_BANKS", "MVOBJ_EQUAL", "MVOBJ_EXACT", "MVOBJ_EXEC", "MVOBJ_NOC4", "MVOBJ_ONLY", "MVOBJ_TIMES", "MVPREV_CHECK", "MVPREV_GROW", "MVPREV_SKIP", "MVPREV_TRUST", "MV_CHECK",
|
||||
"MV_SURVEY",
|
||||
"ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PACE_HZ", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_RES_FIXED", "PATCH_SCENE", "PATCH_WIDE_ASPECT", "PATCH_WIDE_HUD", "PATCH_WIDE_MOVIE", "PATCH_WIDE_OUT", "PATCH_WIDE_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPELINE_CACHE_MAX_MB", "PIPE_BINARIES", "PIPE_BINARIES_MAX_MB", "PIPE_FEEDBACK", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", "POWER_FULL", "TEX_SAME", "PRESENT_HELD", "HELD_SYNC",
|
||||
"ONE_PER_CORE", "OPERATION_MODE", "OUTPUT_RES", "PAD_LAYOUT", "PACE_HZ", "PASS_CRUMBS", "PATCH_OUTPUT", "PATCH_RES_FIXED", "PATCH_SCENE", "PATCH_WIDE_ASPECT", "PATCH_WIDE_HUD", "PATCH_WIDE_MOVIE", "PATCH_WIDE_OUT", "PATCH_FREECAM", "PATCH_WIDE_SCENE", "PCSS", "PCSS_K", "PERF_OVERLAY", "PERF_SLOW_MS", "PIPELINE_CACHE", "PIPELINE_CACHE_MAX_MB", "PIPE_BINARIES", "PIPE_BINARIES_MAX_MB", "PIPE_FEEDBACK", "PIPE_MEMO", "PIPE_PREBUILD", "PIPE_SKIP", "PIPE_SYNC", "PIPE_WAIT", "POWER_FULL", "TEX_SAME", "PRESENT_HELD", "HELD_SYNC",
|
||||
"PREWARM_FULL", "PREWARM_WAIT_MS", "PREWARM_XLATE", "PROBE_MOVIE", "PROBE_MOVIE_SHOT", "PROFILE", "PROFILE_CHAINS", "PROFILE_DIR", "PROFILE_FRAMES", "PROFILE_REPLAY", "PROFILE_SLOW",
|
||||
"PROFILE_MAIN", "PROFILE_RENDER", "PROMPTS", "PUSH_SKIP", "PUSH_STAGE", "RDOC", "RDOC_DIR", "RDOC_DLL", "REALTIME", "REC_END", "REEXEC",
|
||||
"REFLEX", "RENDER_SCALE", "REPLAY_ASYNC", "RTSS", "RTSS_ENUM", "SCENE_SHARE",
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
//
|
||||
// An event (a cutscene, a conversation, a scripted moment) is started by its file's name through the game's event
|
||||
// manager, as the game's own actors start them.
|
||||
//
|
||||
// The free camera stands in for the game's camera inside the game's own camera update (dbg_freecam_host), so the game
|
||||
// itself draws from it.
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
@@ -544,6 +547,7 @@ static void warp_tick(Cpu *c, uint64_t tick) {
|
||||
status_set(there ? "Arrived in %s, %s." : "The teleport to %s, %s was asked for; no arrival was seen.", warp.area,
|
||||
warp.pos[0] ? warp.pos : "its default start");
|
||||
warp.state = W_IDLE;
|
||||
dbg_freecam_reset(); // a free camera left behind in the old area's place starts again from the game's view
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -626,10 +630,127 @@ int dbg_search_next(int mode, uint32_t value) {
|
||||
return kept;
|
||||
}
|
||||
|
||||
// ---- the free camera. The game's camera update (main+0x4c48c0) chooses the frame's position and target (doubles) and
|
||||
// up vector between the camera's own and an override, and at main+0x4c4e50 has them in d0-d2, d3-d5 and s7, s16, s6,
|
||||
// before it stores them into the look-at camera and makes its matrix. The patch set freecam (recomp/patches-v111.txt)
|
||||
// sends that instruction here while the free camera is on: the registers get the free camera's vectors, so the game
|
||||
// draws, culls and loads detail for the view it is given, in play and in cutscenes, and Link, the world and the
|
||||
// game's own camera work go on as they were. The pad's sticks fly it (dbg_pad, from ov_input.c before the game gets the pad's state).
|
||||
#include "input_map.h"
|
||||
|
||||
static struct {
|
||||
volatile int on, capture, sticks; // the menu's: on; take the game's view at the next update; the sticks fly it
|
||||
volatile float speed; // metres a second
|
||||
double pos[3], reach; // the game thread's: where it is, and the game's distance to its target
|
||||
float yaw, pitch;
|
||||
uint64_t last_ns, updates;
|
||||
} fc = {.sticks = 1, .speed = 8.0f, .reach = 5.0};
|
||||
|
||||
static double reg_d(Cpu *c, int r) {
|
||||
uint64_t b = (uint64_t)c->v[r];
|
||||
double v;
|
||||
memcpy(&v, &b, 8);
|
||||
return v;
|
||||
}
|
||||
static void set_d(Cpu *c, int r, double v) {
|
||||
uint64_t b;
|
||||
memcpy(&b, &v, 8);
|
||||
c->v[r] = b;
|
||||
}
|
||||
static void set_s(Cpu *c, int r, float v) {
|
||||
uint32_t b;
|
||||
memcpy(&b, &v, 4);
|
||||
c->v[r] = b;
|
||||
}
|
||||
|
||||
// The update runs for each of the game's cameras, every frame: the one that follows Link and the one its events
|
||||
// direct are the scene's, and the free camera stands in for both; the camera of the interface's 3D models and the
|
||||
// one the sound follows stay the game's. A camera's state (x19) has its actor at +0x10, the actor its name at +0x20.
|
||||
static int scene_camera(uint64_t state) {
|
||||
char name[16] = "";
|
||||
uint64_t actor = rd_ptr(state + 0x10), np = actor ? rd_ptr(actor + 0x20) : 0;
|
||||
if (!np || !rd(np, name, 13)) return 0;
|
||||
return !memcmp(name, "PlayerCamera", 13) || !memcmp(name, "EventCamera", 12);
|
||||
}
|
||||
|
||||
// The patch site's host function, on the patch page at +0xf70. patches.c registers it: that file is compiled with
|
||||
// the game version's patches.h, which has the page's address.
|
||||
void dbg_freecam_host(Cpu *c) {
|
||||
fc.updates++;
|
||||
if (fc.on && scene_camera(c->x[19])) {
|
||||
if (fc.capture) { // start where the game's camera is, looking where it looks
|
||||
double d[3] = {reg_d(c, 3) - reg_d(c, 0), reg_d(c, 4) - reg_d(c, 1), reg_d(c, 5) - reg_d(c, 2)};
|
||||
double len = sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
|
||||
if (isfinite(len) && len > 1e-3) {
|
||||
for (int i = 0; i < 3; i++) fc.pos[i] = reg_d(c, i);
|
||||
fc.yaw = (float)atan2(d[0], d[2]), fc.pitch = (float)asin(d[1] / len);
|
||||
fc.reach = len < 1 ? 1 : len > 50 ? 50 : len;
|
||||
fc.capture = 0;
|
||||
}
|
||||
}
|
||||
if (!fc.capture) {
|
||||
double cp = cos(fc.pitch), dir[3] = {cp * sin(fc.yaw), sin(fc.pitch), cp * cos(fc.yaw)};
|
||||
for (int i = 0; i < 3; i++) set_d(c, i, fc.pos[i]), set_d(c, 3 + i, fc.pos[i] + dir[i] * fc.reach);
|
||||
set_s(c, 7, 0), set_s(c, 16, 1), set_s(c, 6, 0); // up
|
||||
}
|
||||
}
|
||||
set_s(c, 17, (float)reg_d(c, 0)); // the instruction this call stands in for: fcvt s17, d0
|
||||
}
|
||||
|
||||
int dbg_freecam_available(void) { return dbg_supported() && rt_patch_set("freecam") >= 0; }
|
||||
|
||||
void dbg_freecam_set(int on, int sticks, float speed) {
|
||||
if (!dbg_freecam_available()) return;
|
||||
fc.sticks = sticks != 0;
|
||||
fc.speed = speed >= 0.5f && speed <= 200.0f ? speed : 8.0f;
|
||||
if (on && !fc.on) fc.capture = 1;
|
||||
if ((on != 0) != fc.on) rt_log("debug: free camera %s", on ? "on" : "off"); // what a tester changed, for the log
|
||||
fc.on = on != 0;
|
||||
rt_patch_switch(rt_patch_set("freecam"), on != 0);
|
||||
}
|
||||
|
||||
void dbg_freecam_reset(void) { fc.capture = 1; } // back to the game's view, and free from there
|
||||
|
||||
int dbg_freecam(int *sticks, float *speed, double pos[3]) {
|
||||
if (sticks) *sticks = fc.sticks;
|
||||
if (speed) *speed = fc.speed;
|
||||
if (pos) memcpy(pos, fc.pos, sizeof fc.pos);
|
||||
return fc.on;
|
||||
}
|
||||
|
||||
// The game's thread, with the pad's state before the game gets it: while the free camera has the sticks, the left one
|
||||
// moves it along its view and sideways, the right one turns it, ZR and ZL raise and lower it, R makes it four times
|
||||
// as fast and L four times as slow, and the game is told that none of those is touched, so Link stays where he is.
|
||||
void dbg_pad(uint64_t *buttons, int32_t *lx, int32_t *ly, int32_t *rx, int32_t *ry) {
|
||||
if (!fc.on || !fc.sticks || fc.capture) {
|
||||
fc.last_ns = 0;
|
||||
return;
|
||||
}
|
||||
uint64_t now = plat_now_ns();
|
||||
double dt = fc.last_ns ? (double)(now - fc.last_ns) / 1e9 : 0;
|
||||
fc.last_ns = now;
|
||||
if (dt > 0.1) dt = 0.1;
|
||||
double in[4] = {*lx / 32767.0, *ly / 32767.0, *rx / 32767.0, *ry / 32767.0};
|
||||
for (int i = 0; i < 4; i++) in[i] = fabs(in[i]) < 0.15 ? 0 : in[i];
|
||||
const uint64_t zl = 1ull << B_ZL, zr = 1ull << B_ZR, l = 1ull << B_L, r = 1ull << B_R;
|
||||
double rise = (*buttons & zr ? 1 : 0) - (*buttons & zl ? 1 : 0);
|
||||
double step = fc.speed * (*buttons & r ? 4 : 1) * (*buttons & l ? 0.25 : 1) * dt;
|
||||
fc.yaw -= (float)(in[2] * 2.4 * dt);
|
||||
fc.pitch += (float)(in[3] * 1.8 * dt);
|
||||
fc.pitch = fc.pitch > 1.5f ? 1.5f : fc.pitch < -1.5f ? -1.5f : fc.pitch;
|
||||
double cp = cos(fc.pitch), dir[3] = {cp * sin(fc.yaw), sin(fc.pitch), cp * cos(fc.yaw)};
|
||||
double right[3] = {-cos(fc.yaw), 0, sin(fc.yaw)}; // the view's direction crossed with up
|
||||
for (int i = 0; i < 3; i++) fc.pos[i] += (dir[i] * in[1] + right[i] * in[0]) * step;
|
||||
fc.pos[1] += rise * step;
|
||||
*lx = *ly = *rx = *ry = 0;
|
||||
*buttons &= ~(zl | zr | l | r);
|
||||
}
|
||||
|
||||
// ---- NX2NAVI_DBG_TEST="<tick>:flag <hash hex> <0|1>;<tick>:speed <factor>;<tick>:find <value> <size>;
|
||||
// <tick>:next <mode> <value>;<tick>:setfirst <value>;<tick>:holdfirst <value>;<tick>:show [<hash hex>];
|
||||
// <tick>:warp <area> [<start position>];<tick>:event <name>;<tick>:text <game data text's name>": the menu's requests
|
||||
// without the menu, for test runs. Ticks count the game's pad reads from the player's first appearance.
|
||||
// <tick>:warp <area> [<start position>];<tick>:event <name>;<tick>:text <game data text's name>;
|
||||
// <tick>:freecam <0|1> <sticks 0|1> <speed>;<tick>:camgo <dx> <dy> <dz> [<yaw> <pitch>];<tick>:camreset;<tick>:campos":
|
||||
// the menu's requests without the menu, for test runs. Ticks count the game's pad reads from the player's first appearance.
|
||||
static void test_script(uint64_t tick) {
|
||||
static char buf[2048];
|
||||
static const char *next;
|
||||
@@ -649,6 +770,23 @@ static void test_script(uint64_t tick) {
|
||||
dbg_flag_set(hash, (int)on);
|
||||
} else if (sscanf(colon + 1, "speed %f", &f) == 1) {
|
||||
dbg_speed_set(f);
|
||||
} else if (!strncmp(colon + 1, "campos", 6)) {
|
||||
rt_log("debug: tick %llu: free camera %s at %.3f %.3f %.3f, yaw %.3f, pitch %.3f; %llu camera updates through it",
|
||||
(unsigned long long)tick, fc.on ? "on" : "off", fc.pos[0], fc.pos[1], fc.pos[2], fc.yaw, fc.pitch,
|
||||
(unsigned long long)fc.updates);
|
||||
} else if (!strncmp(colon + 1, "freecam ", 8)) { // freecam <on> <sticks> <speed>
|
||||
int cam_on = 0, sticks = 1;
|
||||
float speed = 8;
|
||||
if (sscanf(colon + 9, "%d %d %f", &cam_on, &sticks, &speed) >= 1) dbg_freecam_set(cam_on, sticks, speed);
|
||||
} else if (!strncmp(colon + 1, "camreset", 8)) {
|
||||
dbg_freecam_reset();
|
||||
} else if (!strncmp(colon + 1, "camgo ", 6)) { // camgo <dx> <dy> <dz> <yaw> <pitch>: the free camera moved by hand
|
||||
double d[3] = {0, 0, 0};
|
||||
float yaw = 0, pitch = 0;
|
||||
if (sscanf(colon + 7, "%lf %lf %lf %f %f", &d[0], &d[1], &d[2], &yaw, &pitch) >= 3) {
|
||||
for (int i = 0; i < 3; i++) fc.pos[i] += d[i];
|
||||
fc.yaw += yaw, fc.pitch += pitch;
|
||||
}
|
||||
} else if (!strncmp(colon + 1, "warp ", 5)) {
|
||||
char area[64] = "", pos[64] = "";
|
||||
if (sscanf(colon + 6, "%63[^ ;] %63[^ ;]", area, pos) >= 1) dbg_warp(area, pos);
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
#ifndef __cplusplus
|
||||
#include "cpu.h"
|
||||
void dbg_tick(Cpu *c); // the game's thread, at its pad read (ov_input.c): does what the menu asked for
|
||||
// ... and the pad's state before the game gets it: the free camera takes the sticks and four buttons while it flies
|
||||
void dbg_pad(uint64_t *buttons, int32_t *lx, int32_t *ly, int32_t *rx, int32_t *ry);
|
||||
void dbg_freecam_host(Cpu *c); // the free camera's patch site (recomp/patches-v111.txt, set freecam)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
@@ -39,6 +42,13 @@ void dbg_event_start(const char *name);
|
||||
int dbg_busy(void);
|
||||
void dbg_status(char out[160]);
|
||||
|
||||
// The free camera: the game draws from a camera of the menu's instead of its own. With sticks on, the pad flies it and
|
||||
// Link stands; with sticks off it stays where it is and the pad plays the game. speed in metres a second.
|
||||
int dbg_freecam_available(void); // the build has the camera's patch set
|
||||
void dbg_freecam_set(int on, int sticks, float speed);
|
||||
void dbg_freecam_reset(void); // back to the game's view, free again from there
|
||||
int dbg_freecam(int *sticks, float *speed, double pos[3]); // on or off
|
||||
|
||||
// Memory: values of 1, 2 or 4 bytes in the game's heap.
|
||||
int dbg_search_new(uint32_t value, int size); // returns the number of places
|
||||
int dbg_search_next(int mode, uint32_t value); // 0 equals value, 1 changed, 2 unchanged, 3 greater, 4 less
|
||||
|
||||
@@ -95,6 +95,7 @@ static void npad_states(Cpu *c, uint32_t want) {
|
||||
input_poll(&s.buttons, &s.lx, &s.ly, &s.rx, &s.ry);
|
||||
}
|
||||
dbg_tick(c); // the debug menu's changes to the game, between two of its frames (debug_tools.c)
|
||||
if (live) dbg_pad(&s.buttons, &s.lx, &s.ly, &s.rx, &s.ry); // its free camera takes the sticks while it flies
|
||||
*out = s;
|
||||
ret_x(c, 1);
|
||||
}
|
||||
|
||||
@@ -27,6 +27,13 @@
|
||||
|
||||
uint8_t rt_patch_on[RT_PATCH_COUNT > 0 ? RT_PATCH_COUNT : 1]; // read by the generated C at every patched site
|
||||
|
||||
// The debug menu's free camera (debug_tools.c dbg_freecam_host): the site of the set freecam calls the page's +0xf70.
|
||||
// Registered here, in the file that has the game version's page address.
|
||||
#ifdef RT_PATCH_PAGE
|
||||
#include "debug_tools.h"
|
||||
__attribute__((constructor)) static void freecam_register(void) { rt_register_host(RT_PATCH_PAGE + 0xf70, dbg_freecam_host); }
|
||||
#endif
|
||||
|
||||
#if RT_PATCH_COUNT
|
||||
static const struct { const char *name, *env; int on; } sets[] = {
|
||||
#define X(i, n, e, d) {#n, e, d},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// open, so its input, its sounds and the game's blocked pad are the menu's own): on the left edge, in the performance
|
||||
// overlay's look, with the game in view beside it. What it does to the game is in
|
||||
// debug_tools.c: the game's on/off flags by name, teleporting to the game's own start positions, starting its events,
|
||||
// the game's speed, Link's position, and a search of the game's memory with values to set and to hold. Plain Dear ImGui with English text: a tool for testing, not part of the game's
|
||||
// the game's speed, Link's position, a free camera, and a search of the game's memory with values to set and to hold. Plain Dear ImGui with English text: a tool for testing, not part of the game's
|
||||
// interface. Present thread only.
|
||||
#include <algorithm>
|
||||
#include <ctype.h>
|
||||
@@ -288,7 +288,30 @@ void tab_world(void) {
|
||||
ImGui::TextWrapped("The whole game runs at this speed, its interface and its music's timing included. It goes back to 1x when "
|
||||
"the game is closed.");
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("Not in this menu yet: the free camera.");
|
||||
ImGui::TextColored(ImVec4(236 / 255.0f, 196 / 255.0f, 104 / 255.0f, 1), "Free camera");
|
||||
if (!dbg_freecam_available()) {
|
||||
ImGui::TextWrapped("The free camera needs the game's code built by this version: the launcher's update or its rebuild "
|
||||
"makes it.");
|
||||
return;
|
||||
}
|
||||
int sticks = 1;
|
||||
float cam_speed = 8;
|
||||
double at[3] = {0, 0, 0};
|
||||
bool on = dbg_freecam(&sticks, &cam_speed, at) != 0;
|
||||
bool changed = ImGui::Checkbox("The game draws from a camera of your own", &on);
|
||||
if (on) {
|
||||
if (ImGui::RadioButton("The sticks fly the camera, Link stands", sticks != 0)) sticks = 1, changed = true;
|
||||
if (ImGui::RadioButton("The camera stays where it is, the pad plays the game", sticks == 0)) sticks = 0, changed = true;
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::SliderFloat("##camspeed", &cam_speed, 1.0f, 100.0f, "%.0f metres a second", ImGuiSliderFlags_Logarithmic)) changed = true;
|
||||
if (ImGui::Button("Back to the game's view")) dbg_freecam_reset();
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("x %.1f y %.1f z %.1f", at[0], at[1], at[2]);
|
||||
}
|
||||
if (changed) dbg_freecam_set(on, sticks, cam_speed);
|
||||
ImGui::TextWrapped("Close this menu to fly: the left stick moves along the view and sideways, the right stick turns, ZR and ZL "
|
||||
"go up and down, R is four times as fast and L four times as slow. The game goes on meanwhile and "
|
||||
"draws what the camera sees; the game speed above slows it down. The camera does not stop at walls.");
|
||||
}
|
||||
|
||||
// ---- memory
|
||||
|
||||
@@ -1265,8 +1265,8 @@ static void tab_game(float scale) {
|
||||
const bool on = cfg_bool("NX2NAVI_DEBUG_MENU");
|
||||
if (opt_toggle_text("debug_menu", "Debug menu (F9)",
|
||||
"F9 opens a window for testing: the game's flags by name, a teleport to every area's start "
|
||||
"positions, its cutscenes to start, the game's speed, and a search of its memory with values "
|
||||
"to set and hold. All of these can break a save, and the game saves by itself after a change "
|
||||
"positions, its cutscenes to start, a free camera, the game's speed, and a search of its "
|
||||
"memory with values to set and hold. All of these can break a save, and the game saves by itself after a change "
|
||||
"of area: keep a copy of the save first.", on))
|
||||
opt_apply("NX2NAVI_DEBUG_MENU", on ? "0" : "1");
|
||||
}
|
||||
|
||||
@@ -186,7 +186,8 @@ LLVM_LINK_LIBS = ['-lpsapi', '-lbcrypt', '-luser32', '-lgdi32', '-ldwmapi', '-li
|
||||
DEFAULT_DEPS = ROOT / 'toolchain' / 'deps-mingw'
|
||||
# Runtime sources compiled per install, not in the library: romfs.c includes romfs_config.h, patches.c and ov_zstd.c
|
||||
# the patches.h of the player's game version (its patch page is the page after that version's main; ov_zstd.c's host
|
||||
# functions sit on it). mouse_direct.c also registers host callbacks relative to that version's patch page.
|
||||
# functions sit on it, and patches.c registers the debug menu's free camera there). mouse_direct.c also registers
|
||||
# host callbacks relative to that version's patch page.
|
||||
INSTALL_SIDE = {'romfs.c', 'patches.c', 'ov_zstd.c', 'mouse_direct.c'}
|
||||
LIB_NAME = 'libnx2navirt.a'
|
||||
AR = UCRT / 'llvm-ar.exe' if WIN else on_path('llvm-ar')
|
||||
|
||||
Reference in New Issue
Block a user