Files
NX2navi/runtime/patches.c
chrg6 328554e7c6 Run the sound manager's job at 60 Hz with whole steps above 60 fps.
The job (main+0x415300) hands the time manager's step to the sound system in its phase 0 and runs the sound objects'
and the system's updates in phases 1 and 2. With the set ostep that step is a fraction of a frame above 60 fps, and fed
fractions the game has been reported to keep sounds held after an area unloaded until no voice was free, new sounds
and then the music going silent until a restart. The new set osound (sound_steps, on by default) calls the page's
+0xf10 after the job's prologue: phase 0 adds the step to what the job is owed and runs once a whole step has built
up, phases 1 and 2 follow that choice, and a job that does not run gets phase 3, which returns through its own
epilogue. After phase 0's load of the step the page's +0xf00 makes a fraction one whole step. At 60 fps and below
the job runs every frame with the game's step. Both sites were checked against the module; frame_step_check covers
the gate at 60, 30, 144 and 240 fps. Needs the game code generated again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-13 05:54:58 -05:00

103 lines
4.0 KiB
C

// Code patch sets (recomp/patches.py, gen.py --patches). The generator lifts both the original and the replacement
// instructions of every patched site, and rt_patch_on[set] picks one when the site runs, so a set is switched at start
// without touching code, and also while the game runs (rt_patch_switch: the frame-limit setting of the menu). The sets,
// their environment switches and defaults, and the patch page come from the generated patches.h; without it (C
// generated without --patches) there are no sets and every site runs the original.
//
// The patch page is the first 4 KiB page after main's image, inside the module window memory.c reserved: replacement
// code reaches it with one adrp. Its
// layout belongs to the sets that use it (frame_rate.c: +0 the 30 Hz ratio, +4 the frame step in seconds, +8 the
// step in 60 Hz ticks).
// Probe modes never call rt_patches_init: every set stays off.
#include "cfg.h"
#include "platform/platform.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "host.h"
#if __has_include("patches.h")
#include "patches.h"
#endif
#ifndef RT_PATCH_COUNT
#define RT_PATCH_COUNT 0
#endif
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); }
// The time of day's site of the set ostep (patches-v111.txt) calls the page's +0xf60, the water renderer's site its
// +0xf50, the surfaces' fixed-step sites of the set owater its +0xf20, and the sound job's two sites of the set osound
// its +0xf10 and +0xf00 (frame_rate.c).
__attribute__((constructor)) static void day_register(void) {
if (!RT_V111) return;
rt_register_host(RT_PATCH_PAGE + 0xf60, frame_rate_day_host);
rt_register_host(RT_PATCH_PAGE + 0xf50, frame_rate_water_host);
rt_register_host(RT_PATCH_PAGE + 0xf20, frame_rate_sim_host);
rt_register_host(RT_PATCH_PAGE + 0xf10, frame_rate_sound_gate_host);
rt_register_host(RT_PATCH_PAGE + 0xf00, frame_rate_sound_step_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},
RT_PATCH_SETS(X)
#undef X
};
static void *page;
#endif
void rt_patches_init(void) {
#if RT_PATCH_COUNT
if (!plat_mem_commit(RT_PATCH_PAGE, 0x1000))
rt_fatal("patches: cannot commit the patch page at %#llx", RT_PATCH_PAGE);
page = (void *)(uintptr_t)RT_PATCH_PAGE;
char list[512];
size_t n = 0;
list[0] = 0;
for (int i = 0; i < RT_PATCH_COUNT; i++) {
const char *e = cfg_get(sets[i].env);
// a set with a row in the settings table takes its default from there; the generated default serves the rest
rt_patch_on[i] = (uint8_t)(cfg_meta(sets[i].env) ? cfg_bool(sets[i].env) : e && *e ? strcmp(e, "0") != 0 : sets[i].on);
n += (size_t)snprintf(list + n, sizeof list - n, "%s%s %s", i ? ", " : "", sets[i].name,
rt_patch_on[i] ? "on" : "off");
if (n >= sizeof list) n = sizeof list - 1;
}
rt_log("patches: %s (page %#llx)", list, RT_PATCH_PAGE);
#endif
}
int rt_patch_set(const char *name) {
#if RT_PATCH_COUNT
for (int i = 0; i < RT_PATCH_COUNT; i++)
if (!strcmp(sets[i].name, name)) return i;
#endif
(void)name;
return -1;
}
int rt_patch_enabled(int set) { return set >= 0 && set < RT_PATCH_COUNT && rt_patch_on[set]; }
void rt_patch_disable(int set) {
if (set >= 0 && set < RT_PATCH_COUNT) rt_patch_on[set] = 0;
}
void rt_patch_switch(int set, int on) {
if (set >= 0 && set < RT_PATCH_COUNT) __atomic_store_n(&rt_patch_on[set], (uint8_t)(on != 0), __ATOMIC_RELAXED);
}
void *rt_patch_page(void) {
#if RT_PATCH_COUNT
return page;
#else
return NULL;
#endif
}