mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
The physics' two water managers and the game's interactive floor simulation (the Deku Tree's ground webs) step their surfaces in fixed steps of 1/60 s, as many whole steps as the frame's time holds, and drop the fraction, so every frame shorter than 1/60 s ran none and the surfaces stood still above 60 fps. The new set owater (water_steps, on by default) calls the page between the division and the game's cut, which keeps the fraction per object; at 60 fps the count is the game's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
201 lines
9.8 KiB
C
201 lines
9.8 KiB
C
// Sprint button for Ocarina of Time 1.1.1 (the patch set sprint, recomp/patches-v111.txt). Link sprints while his
|
|
// input flags hold the dash input (bit 4 of the word at *(Link +0x50)). The set's two sites call the host functions
|
|
// below right after the game loads those flags, and they OR the bit in while the sprint button's sprint is on, so the
|
|
// game's own sprint runs: its speed, animation, camera and every rule that ends it.
|
|
//
|
|
// Settings (live): sprint_button picks the button (off, b the game's B button, the bottom face button of the Nintendo
|
|
// layout, or l3 the left stick's click, which has no other use); sprint_mode is toggle (a press starts the sprint, the
|
|
// next press or Link's state ends it) or hold (sprint while the button is held). During gameplay the button is kept
|
|
// from the game (sprint_pad, called by ov_input.c npad_states), so it does not also fire its own action. B is kept only
|
|
// for a press that begins while Link moves (Walk, Jump, Land, Fall, Clamber), and then until it is released, so it
|
|
// puts the weapon away as usual otherwise and the game never sees half a press. Presses made in a menu, a cutscene or
|
|
// a load (no update of Link within GAMEPLAY_POLLS polls) start nothing: B closes the pause menu, and that press is the
|
|
// menu's. Under the game's Swap Jump Controls B is the jump, which a B sprint button keeps from the game while Link
|
|
// moves: L3 is the one to use then.
|
|
// Patch page slots: the set's sites call +0xf40 and +0xf30; +0xf20 and +0xf50 to +0xff0 belong to other sets
|
|
// (runtime/patches.c, mouse_direct.c, ov_zstd.c). A slot registered twice ends the start (dispatch.c rt_register_host).
|
|
//
|
|
// Link's state: the id at [[[[Link +0x18] +0x270] +0x188] +0x1c0] +0x28 (+0x4c when that reads 0x11a, a state that
|
|
// wraps another), the same field main+0x4aed50 compares with 1 (Walk) before the game starts a sprint.
|
|
#include <stdatomic.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "cfg.h"
|
|
#include "host.h"
|
|
#include "layout.h"
|
|
#include "sprint.h"
|
|
|
|
#if __has_include("patches.h")
|
|
#include "patches.h"
|
|
#endif
|
|
|
|
enum { BTN_OFF, BTN_B, BTN_L3 };
|
|
static const char *const button_names[] = {"off", "b", "l3"};
|
|
static const uint64_t button_bits[] = {0, 1u << 1, 1u << 4}; // HidNpadButton bits
|
|
#define BUTTON_ZL (1u << 8)
|
|
|
|
#define DASH_INPUT 0x10u
|
|
#define LINK_VTABLE 0x4b4dfc0ull // main offset: Link's actor class
|
|
#define GAMEPLAY_POLLS 8 // the button is kept from the game while Link's update ran within this many polls
|
|
// Standing still this long ends a toggled sprint that has moved, or this long after a press while standing, which has
|
|
// not moved yet. In 60 Hz frames: above 60 fps Link updates once a rendered frame, each step frame_step_frames() of
|
|
// them, so counting updates would end the sprint on any short stop at 240 fps (four updates are 17 ms there).
|
|
#define WAIT_STOP_FRAMES 4.0
|
|
#define WAIT_START_FRAMES 90.0
|
|
|
|
// What Link's state does to a sprint: MOVE keeps it (and is when the bottom button sprints), KEEP keeps it,
|
|
// WAIT ends it after a few frames, STOP ends it at once. A state not listed stops it too.
|
|
enum { MOVE, KEEP, WAIT, STOP };
|
|
static const struct { int32_t id; uint8_t kind; } states[] = {
|
|
{1, MOVE}, // Walk (also running)
|
|
{13, MOVE}, // Jump
|
|
{32, MOVE}, // Land
|
|
{33, MOVE}, // Fall
|
|
{68, MOVE}, // Clamber
|
|
{253, KEEP}, // StepOn
|
|
{35, KEEP}, // Slide
|
|
{36, KEEP}, // SlideLand
|
|
{40, WAIT}, // Wait
|
|
};
|
|
|
|
static _Atomic uint64_t pressed; // the button's presses since Link's last update (sprint_pad -> sprint_link)
|
|
static _Atomic int held; // the button held at the last poll
|
|
static _Atomic int zl_held; // ZL (targeting) held at the last poll
|
|
static _Atomic int polls = GAMEPLAY_POLLS; // polls since Link's last update (none yet: not gameplay)
|
|
static _Atomic int moving; // Link's state was a MOVE one at his last update
|
|
static _Atomic int active; // the sprint is on: the dash input goes in
|
|
static _Atomic int latched; // the press now held began in gameplay (and, for B, while Link moved): kept from the game
|
|
static int moved; // sprint_link only: the sprint has moved since it started
|
|
static double wait_frames; // sprint_link only: 60 Hz frames stood still
|
|
static int last_button = -1; // sprint_link only: the button setting at the last update
|
|
|
|
static int setting_button(void) {
|
|
const char *v = cfg_value("NX2NAVI_SPRINT_BUTTON");
|
|
for (int i = 0; i < (int)(sizeof button_names / sizeof *button_names); i++)
|
|
if (v && !strcmp(v, button_names[i])) return i;
|
|
return BTN_OFF;
|
|
}
|
|
|
|
static int setting_hold(void) {
|
|
const char *v = cfg_value("NX2NAVI_SPRINT_MODE");
|
|
return v && !strcmp(v, "hold");
|
|
}
|
|
|
|
// Guest reads through Link's pointers: a stale pointer in the chain reads 0 instead of faulting.
|
|
static uint64_t ld64(uint64_t a) { return a && mem_readable(a, 8) ? *(const uint64_t *)(uintptr_t)a : 0; }
|
|
static int32_t ld32(uint64_t a) { return a && mem_readable(a, 4) ? *(const int32_t *)(uintptr_t)a : 0; }
|
|
|
|
// 1 when x19 is the Link the player controls. Link's class has one more instance the player does not control: the
|
|
// game's actor manager ([[main+0x4c7e0d0]], class main+0x4bdfe68) names the controlled one at +0x470 (class
|
|
// main+0x4b49698), whose +0x18 must be Link's own +0x18.
|
|
static int is_link(uint64_t link) {
|
|
if (!link || ld64(link) != MOD_MAIN_BASE + LINK_VTABLE) return 0;
|
|
uint64_t slot = ld64(MOD_MAIN_BASE + 0x4c7e0d0), mgr = slot ? ld64(slot) : 0;
|
|
if (!mgr || ld64(mgr) != MOD_MAIN_BASE + 0x4bdfe68) return 1;
|
|
uint64_t ctl = ld64(mgr + 0x470);
|
|
if (!ctl || ld64(ctl) != MOD_MAIN_BASE + 0x4b49698) return 1;
|
|
return ld64(ctl + 0x18) == ld64(link + 0x18);
|
|
}
|
|
|
|
static int state_kind(uint64_t link) {
|
|
uint64_t p = ld64(link + 0x18);
|
|
if (p) p = ld64(p + 0x270);
|
|
if (p) p = ld64(p + 0x188);
|
|
if (p) p = ld64(p + 0x1c0);
|
|
if (!p) return STOP;
|
|
int32_t id = ld32(p + 0x28);
|
|
if (id == 0x11a) id = ld32(p + 0x4c);
|
|
for (size_t i = 0; i < sizeof states / sizeof *states; i++)
|
|
if (states[i].id == id) return states[i].kind;
|
|
return STOP;
|
|
}
|
|
|
|
// The set runs only while a button is chosen, so with the sprint button off its sites cost nothing. Whether it may run
|
|
// at all is read once, after the start: NX2NAVI_SPRINT_BUTTON_PATCH=0 or a mod whose code patches the same sites (mods.c
|
|
// mods_exefs_switch) leaves it off then, and switching it on later must not undo that.
|
|
static _Atomic int allowed = -1;
|
|
static int sprint_set(void) { return rt_patch_set("sprint"); }
|
|
int sprint_available(void) {
|
|
int a = atomic_load(&allowed);
|
|
if (a < 0) {
|
|
a = rt_patch_enabled(sprint_set());
|
|
atomic_store(&allowed, a);
|
|
}
|
|
return a;
|
|
}
|
|
|
|
void sprint_pad(uint64_t *buttons) {
|
|
if (!sprint_available()) return;
|
|
int b = setting_button(), set = sprint_set();
|
|
if (rt_patch_enabled(set) != (b != BTN_OFF)) rt_patch_switch(set, b != BTN_OFF);
|
|
if (b == BTN_OFF) { // the sites stop calling in: nothing may stay on for when a button is chosen again
|
|
atomic_store(&active, 0), atomic_store(&pressed, 0), atomic_store(&latched, 0);
|
|
return;
|
|
}
|
|
static uint64_t last;
|
|
uint64_t bit = button_bits[b], now = *buttons;
|
|
int n = atomic_load(&polls), gameplay = n < GAMEPLAY_POLLS;
|
|
if (n <= GAMEPLAY_POLLS) atomic_store(&polls, n + 1);
|
|
// A press is decided at its start, for its whole length: kept from the game, and the sprint's, when it begins in
|
|
// gameplay (B: while Link moves); else the game's alone, and no sprint's.
|
|
if (now & ~last & bit) {
|
|
int mine = gameplay && (b != BTN_B || atomic_load(&moving));
|
|
atomic_store(&latched, mine);
|
|
if (mine) atomic_fetch_or(&pressed, bit);
|
|
} else if (!(now & bit)) {
|
|
atomic_store(&latched, 0);
|
|
}
|
|
last = now;
|
|
atomic_store(&held, (now & bit) != 0);
|
|
atomic_store(&zl_held, (now & BUTTON_ZL) != 0);
|
|
if (atomic_load(&latched) && gameplay) *buttons &= ~bit;
|
|
}
|
|
|
|
// The sprint for this update of Link, from his state and the button's presses: 1 when it is on.
|
|
int sprint_step(int kind, int button, int hold) {
|
|
if (button != last_button) last_button = button, atomic_store(&active, 0), atomic_store(&pressed, 0);
|
|
int press = (atomic_exchange(&pressed, 0) & button_bits[button]) != 0, on = atomic_load(&active);
|
|
if (atomic_load(&zl_held)) kind = STOP;
|
|
atomic_store(&moving, kind == MOVE);
|
|
if (button == BTN_OFF) on = 0;
|
|
else if (hold) on = atomic_load(&held) && atomic_load(&latched) && kind != STOP;
|
|
else {
|
|
if (press) {
|
|
if (on) on = 0;
|
|
else if (kind != STOP && (button != BTN_B || kind == MOVE)) on = 1, moved = kind == MOVE, wait_frames = 0;
|
|
} else if (on) {
|
|
if (kind == STOP) on = 0;
|
|
else if (kind == WAIT) {
|
|
wait_frames += frame_step_frames();
|
|
if (wait_frames > (moved ? WAIT_STOP_FRAMES : WAIT_START_FRAMES)) on = 0;
|
|
} else {
|
|
wait_frames = 0;
|
|
moved |= kind == MOVE;
|
|
}
|
|
}
|
|
}
|
|
atomic_store(&active, on);
|
|
return on;
|
|
}
|
|
|
|
// main+0x4ae798, after ldr x10, [x10]: x10 holds Link's input flags for the sprint's start and keep tests.
|
|
static void sprint_link(Cpu *c) {
|
|
if (!is_link(c->x[19])) return;
|
|
atomic_store(&polls, 0);
|
|
if (sprint_step(state_kind(c->x[19]), setting_button(), setting_hold())) c->x[10] |= DASH_INPUT;
|
|
}
|
|
|
|
// main+0xc07f3c, after ldrb w8, [x8]: the same flags' low byte for the stance and camera update.
|
|
static void sprint_stance(Cpu *c) {
|
|
if (atomic_load(&active) && is_link(c->x[19])) c->x[8] |= DASH_INPUT;
|
|
}
|
|
|
|
#ifdef RT_PATCH_PAGE
|
|
__attribute__((constructor)) static void sprint_register(void) {
|
|
if (!RT_V111) return; // the set's sites are in 1.1.1's main only
|
|
rt_register_host(RT_PATCH_PAGE + 0xf40, sprint_link);
|
|
rt_register_host(RT_PATCH_PAGE + 0xf30, sprint_stance);
|
|
}
|
|
#endif
|