Files
NX2navi/runtime/host.h
chrg6 d3e50328bf Keep movies with their sound at any frame rate: one movie frame per update, and the pacer keeps the movie's slots through a late frame.
The game's movie player adds the time manager's step to an accumulator at each update and advances the movie by whole
pictures of it, at most the player's limit per update, which is one for the Triforce movie. While a movie plays the
game is held at 60 updates a second, but every late frame cost an update, the pacer started its slots again from the
late one, and the steady step's payback of the lost time came as steps over one frame, which the player cannot turn
into more than one picture. So each late frame put the picture further behind the sound, which plays by the clock, and
it never caught up.

While a movie plays the step is now exactly one movie frame, and the pacer keeps the movie's slots through a lateness
of up to a second, presenting the frames after a late one at once until the game is back on them. The updates then
average the movie's rate, and the picture, the event's update count and the game's clock all stay with the sound.
movie_clock = 0 gives the earlier behaviour. The movie log now measures the picture on screen against real time where
the game releases it (the game fetches pictures ahead into a list of its own) and reports it at the end of each movie.
test_hitch = <every>:<ms> makes a frame that much late every so many presents, for test runs.

Measured on the Triforce movie (event Dm_Kokiri_0095, 7,040 pictures, 117 s), one exe with movie_clock = 0 against 1,
and a 70 ms late frame every two seconds (test_hitch = 120:70), as a run with natural hitches had shown a drift of 40 ms
every ten seconds. Drift of the picture on screen behind real time, from the movie's first 10 s to its end:

  fps_limit   before (updates/s, drift at the end)   after (updates/s, drift over the movie)
  unlimited   58.86, 2,187 ms behind                  60.22, flat at 53 to 55 ms
  144         58.86, 2,196 ms behind                  60.04, flat at -11 to -15 ms
  120         58.98, 2,257 ms behind                  60.20, flat at -1 to 0 ms
  60          58.88, 2,220 ms behind                  59.98, flat at -21 to -22 ms

Without the late frames both kept time (unlimited: before flat at 21 ms, after flat at -13 ms). The selftest,
--probe-cfg, --probe-kernel, --probe-movie and tests/frame_step_check.py pass; the check now plays a movie started at
240 fps with late and short frames and needs exactly one frame every update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-11 15:09:16 -05:00

455 lines
39 KiB
C

// Interface of the host runtime: guest memory, modules, kernel, IPC, file system and boot.
//
// Guest addresses are host addresses. Every guest-visible region lies below 2^39 (IPC buffer descriptors carry 39
// address bits). Fixed parts of the guest address map:
// modules MOD_<X>_BASE from layout.h, from 0x70_0000_0000
// kernel region RT_KERNEL_BASE, 256 MiB: TLR, TLS blocks, host objects the guest reads (mem_kernel_alloc)
// host functions RT_HOSTFN_BASE, 1 MiB, never accessible: synthetic addresses of host functions
// The stack, heap and alias regions are placed at start-up (mem_layout).
#pragma once
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "cpu.h"
#include "dispatch.h"
// ---- logging and results
void rt_log(const char *fmt, ...) __attribute__((format(printf, 1, 2))); // "[host] <msg>\n" to the log, one write
// The log is stderr plus, in a game run whose stderr is not a file, logs\nx2navi.log next to nx2navi.exe (main.c log_open).
// rt_logf and rt_log_raw write to both without the prefix and the newline: crash reports, fatal errors, guest output.
void rt_logf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
void rt_vlogf(const char *fmt, va_list ap);
void rt_log_raw(const char *p, size_t n);
const char *rt_log_path(void); // the log file, NULL when this run writes none
void rt_log_sync(void); // writes the queued log lines now; later lines are written as they are made
enum { RT_TRACE_SVC = 1, RT_TRACE_HOSTCALL = 2, RT_TRACE_IPC = 4, RT_TRACE_FS = 8, RT_TRACE_GFX = 16, RT_TRACE_PERF = 32,
RT_TRACE_SCHED = 64, RT_TRACE_AUDIO = 128, RT_TRACE_PRESENT = 256, RT_TRACE_COOP = 512 };
extern unsigned rt_trace; // from NX2NAVI_TRACE=svc,host,ipc,fs,gfx,perf,sched,audio,present,coop (or all)
// prof_win.c: NX2NAVI_PROFILE=<path> sampling profiler; NX2NAVI_TRACE=perf frame timing (waits of the render thread)
// PERF_TEX: texture uploads on the render thread (deswizzle, decode, staging), PERF_ASTC included
enum { PERF_PACE, PERF_FENCE, PERF_DRAIN, PERF_PIPE, PERF_XLATE, PERF_TEX, PERF_ASTC, PERF_N };
void prof_start(void);
void prof_write(void); // idempotent; boot.c calls it before _exit
uint64_t perf_begin(void); // start time of a wait; pair with perf_end
void perf_end(int kind, uint64_t t0); // counts render-thread time only
void perf_replay(uint64_t t0); // replay (NVN -> Vulkan recording) time on any thread, waits inside it included
void perf_replay_thread(void); // the calling thread replays for the render thread: perf_end counts its waits
void perf_main_thread(void); // the calling thread is the guest main thread (NX2NAVI_PROFILE_MAIN)
// Events per 60-frame window: graphics pipelines finished by the background workers, draws skipped while pending,
// first texture uploads and re-uploads after CPU writes, draws skipped while a shader translates in the background,
// draws that waited for their pending pipeline and got it, draws into a target the previous frame did not render that
// waited for their pending shader or pipeline instead of being skipped (vk_exec_draw.c prepare_draw). The rest feed only
// the flight recorder (flight.c): graphics pipelines created on any thread, texture images created, images created at an
// address where a live image of another descriptor already starts, write-backs done and skipped because the CPU
// rewrote the storage after the GPU render (vk_texture.c vk_texture_write_back).
enum { PERF_CNT_PIPE_ASYNC, PERF_CNT_PIPE_SKIP, PERF_CNT_TEX_UP, PERF_CNT_TEX_REUP, PERF_CNT_SHADER_SKIP,
PERF_CNT_PIPE_WAIT, PERF_CNT_ONESHOT_WAIT, PERF_CNT_PIPE_BUILT, PERF_CNT_TEX_NEW, PERF_CNT_TEX_ALIAS,
PERF_CNT_WB, PERF_CNT_WB_SKIP, PERF_CNT_N };
void perf_count(int kind);
// NX2NAVI_TRACE=perf: work on any thread that a frame can wait for besides the render thread's waits above, summed per
// frame and per 60-frame window (perf.c perf_mark_add): RomFS reads, host zstd decodes, pipeline creates (vk_pipeline.c
// pipe_create_in), guest memory maps (heap size, MapPhysicalMemory), NVN memory pool initialize and finalize
// (vk_memory.c vk_pool_init, vk_pool_fini), device memory VMA allocates (blocks and dedicated memory: count and
// bytes, no time), and DLSS and DLSS frame generation feature creates (vk_dlaa.c). perf_mark_begin returns 0 while the trace is off, so a normal run pays one branch per site.
enum { PERF_M_FILE, PERF_M_ZSTD, PERF_M_PIPE, PERF_M_GMEM, PERF_M_POOL, PERF_M_DEVMEM, PERF_M_NGX, PERF_M_N };
uint64_t perf_mark_begin(void);
void perf_mark_end(int kind, uint64_t t0, uint64_t bytes); // nothing when t0 is 0
void perf_mark_add(int kind, uint64_t ns, uint64_t bytes); // nothing while the trace is off
// NX2NAVI_TRACE=perf segments and scenes (perf.c): perf_segment ends the running segment of presents at the next present
// with a line of its frame times (mean, 1% low, worst), the work marks above, process memory and VRAM, then the memory
// snapshot, and starts one named name (the input script's MARK lines, input.c). perf_scene notes an area's load
// (romfs.c, the area's static placement file) and takes the memory snapshot once no area has loaded for 10 s.
void perf_segment(const char *name);
void perf_scene(const char *name);
// NX2NAVI_TRACE=perf "mem: sizes" lines (perf.c sizes_log): the entries and bytes of the caches, pools and tables no
// other line gives, every 600 presents and with each memory snapshot. Each subsystem's *_sizes function (weak no-ops
// in perf.c) adds its own with perf_size, taking its locks briefly on the present thread. count or bytes UINT64_MAX:
// not known; limit 0: none.
typedef struct { char line[1800]; int n; uint64_t frame; const char *group; } PerfSizes;
void perf_size(PerfSizes *s, const char *name, uint64_t count, uint64_t limit, uint64_t bytes);
#define PERF_SIZE_NONE UINT64_MAX
void mem_sizes(PerfSizes *s); // memory.c
void kern_sizes(PerfSizes *s); // kernel.c
void setjmp_sizes(PerfSizes *s); // guest_setjmp.c
void fs_host_sizes(PerfSizes *s); // fs_host.c
void romfs_sizes(PerfSizes *s); // romfs.c
void zstd_sizes(PerfSizes *s); // ov_zstd.c
void cfg_sizes(PerfSizes *s); // cfg.c
void vk_mem_sizes(PerfSizes *s); // vk_memory.c
void vk_gpa_sizes(PerfSizes *s); // vk_gpa.c
void vk_texture_sizes(PerfSizes *s); // vk_texture.c
void vk_exec_sizes(PerfSizes *s); // vk_exec_draw.c
void vk_sync_sizes(PerfSizes *s); // vk_sync.c
void vk_usnap_sizes(PerfSizes *s); // vk_usnap.c
void nvn_cmd_sizes(PerfSizes *s); // nvn_api_cmd.c
void vk_pipeline_sizes(PerfSizes *s); // vk_pipeline.c
void gfx_shader_sizes(PerfSizes *s); // gfx_shader.c
// NX2NAVI_REPLAY_TIME=1: how long the replay spends in each kind of op (perf.c perf_replay_op reports it with the
// frame-time summary). Draws and dispatches include their bindings and the vkCmd calls they record; binds are the state
// and resource binds between them; xfer is copies and clears.
enum { PERF_RP_DRAW, PERF_RP_BIND, PERF_RP_XFER, PERF_RP_OTHER, PERF_RP_N };
void perf_replay_op(int cat, uint64_t ns);
void perf_replay_op_at(int cat, uint32_t op, uint64_t ns);
void perf_replay_cpu(uint64_t ns); // the replaying thread's CPU time over one submission's replay (vk_sync.c) // as perf_replay_op, also kept per frame for slow frames
void perf_lock_timing_here(void);
int perf_lock_holds_on(void); // NX2NAVI_LOCK_TIME
void perf_lock_hold(void *site, uint64_t ns); // a lock held ns from site (vk_texture.c, holds over 1 ms) // NX2NAVI_LOCK_TIME: this thread's lock waits go into the frame-time summary
extern void (*perf_rec_waits)(uint64_t *full_ns, uint64_t *drain_ns); // set by the back end (vk_rec.c totals)
void perf_present(uint64_t frame);
void perf_cpu_start(void); // the process's logical processors (NX2NAVI_CACHE_DIE, NX2NAVI_ONE_PER_CORE), at start
void perf_summary_flush(void); // prints the frame-time summary of the running period (exit); the 60 s lines come from perf_present
void gfx_exit_log(void); // back end's lines for the exit summary (weak no-op in prof_win.c)
void perf_gpu_wait(uint64_t t0); // a wait for a GPU fence ended (any thread; the render thread's also counts as PERF_FENCE)
// GPU side of the 60-frame perf line, filled by the graphics back end when it set perf_gpu_stats. gpu_ns: GPU busy time
// since the previous call (timestamp queries, overlaps counted once), have_gpu 0 when the device has no timestamps.
typedef struct {
int have_gpu, have_vram, vram_exact;
uint64_t gpu_ns, gpu_parts, gpu_missed; // busy time, submission parts timed, parts left untimed
uint64_t vram_used, vram_budget;
int have_idle; // calibrated timestamps: the idle split below is filled
uint64_t idle_ns, starved_ns, queued_ns; // GPU idle between timed parts; of it, before the part was submitted; submit to start
uint64_t idle_gaps, max_gap_ns; // gaps over 0.5 ms; the longest
uint64_t mirror_ns; // of gpu_ns, the copies into VRAM mirrors (vk_sync.c ts_submitted)
} PerfGpu;
extern void (*perf_gpu_stats)(PerfGpu *out);
// The performance overlay (perfmon.c): mode 0 off, 1 fps, 2 full. While it is on, perf_present hands every frame to
// perfmon_frame; full also takes the event counts (perf_count) and the GPU timestamps as NX2NAVI_TRACE=perf does.
void perf_set_overlay(int mode);
int perf_gpu_timing(void); // GPU timestamps wanted: NX2NAVI_TRACE=perf, the full overlay, or a
// feature that asked (perf_gpu_timing_want)
void perf_gpu_timing_want(int on); // dynres.c: GPU timestamps while dynamic resolution runs
void perf_gpu_busy(uint64_t ns); // GPU busy time of a timed submission part (vk_sync.c, any thread)
uint64_t perf_gpu_busy_total(void); // its running total (the overlay's GPU time per frame)
#define RT_RESULT(module, desc) ((uint32_t)(module) | ((uint32_t)(desc) << 9))
void rt_sha256(const void *p, size_t n, uint8_t out[32]); // BCrypt; fatal if the provider fails
// ---- guest address map (fixed part)
#define RT_ADDRESS_LIMIT (1ull << 39)
#define RT_KERNEL_BASE 0x7010000000ull
#define RT_KERNEL_SIZE 0x10000000ull
#define RT_HOSTFN_BASE 0x7020000000ull
#define RT_HOSTFN_SIZE 0x100000ull
// ---- AAPCS64 argument access for host functions (section 2.2)
static inline uint64_t arg_x(const Cpu *c, int i) { return i < 8 ? c->x[i] : LD64(c->sp + 8 * (uint64_t)(i - 8)); }
static inline void *arg_p(const Cpu *c, int i) { return (void *)(uintptr_t)arg_x(c, i); }
static inline float arg_s(const Cpu *c, int i) { uint32_t b = (uint32_t)c->v[i]; float f; memcpy(&f, &b, 4); return f; }
static inline double arg_d(const Cpu *c, int i) { uint64_t b = (uint64_t)c->v[i]; double d; memcpy(&d, &b, 8); return d; }
static inline void ret_x(Cpu *c, uint64_t v) { c->x[0] = v; }
static inline void ret_s(Cpu *c, float f) { uint32_t b; memcpy(&b, &f, 4); c->v[0] = b; }
static inline void ret_d(Cpu *c, double d) { uint64_t b; memcpy(&b, &d, 8); c->v[0] = b; }
// ---- host functions (hostfn.c)
// Every host function is a GuestFn at a synthetic address RT_HOSTFN_BASE + 16*i. It runs on the calling guest thread,
// may clobber x0-x18, v0-v7, v16-v31, must preserve x19-x29, sp, v8-v15, tpidr, and returns to the guest BL site with
// a C return. On entry c->pc holds the function's own synthetic address.
typedef struct { const char *name; GuestFn fn; } RtHostEntry;
#define RT_CAT2(a, b) a##b
#define RT_CAT(a, b) RT_CAT2(a, b)
// File-scope declarations; fn must be declared before. The loader binds them by symbol name. The entries of every
// object file meet in one section per kind, which hostfn.c walks: on PE/COFF ".ovr$m", which the linker sorts between
// hostfn.c's ".ovr$a" and ".ovr$z" markers; on ELF "nx2navi_ovr", which the linker brackets with __start_nx2navi_ovr and
// __stop_nx2navi_ovr.
#ifdef _WIN32
#define RT_HOST_SECTION(kind) section("." #kind "$m")
#else
#define RT_HOST_SECTION(kind) section("nx2navi_" #kind)
#endif
#define HOST_OVERRIDE(sym, fn) /* replaces sym in every GOT/GLOB_DAT/ABS64 of every module */ \
__attribute__((used, RT_HOST_SECTION(ovr))) static const RtHostEntry RT_CAT(rt_ovr_, __COUNTER__) = {sym, fn}
#define HOST_OBSERVER(sym, fn) /* JUMP_SLOT only; fn calls rt_original(c) */ \
__attribute__((used, RT_HOST_SECTION(obs))) static const RtHostEntry RT_CAT(rt_obs_, __COUNTER__) = {sym, fn}
uint64_t hostfn_register(const char *name, GuestFn fn); // synthetic address; before hostfn_seal only. The same
// (name, fn) pair returns the same address
GuestFn hostfn_override(const char *name); // NULL if none
GuestFn hostfn_observer(const char *name); // NULL if none
void hostfn_add_override(const char *name, GuestFn fn); // an override outside the linker table (probes); before
// ldr_load
const char *hostfn_name(uint64_t addr); // name registered at a synthetic address, NULL if none
void hostfn_seal(void); // build the dispatch table (rt_dispatch_init over every
// linked module); idempotent; registration ends here
uint64_t hostfn_calls(void); // host-function calls so far, all threads
void hostfn_report(void); // call count per host function to stderr, busiest first
int hostfn_table_size(int observers); // linked HOST_OVERRIDE (0) / HOST_OBSERVER (1) entries
const char *hostfn_table_name(int observers, int i); // symbol of linked entry i, NULL past the end
uint64_t rt_original(Cpu *c); // inside an observer: call the guest definition with the
// current x0..x7, return x0. Uses c->pc, so call it before
// anything that changes c->pc (rt_call restores it)
uint64_t rt_call(Cpu *c, uint64_t fn, int n, ...); // host -> guest, section 2.3. n <= 8 arguments, each read
// as uint64_t: cast every argument. Seals the dispatch
// table on first use. Sets x30 = RT_RET_HOST, restores
// x30 and pc, returns x0. Does not touch sp
#define RT_RET_HOST 0x70200ff000ull // x30 sentinel inside the host-function window; a jump to
// it is a named fatal
// ---- guest memory (memory.c)
typedef struct { uint64_t base, size; } RtRange;
typedef struct { RtRange aslr, stack, heap, alias, kernel, hostfn; uint64_t pool; } RtGuestLayout;
typedef struct { uint64_t base, size; uint32_t state, attr, perm, ipc, device, pad; } RtMemoryInfo; // Horizon layout
void mem_init(void); // reserve every region; on a collision re-run the process
// (up to 20 times, NX2NAVI_REEXEC), then fatal
const RtGuestLayout *mem_layout(void);
int mem_start_note(int probe); // a game's start: a warning line when the free commit is
// below the game's need (mem_need.h); 1 when it is
void mem_guest_census(uint64_t frame); // NX2NAVI_TRACE=perf: committed bytes per guest region
void mem_lazy_heap_report(uint64_t frame); // NX2NAVI_TRACE=perf: the lazy heap's faults since the last line
uint32_t mem_set_heap_size(uint64_t size, uint64_t *addr); // 0x01
uint32_t mem_set_permission(uint64_t a, uint64_t n, uint32_t perm); // 0x02
uint32_t mem_set_attribute(uint64_t a, uint64_t n, uint32_t mask, uint32_t at); // 0x03
uint32_t mem_map(uint64_t dst, uint64_t src, uint64_t n); // 0x04
uint32_t mem_unmap(uint64_t dst, uint64_t src, uint64_t n); // 0x05
uint32_t mem_query(uint64_t a, RtMemoryInfo *out); // 0x06
int mem_readable(uint64_t a, uint64_t n); // every page readable
uint32_t mem_map_gen(void); // changes with everything mem_readable answers from (mapping, heap, permissions)
int mem_read_hold(uint64_t a, uint64_t n); // mem_readable that keeps the range mapped until mem_read_release
void mem_read_release(void);
uint32_t mem_map_physical(uint64_t a, uint64_t n); // 0x2C
uint32_t mem_unmap_physical(uint64_t a, uint64_t n); // 0x2D
int mem_info(uint32_t id, uint64_t *out); // GetInfo memory ids 2-7, 12-17, 21, 22, 28; 0 if not a memory id
void *mem_kernel_alloc(uint64_t size, uint64_t align); // zeroed, guest-visible, in the kernel region; align <= 64 KiB
uint64_t mem_written_pages(uint64_t addr, uint64_t size, uint64_t *pages, uint64_t max);
// GetWriteWatch(RESET): fills pages[] (4 KiB addresses)
// with the pages of [addr, addr+size) written since the
// last call, returns their count (at most max; a page
// past max stays reported next time). The range must lie
// inside the stack/heap block or the alias region
// (write watch); otherwise returns UINT64_MAX
uint64_t mem_written_peek(uint64_t addr, uint64_t size, uint64_t *pages, uint64_t max);
// the same without the reset: the bits stay for the next
// mem_written_pages (verify checks only)
int mem_freeze(uint64_t a); // makes the 2 MiB region at a (2 MiB aligned, watched,
// committed PAGE_READWRITE) read-only until a write or
// mem_thaw thaws it; 1 frozen (or already), 0 refused
int mem_frozen(uint64_t a); // the 2 MiB region of a is frozen: no page in it changed
// since its freeze
void mem_thaw(uint64_t a, uint64_t n); // makes the frozen regions over [a, a+n) writable again;
// before a kernel write (file read) into guest memory;
// also commits pages the lazy heap left reserved
void mem_kernel_access(uint64_t a, uint64_t n); // NX2NAVI_HEAP_LAZY: commits the reserved heap pages of
// [a, a+n) before the kernel reads them (a file write)
uint64_t mem_untouched_run(uint64_t a, uint64_t end); // NX2NAVI_HEAP_LAZY: the end of the run of heap pages at a
// that were never committed (a scan steps over it), else 0
int mem_host_readable(uint64_t a, uint64_t n); // plat_mem_readable that first commits the untouched
// pages of the lazy heap in [a, a+n), which read zero
void mem_freeze_stats(uint64_t *frozen, uint64_t *freezes, uint64_t *fault_thaws, uint64_t *other_thaws,
uint64_t *hot_holds); // hot_holds: quick thaws that held their region thawed (memory.c "Hot regions")
// NX2NAVI_WB_PROBE=1 (memory.c, "Probed regions"): who reads what the GPU wrote back into guest memory.
// NX2NAVI_WB_LAZY (memory.c, "Lazy write-back"): pages the GPU wrote that come down only when the CPU touches them.
int mem_lazy_enable(void (*fetch)(uint64_t h0, uint64_t h1, uint64_t off, uint64_t size),
int (*clean)(uint64_t lo, uint64_t hi, void *ctx));
uint64_t mem_lazy_arm(uint64_t a, uint64_t n, uint64_t h0, uint64_t h1, uint64_t off, void *ctx);
int mem_lazy_skip(uint64_t a, uint64_t n, uint64_t h0, int64_t delta, uint64_t out[][2], int max);
int mem_lazy_armed(uint64_t a, uint64_t n);
void mem_lazy_window(int open);
void mem_lazy_release(uint64_t a, uint64_t n);
void mem_lazy_report(uint64_t frame, uint64_t skipped);
void mem_lazy_note(char *out, size_t n); // its state as a phrase, without a lock (a fatal error's line)
int mem_lazy_pick(uint64_t *addr, int max); // test: addresses of armed pages
int mem_probe(uint64_t a, uint64_t n); // the 2 MiB regions inside [a, a+n) fault on their next access
int mem_probing(void); // the probe is on
void mem_probe_report(uint64_t frame); // logs the accesses since the last report
// regions frozen now; freezes and thaws since the last call
void mem_kernel_free(void *p, uint64_t size); // TLR/TLS reuse after thread exit
// ---- modules (loader.c)
typedef struct {
const char *name; uint64_t base, size, text_end, data_off; // text_end, data_off: offsets from base
uint64_t init, init_array, init_array_size, ctor_flag; // absolute guest addresses; init_array_size in bytes
} RtLoaded;
void ldr_load(const char *exefs_dir); // load, relocate (overrides applied), seal; once
int ldr_count(void);
const RtLoaded *ldr_module(int i); // order: main, subsdk0, sdk
uint64_t ldr_symbol(const char *name); // exported definition, 0 if none
uint64_t ldr_plt_stub(const char *module, const char *name); // PLT stub whose GOT slot holds name, 0 if none
const char *ldr_describe(uint64_t a, char *buf, int n); // "main+0x1234 (symbol+0x10)", "host <name>", or the address
uint8_t *ldr_read_verified(const char *dir, const char *file, size_t *len); // whole file, SHA-256 checked against
// modules.c (fatal on mismatch); free() it
// Written by tools/build_game.py into build/<version>/modules.c.
typedef struct { const char *file, *sha256; } RtFileHash;
extern const RtFileHash rt_exefs_sha256[]; // tools/data/expected-<version>.json "exefs_sha256"
extern const int rt_exefs_sha256_count;
extern const char rt_default_exefs[], rt_default_romfs[], rt_default_profile[];
extern const unsigned long long rt_app_pool_bytes; // the title's application memory pool (GetInfo 6)
extern const int rt_nvn_api_version[2]; // NVN_DEVICE_INFO_API_{MAJOR,MINOR}_VERSION the title requires
extern const int rt_game_version; // the game build: 111 (1.1.1, make_game.py), 110 (1.1.0), 121 (1.2.1) or 143 (1.4.3)
extern const char rt_port_version[]; // VERSION at the build's root: 0.1.0, 0.1.0+973c626 in a release
#define RT_V110 (rt_game_version == 110) // 1.1.0: its own boot constants and data slots
#define RT_V111 (rt_game_version == 111) // 1.1.1: its own boot constants (boot.c)
#define RT_V143 (rt_game_version >= 143)
// ---- kernel (kernel.c)
typedef struct RtThread RtThread;
Cpu *kern_cpu(void); // current guest thread's Cpu, NULL on host threads
void fs_save_crash_wait(void); // fs_host.c: the crash report's and rt_fatal's bounded wait for a save Commit under way
void kern_free_cores_note(void); // for crash reports: logs that free_cores = 1 lifted the
// virtual core gate, nothing when the gate is on
uint32_t kern_main_thread(Cpu **cpu); // main thread object and Cpu for the calling host thread
// (main.npdm: 1 MiB stack at the top of the stack region,
// priority 44, core 0); on the main thread: its handle
void kern_main_stack(uint64_t *base, uint64_t *size); // the main thread's stack, size 0 before kern_main_thread;
// svc.c reports it to QueryMemory, memory.c does not know it
void kern_run_on_guest_main(void (*fn)(Cpu *c)); // start the guest main host thread, run fn there, join
int kern_core_away(void); // give the virtual core up around a blocking host wait
void kern_core_back(int *away);
void kern_checkpoint(void); // preemption point, at every SVC and indirect call
// A preempt request is pending for the thread (Cpu.thread): its RtThread starts with the flag (kernel.c).
static inline int kern_preempt_pending(const void *thread) { return __atomic_load_n((const int *)thread, __ATOMIC_RELAXED); }
// Set once before guest threads start (NX2NAVI_CORE_DIAG, NX2NAVI_ASYNC_PREEMPT): host calls (hostfn.c) and SVCs (svc.c) count their depth in
// the thread's second int, so the kernel knows whether a core owner runs game code at the top level.
extern int kern_host_track;
static inline int *kern_host_depth(void *thread) { return (int *)thread + 1; }
void kern_host_left(void *thread); // back at depth 0 with a preempt request pending
int kern_core_diag_on(void); // NX2NAVI_CORE_DIAG
void kern_core_diag_frame(uint64_t frame, uint64_t interval_ns); // perf_present, every present (also logs the
// asynchronous preemption counters)
void kern_sched_report(uint64_t frame); // NX2NAVI_TRACE=sched: per-thread time split, on present
uint32_t kern_handle_new(int kind, void *obj); // kind: KERN_SESSION or KERN_TMEM (ipc.c)
void *kern_handle_get(uint32_t h, int kind);
void kern_handle_close(uint32_t h); // CloseHandle from the host side, any thread; fatal on a
// bad handle
enum { KERN_SESSION = 4, KERN_TMEM = 5 };
uint32_t kern_event_new(void); // writable handle
uint32_t kern_event_readable(uint32_t writable);
void kern_event_signal(uint32_t writable);
void kern_event_clear(uint32_t writable); // server-side clear (e.g. a drained message queue)
void kern_bind_system_event(Cpu *c, uint64_t ev, int autoclear); // guest thread only
void kern_signal_system_event(uint64_t ev); // any thread
void kern_clear_system_event(uint64_t ev); // any thread; a bound, live event only
int kern_svc(Cpu *c, uint32_t imm); // thread/sync SVCs; 1 if handled
uint64_t kern_now_ns(void);
extern uint64_t rt_guest_start_ns; // kern_now_ns when the guest started (boot.c), 0 before
typedef struct { uint64_t svcs, threads, host_calls; } RtStats;
RtStats rt_stats(void); // host_calls: hostfn_calls()
// ---- IPC (ipc.c)
int ipc_svc(Cpu *c, uint32_t imm); // 0x15, 0x1F, 0x21, 0x22; 1 if handled
void ipc_handle_closed(int kind, void *obj); // CloseHandle on a KERN_SESSION/KERN_TMEM handle
// ---- file system (romfs.c / fs_host.c)
void fs_init(void); // idempotent; paths from rt_config()
// ---- account (ov_system.c)
void account_uid(uint64_t out[2]); // the local user's nn::account::Uid {lo, hi}, persistent in
// <profile>/account.uid; fs_host.c names save data after it
// ---- movies (ov_movie.c)
void movie_tick(Cpu *c); // at every present, on the presenting guest thread: hands
// decoded movie frames over when due, signals their events
// ---- camera speed (camera_speed.c): NX2NAVI_STICK_SENS
float cam_speed_wanted(void); // the factor NX2NAVI_STICK_SENS asks for, 0.1 to 3
float cam_speed_applied(void); // the factor in effect: 1 until romfs.c serves the pack
int cam_pack_build(const void *in, size_t n, const void *zsdic, size_t zsdic_n, void **out, size_t *out_n);
// Pack/Actor/PlayerCamera.pack.zs with the camera speeds
// multiplied, malloc'd; 0 = factor 1 or cannot (logged)
void cam_pack_opened(void); // romfs.c: the game opened the camera's pack
void cam_speed_tick(void); // every present: a changed setting goes into the game's memory
void cam_pack_served(void); // romfs.c serves the multiplied pack
// ---- game resolution (resolution.c): NX2NAVI_OUTPUT_RES, NX2NAVI_RENDER_SCALE
uint64_t res_heap_extra_bytes(void); // added to the application pool (mem_init), 2 MiB steps
uint64_t res_heap_extra_kib(void); // added to the HeapDef texture heap and its parent; 0 = none
int res_heapdef_build(const void *in, size_t n, void **out, size_t *out_n); // the HeapDef table (.byml.zs) with
// the grown heaps, malloc'd; 0 = cannot (logged)
void res_heapdef_served(void); // romfs.c serves the grown table
void wide_init(void);
void wide_present(void); // each present: DLSS's render scale (res_fixed) // after rt_patches_init: widescreen.c, NX2NAVI_GAME_ASPECT
int wide_available(void); // the generated C has the widescreen sets
void wide_picture(unsigned *w, unsigned *h); // the picture the game draws (its scene is never larger)
uint64_t wide_heap_extra_kib(void); // the texture heap's growth for the widescreen picture
void res_init(void); // after rt_patches_init: the patch page and the sets
float res_dlss_lod_bias(void); // NX2NAVI_DLSS upscaling: texture LOD bias (<= 0)
int res_dlss_mode(const char *mode); // NX2NAVI_DLSS changed while running: new scene size
void dynres_frame(void); // dynamic resolution: one presented frame (vk_backend.c)
const char *dynres_override(void); // the DLSS mode dynamic resolution uses now, NULL: the setting's
void res_fn_hit(uint64_t addr, uint64_t x0); // main.c fnprof_hit: catches the renderer at start
void res_present(Cpu *c); // nvnQueuePresentTexture: applies a pending scene size
void res_sizes(unsigned out[2], unsigned scene[2]); // the output and scene sizes in effect (the game's own
// until res_init switched the sets on)
int res_adjustable(void); // the build has the output and scene patch sets
int res_is_16x9(unsigned w, unsigned h); // within 1% of 16:9 (the test res_init and romfs.c share)
int res_wants_wide_overlay(const unsigned out[2]); // not 16:9 on a game version with the camera aspect patch
void res_wanted(unsigned out[2]); // the output size NX2NAVI_OUTPUT_RES asks for
void res_fov_live(void); // NX2NAVI_FOV to the camera (set fov); on a settings change
// ---- code patches (patches.c) and the frame step (frame_rate.c)
void rt_patches_init(void); // after ldr_load: switches from the environment, the patch
// page committed; logs the sets
int rt_patch_set(const char *name); // index of a set of generated patches.h, -1 if absent
int rt_patch_enabled(int set); // the set is on (0 for -1)
void rt_patch_disable(int set); // before the guest starts: a set needs another that is off
void rt_patch_switch(int set, int on); // at run time (live settings): each site takes the new code
// the next time it runs
void *rt_patch_page(void); // the page the patched code reads (NULL without the table)
void frame_step_init(void); // after rt_patches_init: the real-time step's first value
int frame_step_realtime(void); // the real-time step is on: presents ignore the interval
uint64_t frame_step_cap_ns(void); // with the real-time step: the shortest frame in ns
int frame_step_movie_hz(void); // the movie rate frame_step_cap_ns paces at; 0: no movie
int ov_movie_live(void); // ov_movie2.c: a movie plays (frame_rate.c movie_cap_hz)
int frame_limit_adjustable(void); // the frame limit setting changes the rate (always)
int frame_free_running(void); // no fps60/realtime patch set: the game steps by the clock
double frame_step_limit_hz(void); // the frame rate the limit gives (overlay); 0 unknown
double frame_step_game_hz(void); // the rate the game draws at under the limit; 0 unknown
float frame_step_frames(void); // the 60 Hz frames the game steps per update (set ostep), else 1
// rtss_limit.c: RivaTuner Statistics Server takes the frame limit where it runs
void rtss_limit_want(int64_t mhz); // the pacer's limit in millihertz: >0, 0 none, -1 keep RTSS out
int rtss_limit_active(void); // RTSS limits to the wanted rate: the pacer holds no frames
int rtss_limit_state(void); // menu: 1 RTSS limits, 2 its profile is read-only, 3 it let the
// game run past its limit
void rtss_limit_restore(void); // before the process ends: the profile's old limit back
int64_t frame_step_world_us(void); // game time since the loaded save's first frame, -1 before
// (NX2NAVI_FPS_LIMIT, live)
uint64_t frame_step_ticks(void); // game time since the first present in 60 Hz ticks (input scripts)
// In-launch A/B measurement: NX2NAVI_AB=<seconds> splits the world clock (frame_step_world_us) into blocks of that much
// game time in the order A B B A (repeated); rt_ab() is 1 in the B blocks, 0 in A blocks, before the world clock and
// without NX2NAVI_AB. rt_knob(name, def) reads the switch
// NX2NAVI_<name> once per call (def when unset): 0 off, 2 for "ab" (on in the B blocks only), 1 for anything else;
// callers keep it and test it with RT_KNOB_ON. The perf line counts the B frames of its window (tools/ab_summary.py
// compares the windows that are wholly A or wholly B).
int rt_ab(void);
int rt_knob(const char *name, const char *def);
#define RT_KNOB_ON(mode) ((mode) == 2 ? rt_ab() : (mode))
void frame_step_present(uint64_t display_ns, int interval); // nvnQueuePresentTexture: the frame's display time and
// present interval (60 Hz ticks)
// ---- boot (boot.c, main.c)
typedef struct { const char *exefs, *romfs, *profile; int boot; double seconds; const char *shot, *gpu; } RtConfig;
// romfs: directory with romfs.bin and index.json;
// frames: --frames N (0 = no limit); shot: --shot
// PATH; gpu: NX2NAVI_GPU
const RtConfig *rt_config(void);
void rt_boot_event(const char *what, Cpu *c, const char *detail); // checkpoint log; "nvn" ends --boot
void rt_boot(void); // boot.c
// Modes of nx2navi.exe. main.c has run mem_init and parsed the paths; each mode calls ldr_load itself if it needs the
// modules, registers its host functions, then seals (hostfn_seal, or implicitly at the first rt_call). Return the
// process exit code.
int list_audio_devices(void); // audio_mixer.c: --list-audio-devices (SDL3 on every window back end)
int probe_cfg(void); // cfg_probe.c
int probe_dpi(void); // dpi_probe_win.c (Windows only)
int list_monitors(void); // --list-monitors: gfx_window_win.c, gfx_window_sdl.c
int probe_mods(void); // mods_probe.c
void discord_init(void); // discord_rpc.c: the Rich Presence thread, game runs only
int probe_discord(void); // discord_rpc.c
int probe_loader(void), probe_kernel(void), probe_ipc(void), probe_services(void), probe_fs(void);
int probe_gfx_abi(void); // gfx_abi_probe.c
int probe_vkdevice(void); // vk_device.c
int vk_sync_probe_devlost(void); // vk_sync.c: --probe-devlost
int probe_nvn(void), probe_nvn_tex(void); // nvn_probe.c, nvn_tex_probe.c
int probe_window(void); // gfx_window_probe.c
int probe_shader(void); // gfx_shader.c
int probe_census_dir(char *dir, size_t n); // gfx_shader_probe.c: build/<version>/shaders for the probes
int probe_vkmem(void), probe_vkexec(void), probe_astc(void); // tex_probe.c, vk_exec_probe.c, astc_probe.c
int probe_meshfb(void); // mesh_fb_probe.c
int probe_zstd(void); // ov_zstd.c
int probe_gamepad(void); // input.c
int probe_movie(void); // movie_probe.c: the host movie decoders on one RomFS movie
int vk_pipeline_prepare_shaders(void); // vk_pipeline.c: --prepare-shaders (tools/prepare_shaders.py)
int vk_pipeline_warm_shaders(void); // vk_pipeline.c: --warm-shaders (tools/build_game.py)
int vk_pipeline_prepare_pipelines(void); // vk_pipeline.c: --prepare-pipelines (the shared pipeline list)
int vk_pipeline_probe_prewarm(void); // vk_pipeline.c: --probe-prewarm
int vk_pipeline_probe_buildgate(void); // vk_pipeline.c: --probe-buildgate
int vk_ffx_probe_cache(void); // vk_ffx.cpp: --probe-ffx-cache
// vk_device.c: the drivers' own shader caches beside the exe (MESA_SHADER_CACHE_DIR, on NVIDIA's Linux driver
// __GL_SHADER_DISK_CACHE_PATH and _SIZE) in the environment, once; before any thread can load a Vulkan driver
void vk_driver_cache_env(void);
int vk_pipeline_spirv_selftest(void); // vk_pipeline.c: --selftest, the SPIR-V rewrites without a device