Files
NX2navi/runtime/ov_system.c
epicmeme546 754ded99e0 Raise the frame rate in Kokiri Forest and apply more settings during a run.
The foliage's sun shadows took most of the GPU time: one mesh draw shaded about 1,000 million fragments in the far cascade. The far cascade now shades them at a 2x2 rate, and the two far cascades drop one side of the double-sided cards. At the forest entrance the frame rate went from 18 to about 43 fps on an RX 7900 XT.

FSR 3.1, its frame generation and the console mode now take effect without a restart. The full performance overlay lists the settings shown in the options menu.

NX2NAVI_GPU_PASSES times each pass and counts its fragments, and NX2NAVI_SET_AT changes settings at a given frame for test runs. Both are off by default.

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

1074 lines
49 KiB
C

// Contains code Copyright (c) 2026 MacNativeGames contributors (MIT; see THIRD_PARTY_NOTICES.md).
// System service overrides: nn::oe, applet, am, prepo, nfp, err, aoc, pctl, diag,
// account, time and pl, and the six observers. Nothing here talks to a Horizon service; each call is answered by the
// host. Stub rule: queries and initialisation succeed with neutral values, data paths stop with a named fatal.
//
// Environment:
// NX2NAVI_LANG=<Switch language code> the game's language as given ("ja", "en-GB", "zh-Hant", ...)
// NX2NAVI_LOCALE=<BCP 47 tag> else the language for this locale ("pt-BR"), else the Windows user locale;
// a language the game does not ship becomes en-US
// NX2NAVI_OPERATION_MODE=docked|handheld operation and performance mode reported to the game (default docked)
// NX2NAVI_ASSUME_FOCUS=1|0 1 (default, the Game tab's row): GetCurrentFocusState always reports in focus,
// whatever the window says; 0: out of focus while the window is, as on a Switch
// NX2NAVI_SHARED_FONTS=<dir> nn::pl shared fonts (tools/extract_fonts.py output), default
// <romfs>/../../firmware/shared-fonts
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "amiibo.h"
#include "cfg.h"
#include "fs.h"
#include "host.h"
#include "ov.h"
#include "platform/platform.h"
#define NFP_R_DEVICE_NOT_FOUND 0x8073u // nfp: no NFC reader on the host
#define ACCOUNT_R_INVALID 0x7c67u // the result for an unknown user handle or uid
#define TIME_R_INVALID 0x74u
#define TIME_R_SOURCE_MISMATCH 0x18074u
static void need(int ok, const char *what) {
if (!ok) rt_fatal("%s", what);
}
static const char *caller(Cpu *c, char *buf, int n) { return ldr_describe(c->x[30] - 4, buf, n); }
// ==== nn::oe (services.c)
// NX2NAVI_OPERATION_MODE: 1 handheld (OperationMode 0, PerformanceMode Normal 0), 0 docked (1, Boost 1). It follows the
// setting; the game is told of a change as the console tells it (ov_operation_mode_live).
int ov_handheld(void) {
const char *e = cfg_value("NX2NAVI_OPERATION_MODE");
if (!strcmp(e, "docked")) return 0;
if (!strcmp(e, "handheld")) return 1;
rt_fatal("NX2NAVI_OPERATION_MODE must be docked or handheld, not '%s'", e);
}
// After a settings change (gfx_present.c window_settings_live): a new mode reaches the game through the applet messages
// OperationModeChanged and PerformanceModeChanged, which make it read the mode again.
void ov_operation_mode_live(void) {
static int was = -1;
int h = ov_handheld();
if (h != was) {
rt_log("oe: operation mode reported to the game: %s", h ? "handheld (performance normal)" : "docked (performance boost)");
if (was >= 0) oe_push_message(0x1e), oe_push_message(0x1f);
was = h;
}
}
// The Switch language for a BCP 47 tag; en-US for a language the game does not ship.
static const char *switch_language(const char *tag) {
char lang[8] = "", region[4] = "", script[8] = "";
size_t n = strcspn(tag, "-_");
if (n < sizeof lang)
for (size_t i = 0; i < n; i++) lang[i] = (char)tolower((unsigned char)tag[i]);
for (const char *p = tag + n; *p;) {
p += strspn(p, "-_");
size_t k = strcspn(p, "-_");
if (k == 2 && isalpha((unsigned char)p[0]) && isalpha((unsigned char)p[1])) {
region[0] = (char)toupper((unsigned char)p[0]);
region[1] = (char)toupper((unsigned char)p[1]);
} else if (k == 3 && isdigit((unsigned char)p[0])) {
memcpy(region, p, 3); // UN M.49 area, e.g. es-419
} else if (k == 4 && !*script) {
for (size_t i = 0; i < 4; i++) script[i] = (char)(i ? tolower((unsigned char)p[i]) : toupper((unsigned char)p[i]));
}
p += k;
}
if (!strcmp(lang, "ja") || !strcmp(lang, "de") || !strcmp(lang, "it") || !strcmp(lang, "nl") ||
!strcmp(lang, "ru") || !strcmp(lang, "ko") || !strcmp(lang, "pl"))
return !strcmp(lang, "ja") ? "ja" : !strcmp(lang, "de") ? "de" : !strcmp(lang, "it") ? "it"
: !strcmp(lang, "nl") ? "nl" : !strcmp(lang, "ru") ? "ru" : !strcmp(lang, "ko") ? "ko" : "pl";
// Brazil's Portuguese is a language of its own on the Switch (pt-BR, Mals/USpt); Portugal's is pt
if (!strcmp(lang, "pt")) return !strcmp(region, "BR") ? "pt-BR" : "pt";
if (!strcmp(lang, "fr")) return !strcmp(region, "CA") ? "fr-CA" : "fr";
if (!strcmp(lang, "es")) return *region && strcmp(region, "ES") ? "es-419" : "es";
if (!strcmp(lang, "zh"))
return !strcmp(script, "Hant") || !strcmp(region, "TW") || !strcmp(region, "HK") || !strcmp(region, "MO")
? "zh-Hant" : "zh-Hans";
if (!strcmp(lang, "en"))
return !strcmp(region, "GB") || !strcmp(region, "IE") || !strcmp(region, "AU") || !strcmp(region, "NZ")
? "en-GB" : "en-US";
return "en-US";
}
// The game's language as a Switch language code (NX2NAVI_LANG, else NX2NAVI_LOCALE, else the Windows user locale). NX2navi
// accepts "zh-Hans" and "zh-Hant" for Chinese and falls back to Japanese for "zh-CN" and "zh-TW", so those two
// spellings, which older nx2navi.ini files and menus wrote, are mapped to them.
const char *ov_language(void) {
static const char *const codes[] = {"ja", "en-US", "fr", "de", "it", "es", "ko", "nl", "pt", "ru",
"en-GB", "fr-CA", "es-419", "zh-Hans", "zh-Hant", "pt-BR", "pl"};
const char *code = cfg_get("NX2NAVI_LANG"); // no fixed default: NX2NAVI_LOCALE or the Windows user locale decides when unset
if (code && *code) {
if (!strcmp(code, "zh-CN")) return "zh-Hans";
if (!strcmp(code, "zh-TW")) return "zh-Hant";
for (size_t i = 0; i < sizeof codes / sizeof *codes; i++)
if (!strcmp(code, codes[i])) return codes[i];
rt_fatal("NX2NAVI_LANG '%s' is not a Switch language code", code);
}
const char *tag = cfg_get("NX2NAVI_LOCALE");
char sys[85] = "en-US";
if (!tag || !*tag) {
if (*plat_user_locale()) snprintf(sys, sizeof sys, "%s", plat_user_locale());
tag = sys;
}
return switch_language(tag);
}
// LanguageCode GetDesiredLanguage(): char[8] in x0
static void ov_desired_language(Cpu *c) {
const char *code = ov_language();
uint64_t v = 0;
memcpy(&v, code, strlen(code));
ret_x(c, v);
}
// nn::oe::Initialize opens the appletOE service (served by ipc.c now that domains are supported) and builds the
// module's own service object, which the oe wrappers the game calls internally dereference. It must run: observe
// it and call the guest definition instead of replacing it.
static void ov_oe_initialize(Cpu *c) {
rt_original(c);
rt_log("oe: application environment: a local host process");
}
HOST_OBSERVER("_ZN2nn2oe10InitializeEv", ov_oe_initialize);
// void SetGraphicsScaleChangedNotificationEnabled(bool): a thin wrapper over the appletOE service object, which
// the overridden Initialize never builds; the notification is a no-op on a desktop window.
static void ov_oe_graphics_scale(Cpu *c) { (void)c; }
HOST_OVERRIDE("_ZN2nn2oe42SetGraphicsScaleChangedNotificationEnabledEb", ov_oe_graphics_scale);
// GraphicsScalingParameters GetGraphicsScalingParameters(): 12 bytes returned in x0 (words 0-1) and w1 (word 2).
// Word 0 is a scale *mode*, not a float: the SDK's own caller (sdk+0x59a690) maps 10 -> 0, 20 -> 1, 30 -> 2 and
// aborts on anything else (an earlier {1.0f, 1.0f} here hit that abort). 10 is the console's default (the graphics
// scale controller's current scale); the other two words are zero.
static void ov_oe_scaling_parameters(Cpu *c) {
ret_x(c, 10);
c->x[1] = 0;
}
HOST_OVERRIDE("_ZN2nn2oe28GetGraphicsScalingParametersEv", ov_oe_scaling_parameters);
// void SetPerformanceConfiguration(PerformanceMode mode, int config)
static void ov_performance_config(Cpu *c) {
int64_t mode = (int32_t)arg_x(c, 0);
if (mode < 0 || mode > 1) rt_fatal("nn::oe::SetPerformanceConfiguration: invalid mode %lld", (long long)mode);
rt_log("oe: performance request mode %lld configuration %#x: the host OS schedules the CPU", (long long)mode,
(uint32_t)arg_x(c, 1));
}
// void SetCpuBoostMode(CpuBoostMode mode)
static void ov_cpu_boost(Cpu *c) {
int64_t mode = (int32_t)arg_x(c, 0);
if (mode < 0 || mode > 2) rt_fatal("nn::oe::SetCpuBoostMode: unknown mode %lld", (long long)mode);
}
static void ov_operation_mode(Cpu *c) { ret_x(c, ov_handheld() ? 0 : 1); }
static void ov_performance_mode(Cpu *c) { ret_x(c, ov_handheld() ? 0 : 1); }
// void SetFocusHandlingMode(FocusHandlingMode mode): the nnMain checkpoint of --boot
static volatile int32_t focus_mode;
static void ov_focus_mode(Cpu *c) {
int64_t mode = (int32_t)arg_x(c, 0);
if (mode < 0 || mode > 3) rt_fatal("nn::oe::SetFocusHandlingMode: unknown mode %lld", (long long)mode);
plat_xchg32(&focus_mode, (int32_t)mode);
if (rt_config()->boot) {
rt_boot_event("checkpoint", c, "SetFocusHandlingMode");
} else {
char lr[96];
rt_log("oe: focus handling mode %lld from %s", (long long)mode, caller(c, lr, sizeof lr));
}
}
// FocusState GetCurrentFocusState(): 1 in focus, 2 out of focus (nn::oe::FocusState). The game sets focus handling
// mode 2: on a Switch the system then suspends the whole process while it is out of focus and sends Resume when it
// returns. Here only the game's main thread stops (it waits in PopNotificationMessage for the next message) while its
// loader threads, audio and clocks run on, and a load interrupted that way was seen to never finish (Linux, 1.4.3).
// So NX2NAVI_ASSUME_FOCUS=1, the default, keeps it at 1; with 0 it follows the window through
// oe_set_focus. The caller of oe_set_focus also pushes OE_MSG_FOCUS_STATE_CHANGED.
//
// The setting is read on every focus change rather than once at start: a focus change is rare, the lookup is a short
// walk of the loaded ini entries, and the Game tab's row then takes effect the next time the window is left, without
// a restart.
static volatile int32_t focus_state = 1;
void oe_set_focus(int in_focus) {
plat_xchg32(&focus_state, in_focus || atoi(cfg_value("NX2NAVI_ASSUME_FOCUS")) ? 1 : 2);
}
static void ov_focus_state(Cpu *c) { ret_x(c, (uint64_t)focus_state); }
static volatile int32_t notify_resume, notify_operation, notify_performance;
static void ov_resume_enabled(Cpu *c) { plat_xchg32(&notify_resume, (int32_t)(arg_x(c, 0) & 0xff)); }
static void ov_operation_enabled(Cpu *c) { plat_xchg32(&notify_operation, (int32_t)(arg_x(c, 0) & 0xff)); }
static void ov_performance_enabled(Cpu *c) { plat_xchg32(&notify_performance, (int32_t)(arg_x(c, 0) & 0xff)); }
// ==== nn::oe, applet, am, prepo, nfp, err (applet_host.c)
static PlatMutex lock = PLAT_MUTEX_INIT;
static PlatCond message_cv = PLAT_COND_INIT;
static uint32_t messages[32];
static unsigned head, tail;
static float balance[2] = {1.0f, 1.0f};
static uint64_t start_ticks;
__attribute__((constructor)) static void started(void) { start_ticks = rt_ticks(); }
void oe_push_message(uint32_t message) {
plat_mutex_lock(&lock);
if (tail - head < 32) {
messages[tail++ % 32] = message;
plat_cond_broadcast(&message_cv);
}
plat_mutex_unlock(&lock);
}
// void GetExpectedVolumeBalance(float *left, float *right)
static void ov_get_balance(Cpu *c) {
float *l = arg_p(c, 0), *r = arg_p(c, 1);
plat_mutex_lock_shared(&lock);
if (l) *l = balance[0];
if (r) *r = balance[1];
plat_mutex_unlock_shared(&lock);
}
// void SetExpectedVolumeBalance(float left, float right)
static void ov_set_balance(Cpu *c) {
float l = arg_s(c, 0), r = arg_s(c, 1);
if (!(l >= 0 && l <= 1 && r >= 0 && r <= 1))
rt_fatal("nn::oe::SetExpectedVolumeBalance: balance %g/%g out of range", (double)l, (double)r);
plat_mutex_lock(&lock);
balance[0] = l;
balance[1] = r;
plat_mutex_unlock(&lock);
}
// TimeSpan GetProgramTotalActiveTime(): nanoseconds since the process started (19.2 MHz ticks * 625 / 12)
static void ov_active_time(Cpu *c) {
ret_x(c, (uint64_t)((unsigned __int128)(rt_ticks() - start_ticks) * 625 / 12));
}
// void SetUserInactivityDetectionTimeExtended[Unsafe](bool): the host never puts the console to sleep
static void ov_inactivity_extended(Cpu *c) { (void)c; }
// void FinishStartupLogo(): no system logo is drawn on the host
static void ov_finish_logo(Cpu *c) { (void)c; rt_log("oe: the game finished its startup logo"); }
// bool TryPopLaunchParameter(size_t *size, void *buffer, size_t capacity): launched directly, no parameter
static void ov_launch_parameter(Cpu *c) {
uint64_t *size = arg_p(c, 0);
if (size) *size = 0;
ret_x(c, 0);
}
// bool TryPopNotificationMessage(Message *out)
static void ov_try_pop(Cpu *c) {
uint32_t *out = arg_p(c, 0);
plat_mutex_lock(&lock);
int ok = head != tail;
if (ok) {
uint32_t m = messages[head++ % 32];
if (out) *out = m;
}
plat_mutex_unlock(&lock);
ret_x(c, (uint64_t)ok);
}
// Message PopNotificationMessage(): blocks like a Horizon wait, so the thread gives its virtual core up meanwhile
static void ov_pop(Cpu *c) {
int away = kern_core_away();
plat_mutex_lock(&lock);
while (head == tail) plat_cond_wait(&message_cv, &lock);
uint32_t m = messages[head++ % 32];
plat_mutex_unlock(&lock);
kern_core_back(&away);
ret_x(c, m);
}
// int applet::GetPreviousProgramIndex(): started directly, not from an ExecuteProgram chain
static void ov_previous_program(Cpu *c) { ret_x(c, (uint64_t)(int64_t)-1); }
// AppletResourceUserId applet:: / am::GetAppletResourceUserId(): one application, one stable non-zero id
static void ov_aruid(Cpu *c) { ret_x(c, 1); }
// nn::prepo: play reports stay local (no Nintendo servers)
static volatile int64_t reports;
static void ov_report_save(Cpu *c) { // Result PlayReport::Save() and Save(const account::Uid &)
int64_t n = plat_inc64(&reports);
if (n % 100 == 1) rt_log("prepo: play report kept local (not sent), %lld so far", (long long)n);
ret_x(c, 0);
}
static void ov_result_ok(Cpu *c) { ret_x(c, 0); } // RequestImmediateTransmission, AddInternetConnectionStatus
// Switch 2: Result PlayReport::Send(const account::Uid &) replaces Save (kept local like it);
// Result AsyncContext::GetCompletionEvent(SystemEvent *out): out becomes a signalled event; AsyncContext::GetResult();
// SystemEvent *GetOverSaveFrequencyLimitEvent(): an event that never fires (no save limit here)
static void ov_report_event(Cpu *c) {
void *out = arg_p(c, 1);
if (out) {
kern_bind_system_event(c, (uint64_t)(uintptr_t)out, 0);
kern_signal_system_event((uint64_t)(uintptr_t)out);
}
ret_x(c, 0);
}
static void ov_report_limit_event(Cpu *c) {
static uint8_t ev[0x40]; // an nn::os::SystemEventType
static volatile int32_t bound;
if (!plat_xchg32(&bound, 1)) kern_bind_system_event(c, (uint64_t)(uintptr_t)ev, 0);
ret_x(c, (uint64_t)(uintptr_t)ev);
}
static void ov_nothing(Cpu *c) { (void)c; }
// nn::nfp: NFC reader & amiibo support
static volatile int32_t nfp_state;
static void ov_nfp_initialize(Cpu *c) { plat_xchg32(&nfp_state, 1); ret_x(c, 0); }
static void ov_nfp_finalize(Cpu *c) {
plat_xchg32(&nfp_state, 0);
amiibo_stop_detection();
amiibo_unmount();
ret_x(c, 0);
}
static void ov_nfp_state(Cpu *c) { ret_x(c, (uint64_t)nfp_state); }
// Result ListDevices(DeviceHandle *out, int *count, int max)
static void ov_nfp_list(Cpu *c) {
void *out = arg_p(c, 0);
int32_t *count = arg_p(c, 1);
int32_t max = (int32_t)arg_x(c, 2);
if (!amiibo_is_device_present()) {
if (count) *count = 0;
ret_x(c, NFP_R_DEVICE_NOT_FOUND);
return;
}
if (out && max > 0) {
static const uint8_t dev[8] = { 1, 0, 0, 0, 0, 0, 0, 0 };
memcpy(out, dev, sizeof dev);
}
if (count) *count = 1;
ret_x(c, 0);
}
// Result AttachAvailabilityChangeEvent(os::SystemEventType *event)
static void ov_nfp_availability(Cpu *c) {
uint64_t ev = arg_x(c, 0);
if (!ev) rt_fatal("nn::nfp::AttachAvailabilityChangeEvent: null event");
kern_bind_system_event(c, ev, 0);
kern_signal_system_event(ev);
ret_x(c, 0);
}
// DeviceState GetDeviceState(const DeviceHandle &)
static void ov_nfp_device_state(Cpu *c) {
(void)c;
ret_x(c, (uint64_t)amiibo_get_device_state());
}
// Result Mount(const DeviceHandle &, ModelType, MountTarget)
static void ov_nfp_mount(Cpu *c) {
(void)c;
if (!amiibo_has_tag()) {
ret_x(c, NFP_R_DEVICE_NOT_FOUND);
return;
}
amiibo_mount();
ret_x(c, 0);
}
// Result GetNpadId(u32 *npad_id, const DeviceHandle &)
static void ov_nfp_get_npad_id(Cpu *c) {
uint32_t *npad = arg_p(c, 0);
if (npad) *npad = 0; // Handheld / Player 1
ret_x(c, 0);
}
// Result AttachActivateEvent(os::SystemEventType *event, const DeviceHandle &)
static void ov_nfp_attach_activate(Cpu *c) {
uint64_t ev = arg_x(c, 0);
if (ev) {
kern_bind_system_event(c, ev, 0);
if (amiibo_has_tag()) kern_signal_system_event(ev);
}
ret_x(c, 0);
}
// Result AttachDeactivateEvent(os::SystemEventType *event, const DeviceHandle &)
static void ov_nfp_attach_deactivate(Cpu *c) {
uint64_t ev = arg_x(c, 0);
if (ev) {
kern_bind_system_event(c, ev, 0);
}
ret_x(c, 0);
}
// Result StartDetection(const DeviceHandle &)
static void ov_nfp_start_detection(Cpu *c) {
(void)c;
amiibo_start_detection();
ret_x(c, 0);
}
// Result StopDetection(const DeviceHandle &)
static void ov_nfp_stop_detection(Cpu *c) {
(void)c;
amiibo_stop_detection();
ret_x(c, 0);
}
// Result GetTagInfo(TagInfo *out, const DeviceHandle &)
static void ov_nfp_get_tag_info(Cpu *c) {
void *out = arg_p(c, 0);
if (!amiibo_has_tag() && !amiibo_is_mounted()) {
ret_x(c, NFP_R_DEVICE_NOT_FOUND);
return;
}
if (out) amiibo_get_tag_info(out);
ret_x(c, 0);
}
// Result GetModelInfo(ModelInfo *out, const DeviceHandle &)
static void ov_nfp_get_model_info(Cpu *c) {
void *out = arg_p(c, 0);
if (!amiibo_is_mounted() && !amiibo_has_tag()) {
ret_x(c, NFP_R_DEVICE_NOT_FOUND);
return;
}
if (out) amiibo_get_model_info(out);
ret_x(c, 0);
}
static void ov_nfp_no_device(Cpu *c) { ret_x(c, NFP_R_DEVICE_NOT_FOUND); }
// void err::ShowError(Result): the game's error dialog, logged
static void ov_show_error(Cpu *c) {
uint32_t r = (uint32_t)arg_x(c, 0);
char lr[96];
rt_log("err: game error dialog %04u-%04u (result %#x) from %s", 2000 + (r & 0x1ff), (r >> 9) & 0x1fff, r,
caller(c, lr, sizeof lr));
}
// ==== SDK-internal initialisation (services.c)
static void ov_diag_startup(Cpu *c) { (void)c; rt_log("diag: host diagnostics"); }
static void ov_aoc_initialize(Cpu *c) { (void)c; rt_log("aoc: no add-on content"); }
// 1.4.3's main lists add-on content: there is none.
// int ListAddOnContent(AddOnContentIndex *out, int offset, int count)
static void ov_aoc_list(Cpu *c) {
int offset = (int)arg_x(c, 1), count = (int)arg_x(c, 2);
if (offset < 0 || count < 0) rt_fatal("aoc: ListAddOnContent range %d+%d", offset, count);
ret_x(c, 0);
}
// ==== nn::nifm: this build is offline. Initialize succeeds without the nifm:u session, the network is never
// available, a request completes at once (unaccepted), and the calls that report its error return
// NetworkCommunicationDisabled (nifm module 110, description 1111).
#define NIFM_R_COMMUNICATION_DISABLED 0x8ae6eu
static void ov_nifm_initialize(Cpu *c) { rt_log("nifm: offline, no network"); ret_x(c, 0); }
static void ov_nifm_available(Cpu *c) { ret_x(c, 0); }
static void ov_nifm_request(Cpu *c) { (void)c; }
static void ov_nifm_error(Cpu *c) { ret_x(c, NIFM_R_COMMUNICATION_DISABLED); }
// int CountAddOnContent(): the SDK's own reads the aoc service object the overridden Initialize never opens.
static void ov_aoc_count(Cpu *c) { ret_x(c, 0); }
static void ov_err_context(Cpu *c) { (void)c; rt_log("err: error contexts stay local"); }
static void ov_pctl_initialize(Cpu *c) { (void)c; rt_log("pctl: no parental controls"); }
static void ov_abort_observer(Cpu *c) { (void)c; rt_log("diag: the host crash reporter observes aborts"); }
// ==== observers (services.c): log, then the guest definition
static void observe(Cpu *c, const char *name) {
rt_log("ENTER %s", name);
rt_original(c);
rt_log("RETURN %s", name);
}
static void obs_os_initialize(Cpu *c) { observe(c, "nnosInitialize"); }
static void obs_sdk_initialize(Cpu *c) { observe(c, "nninitInitializeSdkModule"); }
static void obs_startup(Cpu *c) { observe(c, "nninitStartup"); }
static void obs_main(Cpu *c) { observe(c, "nnMain"); }
// Result os::AllocateAddressRegion(uintptr_t *out, size_t size)
static void obs_address_region(Cpu *c) {
uint64_t *out = arg_p(c, 0), size = arg_x(c, 1);
uint32_t r = (uint32_t)rt_original(c);
if (rt_trace & RT_TRACE_HOSTCALL) // 20,000 lines a session otherwise, each a write on a game thread
rt_log("AllocateAddressRegion size %#llx result %#x address %#llx", (unsigned long long)size, r,
(unsigned long long)(r || !out ? 0 : *out));
}
// Result os::AllocateMemoryPages(uintptr_t address, size_t size)
static void obs_memory_pages(Cpu *c) {
uint64_t a = arg_x(c, 0), size = arg_x(c, 1);
uint32_t r = (uint32_t)rt_original(c);
if (rt_trace & RT_TRACE_HOSTCALL)
rt_log("AllocateMemoryPages address %#llx size %#llx result %#x", (unsigned long long)a, (unsigned long long)size, r);
}
// ==== nn::account (account.c): one offline local user whose Uid survives restarts in <profile>/account.uid
typedef struct { uint64_t lo, hi; } Uid;
typedef struct { Uid uid; uint64_t token; } UserHandle;
static PlatMutex account_lock = PLAT_MUTEX_INIT;
static Uid local;
static uint64_t tokens[64], next_token = 1;
static void make_dirs(const char *dir) {
plat_file_mkdir_p(dir);
if (!plat_dir_exists(dir)) rt_fatal("account: cannot create %s", dir);
}
// Holding account_lock. 1 when <profile>/save holds exactly one folder named as fs_host.c names an account's
// (<uid hi><uid lo>, 32 hex digits); local is then that account.
static int local_adopted;
typedef struct { int n; uint64_t hi, lo; } AdoptScan;
static int adopt_cb(const char *name, int kind, uint64_t size, void *ctx) {
(void)size;
AdoptScan *a = ctx;
if (kind != PLAT_FS_DIR && kind != PLAT_FS_LINK) return 1;
if (strlen(name) != 32 || strspn(name, "0123456789abcdefABCDEF") != 32) return 1;
char half[17];
memcpy(half, name, 16), half[16] = 0;
a->hi = strtoull(half, NULL, 16);
a->lo = strtoull(name + 16, NULL, 16);
a->n++;
return 1;
}
static int adopt_save_uid(const char *profile) {
char save[1100];
AdoptScan a = {0};
if (snprintf(save, sizeof save, "%s/save", profile) >= (int)sizeof save || !plat_dir_exists(save)) return 0;
plat_fs_list(save, adopt_cb, &a);
if (a.n != 1 || (!a.hi && !a.lo)) return 0;
local.lo = a.lo;
local.hi = a.hi;
local_adopted = 1;
return 1;
}
// Holding account_lock.
static void load_uid(void) {
if (local.lo || local.hi) return;
const char *dir = rt_config()->profile;
char path[1100];
make_dirs(dir);
need(snprintf(path, sizeof path, "%s/account.uid", dir) < (int)sizeof path, "account: path too long");
int64_t size = plat_file_size(path);
// a file that is there but cannot be asked for its size is no reason for a new account: its saves would vanish
if (size < 0 && plat_fs_kind(path) != PLAT_FS_MISSING) rt_fatal("account: cannot read %s", path);
if (size <= 0) {
// account.uid lost (a profile copied without it, or the save folder alone put in place): the one account
// folder in save/ is the player's, and taking its name keeps that save; otherwise a new account
if (!adopt_save_uid(dir)) {
do need(plat_random(&local, sizeof local), "account: random uid");
while (!local.lo && !local.hi);
}
need(plat_file_write_atomic(path, &local, sizeof local), "account: persist the local account identity");
rt_log("account: %s local account in %s", local_adopted ? "the save folder's" : "new", path);
} else {
uint64_t got = 0;
uint8_t *b = plat_file_read(path, &got);
if (b && got == sizeof local) memcpy(&local, b, sizeof local);
free(b);
if (got != sizeof local || (!local.lo && !local.hi))
rt_fatal("account: %s is not a local account identity (16 bytes)", path);
}
}
// host.h: the save directory of fs_host.c is named after this Uid.
void account_uid(uint64_t out[2]) {
plat_mutex_lock(&account_lock);
load_uid();
out[0] = local.lo;
out[1] = local.hi;
plat_mutex_unlock(&account_lock);
}
static int same_uid(const Uid *u) { return u && u->lo == local.lo && u->hi == local.hi; }
// Holding account_lock.
static int valid_handle(const UserHandle *h) {
if (!h || !same_uid(&h->uid)) return 0;
for (int i = 0; i < 64; i++)
if (tokens[i] && tokens[i] == h->token) return 1;
return 0;
}
// void Initialize()
static void ov_account_initialize(Cpu *c) {
uint64_t uid[2];
(void)c;
account_uid(uid);
}
// bool TryOpenPreselectedUser(UserHandle *out)
static void ov_open_user(Cpu *c) {
UserHandle *out = arg_p(c, 0);
if (!out) rt_fatal("nn::account::TryOpenPreselectedUser: null handle");
plat_mutex_lock(&account_lock);
load_uid();
int ok = 0;
for (int i = 0; i < 64 && !ok; i++)
if (!tokens[i]) {
tokens[i] = next_token++;
*out = (UserHandle){local, tokens[i]};
ok = 1;
}
plat_mutex_unlock(&account_lock);
ret_x(c, (uint64_t)ok);
}
// void CloseUser(const UserHandle &)
static void ov_close_user(Cpu *c) {
const UserHandle *h = arg_p(c, 0);
plat_mutex_lock(&account_lock);
if (!valid_handle(h)) rt_fatal("nn::account::CloseUser: not an open user handle");
for (int i = 0; i < 64; i++)
if (tokens[i] == h->token) tokens[i] = 0;
plat_mutex_unlock(&account_lock);
}
// Result GetUserId(Uid *out, const UserHandle &)
static void ov_user_id(Cpu *c) {
Uid *out = arg_p(c, 0);
plat_mutex_lock_shared(&account_lock);
int ok = out && valid_handle(arg_p(c, 1));
if (ok) *out = local;
plat_mutex_unlock_shared(&account_lock);
ret_x(c, ok ? 0 : ACCOUNT_R_INVALID);
}
// Result GetUserExistence(bool *out, const Uid &)
static void ov_user_existence(Cpu *c) {
uint8_t *out = arg_p(c, 0);
const Uid *u = arg_p(c, 1);
if (!out || !u) { ret_x(c, ACCOUNT_R_INVALID); return; }
plat_mutex_lock(&account_lock);
load_uid();
*out = (uint8_t)same_uid(u);
plat_mutex_unlock(&account_lock);
ret_x(c, 0);
}
// Result GetNickname(Nickname *out, const Uid &): 33 bytes, NUL-terminated UTF-8
static void ov_nickname(Cpu *c) {
char *out = arg_p(c, 0);
plat_mutex_lock(&account_lock);
load_uid();
int ok = out && same_uid(arg_p(c, 1));
plat_mutex_unlock(&account_lock);
if (ok) {
memset(out, 0, 33);
memcpy(out, "Local", 5);
}
ret_x(c, ok ? 0 : ACCOUNT_R_INVALID);
}
// ==== nn::time (time_host.c): the host clock and time zone. User and network clocks follow the host wall clock; the
// steady clock counts seconds since Windows started, with one random source id per process. ClockSnapshot has the
// public 0xD0-byte layout, because game code reads its fields through inline accessors.
typedef struct { int64_t value; uint8_t source[16]; } SteadyClockTimePoint;
typedef struct { int64_t offset; SteadyClockTimePoint point; } SystemClockContext;
typedef struct { int16_t year; int8_t month, day, hour, minute, second, pad; } CalendarTime;
typedef struct { int32_t day_of_week, day_of_year; char name[8]; uint8_t dst, pad[3]; int32_t utc_offset; } CalendarInfo;
typedef struct {
SystemClockContext user, network;
int64_t user_time, network_time;
CalendarTime user_calendar, network_calendar;
CalendarInfo user_info, network_info;
SteadyClockTimePoint steady;
char location[0x24];
uint8_t automatic_correction, type, pad[2];
} ClockSnapshot;
_Static_assert(sizeof(SteadyClockTimePoint) == 0x18 && sizeof(CalendarTime) == 8 && sizeof(CalendarInfo) == 0x18,
"nn::time types");
_Static_assert(sizeof(ClockSnapshot) == 0xd0, "nn::time::ClockSnapshot");
static uint8_t source_id[16];
static volatile int32_t time_ready;
static PlatOnce source_once = PLAT_ONCE_INIT;
static void make_source(void *param) {
(void)param;
need(plat_random(source_id, sizeof source_id), "time: source id");
plat_tz_west(); // reads the zone once, before the first localtime
return;
}
static SteadyClockTimePoint steady_now(void) {
plat_once(&source_once, make_source, NULL);
SteadyClockTimePoint p = {(int64_t)(plat_now_ms() / 1000), {0}};
memcpy(p.source, source_id, 16);
return p;
}
static int64_t posix_now(void) { return (int64_t)time(NULL); }
// Days since 1970-01-01 of a proleptic Gregorian date, and back (H. Hinnant's civil calendar algorithms).
static int64_t days_from_civil(int64_t y, unsigned m, unsigned d) {
y -= m <= 2;
int64_t era = (y >= 0 ? y : y - 399) / 400;
unsigned yoe = (unsigned)(y - era * 400);
unsigned doy = (153 * (m > 2 ? m - 3 : m + 9) + 2) / 5 + d - 1;
unsigned doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
return era * 146097 + (int64_t)doe - 719468;
}
static void civil_from_days(int64_t z, int64_t *y, unsigned *m, unsigned *d) {
z += 719468;
int64_t era = (z >= 0 ? z : z - 146096) / 146097;
unsigned doe = (unsigned)(z - era * 146097);
unsigned yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
unsigned doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
unsigned mp = (5 * doy + 2) / 153;
*d = doy - (153 * mp + 2) / 5 + 1;
*m = mp < 10 ? mp + 3 : mp - 9;
*y = (int64_t)yoe + era * 400 + (*m <= 2);
}
static int64_t floor_div(int64_t a, int64_t b) { return a / b - (a % b != 0 && (a < 0) != (b < 0)); }
// UTC calendar fields of a POSIX time. 0 if the year does not fit CalendarTime.
static int utc_calendar(int64_t t, CalendarTime *c, CalendarInfo *info) {
int64_t days = floor_div(t, 86400), sec = t - days * 86400, y;
unsigned m, d;
civil_from_days(days, &y, &m, &d);
*c = (CalendarTime){(int16_t)y, (int8_t)m, (int8_t)d, (int8_t)(sec / 3600), (int8_t)(sec / 60 % 60),
(int8_t)(sec % 60), 0};
if (info) {
memset(info, 0, sizeof *info);
info->day_of_week = (int32_t)(days + 4 - floor_div(days + 4, 7) * 7); // 1970-01-01 was a Thursday
info->day_of_year = (int32_t)(days - days_from_civil(y, 1, 1));
memcpy(info->name, "UTC", 3);
}
return y >= -32768 && y <= 32767;
}
// Local calendar fields of a POSIX time in the Windows time zone. The zone name is "UTC" or the UTC offset ("+03",
// "-0330"), as tzdata names zones without an abbreviation; Windows has no abbreviations. 0 outside localtime's range.
static int local_calendar(int64_t t, CalendarTime *c, CalendarInfo *info) {
plat_once(&source_once, make_source, NULL);
struct tm tm;
if (!plat_localtime(t, &tm)) return 0;
int64_t as_utc = days_from_civil(tm.tm_year + 1900, (unsigned)tm.tm_mon + 1, (unsigned)tm.tm_mday) * 86400 +
tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec;
int32_t offset = (int32_t)(as_utc - t);
*c = (CalendarTime){(int16_t)(tm.tm_year + 1900), (int8_t)(tm.tm_mon + 1), (int8_t)tm.tm_mday, (int8_t)tm.tm_hour,
(int8_t)tm.tm_min, (int8_t)tm.tm_sec, 0};
if (info) {
memset(info, 0, sizeof *info);
info->day_of_week = tm.tm_wday;
info->day_of_year = tm.tm_yday;
int a = offset < 0 ? -offset : offset;
if (!offset) memcpy(info->name, "UTC", 3);
else if (a % 3600) snprintf(info->name, sizeof info->name, "%c%02d%02d", offset < 0 ? '-' : '+', a / 3600, a / 60 % 60);
else snprintf(info->name, sizeof info->name, "%c%02d", offset < 0 ? '-' : '+', a / 3600);
info->dst = tm.tm_isdst > 0;
info->utc_offset = offset;
}
return 1;
}
// IANA name of the standard offset: Etc/GMT+5 is UTC-5 (POSIX sign); "UTC" for a zone off whole hours.
static void location_name(char out[0x24]) {
long west = plat_tz_west();
memset(out, 0, 0x24);
if (!west || west % 3600) snprintf(out, 0x24, "UTC");
else snprintf(out, 0x24, "Etc/GMT%+ld", west / 3600);
}
static void ov_time_initialize(Cpu *c) {
plat_once(&source_once, make_source, NULL);
plat_xchg32(&time_ready, 1);
ret_x(c, 0);
}
static void ov_time_finalize(Cpu *c) { plat_xchg32(&time_ready, 0); ret_x(c, 0); }
static void ov_time_ready(Cpu *c) { ret_x(c, (uint64_t)time_ready); }
// Result StandardUserSystemClock / StandardNetworkSystemClock::GetCurrentTime(PosixTime *out)
static void ov_current_time(Cpu *c) {
int64_t *out = arg_p(c, 0);
if (!out) { ret_x(c, TIME_R_INVALID); return; }
*out = posix_now();
ret_x(c, 0);
}
// time_point StandardSteadyClock::now(): the int64 seconds in x0 only. The SDK caller (sdk+0x3d1d88) reads x0;
// returning the 0x18-byte struct through x8 would write through a stale pointer (time_host.c).
static void ov_steady_now(Cpu *c) { ret_x(c, (uint64_t)steady_now().value); }
// Result GetStandardSteadyClockCurrentTimespec(timespec *) / GetStandardUserSystemClockCurrentTimespec(timespec *):
// the SDK libc's clock_gettime(CLOCK_MONOTONIC / CLOCK_REALTIME) once a title selects the nn::time clocks
// (sdk+0xaf0f8 mode 3; Switch 2 titles do, via std::chrono::steady_clock). The SDK's own versions need the time
// service state the overridden Initialize never builds, fail, and libc++ then throws std::system_error.
typedef struct { int64_t sec, nsec; } GuestTimespec;
static void ov_steady_timespec(Cpu *c) {
GuestTimespec *out = arg_p(c, 0);
if (!out) { ret_x(c, TIME_R_INVALID); return; }
uint64_t ns = plat_now_ns();
out->sec = (int64_t)(ns / 1000000000u);
out->nsec = (int64_t)(ns % 1000000000u);
ret_x(c, 0);
}
static void ov_user_timespec(Cpu *c) {
GuestTimespec *out = arg_p(c, 0);
if (!out) { ret_x(c, TIME_R_INVALID); return; }
uint64_t ns = plat_unix_time_ns();
out->sec = (int64_t)(ns / 1000000000u);
out->nsec = (int64_t)(ns % 1000000000u);
ret_x(c, 0);
}
HOST_OVERRIDE("_ZN2nn4time37GetStandardSteadyClockCurrentTimespecEP8timespec", ov_steady_timespec);
HOST_OVERRIDE("_ZN2nn4time41GetStandardUserSystemClockCurrentTimespecEP8timespec", ov_user_timespec);
// Result ToCalendarTime(CalendarTime *out, CalendarAdditionalInfo *info, const PosixTime &t): local time
static void ov_to_calendar(Cpu *c) {
CalendarTime *out = arg_p(c, 0);
CalendarInfo *info = arg_p(c, 1);
const int64_t *t = arg_p(c, 2);
if (!out || !t) { ret_x(c, TIME_R_INVALID); return; }
CalendarTime ct;
CalendarInfo ci;
if (!local_calendar(*t, &ct, &ci)) { ret_x(c, TIME_R_INVALID); return; }
*out = ct;
if (info) *info = ci;
ret_x(c, 0);
}
// CalendarTime ToCalendarTimeInUtc(const PosixTime &t): 8 bytes in x0
static void ov_to_calendar_utc(Cpu *c) {
const int64_t *t = arg_p(c, 0);
CalendarTime ct;
utc_calendar(t ? *t : 0, &ct, NULL);
uint64_t v;
memcpy(&v, &ct, 8);
ret_x(c, v);
}
// PosixTime ToPosixTimeFromUtc(const CalendarTime &c): months out of 1..12 carry into the year, like timegm
static void ov_to_posix_utc(Cpu *c) {
const CalendarTime *ct = arg_p(c, 0);
if (!ct) rt_fatal("nn::time::ToPosixTimeFromUtc: null calendar time");
int64_t mon = ct->month - 1, y = ct->year + floor_div(mon, 12);
mon -= floor_div(mon, 12) * 12;
int64_t t = (days_from_civil(y, (unsigned)mon + 1, 1) + ct->day - 1) * 86400 + ct->hour * 3600 + ct->minute * 60 +
ct->second;
ret_x(c, (uint64_t)t);
}
// ClockSnapshot::ClockSnapshot()
static void ov_snapshot_init(Cpu *c) {
ClockSnapshot *s = arg_p(c, 0);
if (s) memset(s, 0, sizeof *s);
}
// static Result ClockSnapshot::CreateWithStandardSystemClock(ClockSnapshot *out)
static void ov_snapshot_create(Cpu *c) {
ClockSnapshot *s = arg_p(c, 0);
if (!s) { ret_x(c, TIME_R_INVALID); return; }
SteadyClockTimePoint steady = steady_now();
int64_t now = posix_now();
ClockSnapshot n = {0};
n.user = (SystemClockContext){now - steady.value, steady};
n.network = n.user;
n.user_time = n.network_time = now;
if (!local_calendar(now, &n.user_calendar, &n.user_info)) rt_fatal("nn::time: the host clock is out of range");
n.network_calendar = n.user_calendar;
n.network_info = n.user_info;
n.steady = steady;
location_name(n.location);
n.automatic_correction = 1;
*s = n;
ret_x(c, 0);
}
// Result CalculateSpanBetween(TimeSpan *out, const ClockSnapshot &a, const ClockSnapshot &b): nanoseconds
static void ov_span_between(Cpu *c) {
int64_t *out = arg_p(c, 0);
const ClockSnapshot *a = arg_p(c, 1), *b = arg_p(c, 2);
if (!out || !a || !b) { ret_x(c, TIME_R_INVALID); return; }
if (memcmp(a->steady.source, b->steady.source, 16)) { ret_x(c, TIME_R_SOURCE_MISMATCH); return; }
*out = (b->steady.value - a->steady.value) * 1000000000ll;
ret_x(c, 0);
}
// TimeSpan CalculateStandardUserSystemClockDifferenceByUser(const ClockSnapshot &a, const ClockSnapshot &b): the
// change of the user clock offset between the snapshots (manual adjustments) in nanoseconds
static void ov_user_difference(Cpu *c) {
const ClockSnapshot *a = arg_p(c, 0), *b = arg_p(c, 1);
if (!a || !b) rt_fatal("nn::time::CalculateStandardUserSystemClockDifferenceByUser: null snapshot");
if (memcmp(a->user.point.source, b->user.point.source, 16)) { ret_x(c, 0); return; }
ret_x(c, (uint64_t)((b->user.offset - a->user.offset) * 1000000000ll));
}
// ==== nn::pl shared fonts (shared_font.c). NX2navi asks for them only from its font loader, only with Simplified Chinese,
// Korean or Traditional Chinese text, and only for type 0; tools/extract_fonts.py writes
// them from the user's firmware. Each type is read on first use into guest-visible memory as the BFTTF file the pl
// shared memory holds; Address is 8 bytes past the file start (after the BFTTF header) and Size the decoded font size.
// The load state is always Loaded: the game spins until it is, so missing files stop with a named fatal at
// Address/Size instead of a hang. NX2NAVI_SHARED_FONTS=<dir> overrides <romfs>/../../firmware/shared-fonts.
#define SHARED_FONT_TYPES 6
#define BFTTF_MAGIC 0x7f9a0218u
static const char *const font_files[SHARED_FONT_TYPES] = {
"0-standard.bfttf", "1-chinese-simplified.bfttf", "2-chinese-simplified-extension.bfttf",
"3-chinese-traditional.bfttf", "4-korean.bfttf", "5-nintendo-extension.bfttf"};
static struct { PlatMutex lock; const uint8_t *file[SHARED_FONT_TYPES]; uint64_t size[SHARED_FONT_TYPES]; } fonts;
const char *ov_shared_font_dir(char *buf, size_t n) {
const char *e = cfg_get("NX2NAVI_SHARED_FONTS");
if (e && *e) snprintf(buf, n, "%s", e);
else {
char exe[PLAT_PATH_MAX], probe[PLAT_PATH_MAX + 40]; // shared-fonts next to nx2navi.exe (tools/package_build.py), else the repo's
int len = plat_exe_path(exe, sizeof exe);
char *cut = len && len < sizeof exe ? plat_last_sep(exe) : NULL;
if (cut) *cut = 0;
snprintf(probe, sizeof probe, "%s/shared-fonts/0-standard.bfttf", exe);
if (cut && plat_file_exists(probe)) snprintf(buf, n, "%s/shared-fonts", exe);
else snprintf(buf, n, "%s/../../firmware/shared-fonts", rt_config()->romfs);
}
return buf;
}
static uint32_t be32(const uint8_t *p) { return (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | p[3]; }
// The decoded font size of a BFTTF image, 0 if it is not one: big-endian words XORed with key = word0 ^ BFTTF_MAGIC,
// word1 = font size, then an sfnt (TrueType or OpenType) font.
uint64_t ov_bfttf_size(const uint8_t *p, uint64_t len) {
if (len < 12) return 0;
uint32_t key = be32(p) ^ BFTTF_MAGIC, size = be32(p + 4) ^ key, first = be32(p + 8) ^ key;
return (uint64_t)size + 8 == len && (first == 0x00010000u || first == 0x4f54544fu) ? size : 0;
}
static const char *font_type_name(int64_t t) { return font_files[t] + 2; }
static int64_t font_type(Cpu *c, const char *fn) {
int64_t t = (int32_t)arg_x(c, 0);
if (t < 0 || t >= SHARED_FONT_TYPES) rt_fatal("nn::pl::%s: invalid shared font type %lld", fn, (long long)t);
return t;
}
static void ov_font_state(Cpu *c) { font_type(c, "GetSharedFontLoadState"); ret_x(c, 1); }
// The font of type t, read and checked on first use; a named fatal when it is missing or damaged.
static const uint8_t *font_data(Cpu *c, const char *fn, int64_t t) {
plat_mutex_lock(&fonts.lock);
if (!fonts.file[t]) {
char dir[PLAT_PATH_MAX], path[PLAT_PATH_MAX + 64], lr[96];
snprintf(path, sizeof path, "%s/%s", ov_shared_font_dir(dir, sizeof dir), font_files[t]);
FILE *f = fopen(path, "rb");
if (!f)
rt_fatal("nn::pl::%s(%lld) from %s: no shared font %s (needed for Chinese and Korean text); run "
"tools/extract_fonts.py --firmware <firmware NCA dir> or set NX2NAVI_SHARED_FONTS", fn, (long long)t,
caller(c, lr, sizeof lr), path);
plat_fseek64(f, 0, SEEK_END);
int64_t len = plat_ftell64(f);
plat_fseek64(f, 0, SEEK_SET);
if (len < 12 || len > (64 << 20)) rt_fatal("nn::pl: shared font %s: size %lld", path, (long long)len);
uint8_t *p = mem_kernel_alloc((uint64_t)len, 16);
size_t got = fread(p, 1, (size_t)len, f);
fclose(f);
uint64_t size = got == (size_t)len ? ov_bfttf_size(p, (uint64_t)len) : 0;
if (!size) rt_fatal("nn::pl: shared font %s is not a BFTTF font", path);
fonts.size[t] = size;
__atomic_store_n(&fonts.file[t], p, __ATOMIC_RELEASE);
rt_log("pl: shared font %lld (%s): %llu bytes from %s", (long long)t, font_type_name(t),
(unsigned long long)size, path);
}
const uint8_t *p = fonts.file[t];
plat_mutex_unlock(&fonts.lock);
return p;
}
static void ov_font_address(Cpu *c) {
int64_t t = font_type(c, "GetSharedFontAddress");
ret_x(c, (uint64_t)(uintptr_t)(font_data(c, "GetSharedFontAddress", t) + 8));
}
static void ov_font_size(Cpu *c) {
int64_t t = font_type(c, "GetSharedFontSize");
font_data(c, "GetSharedFontSize", t);
ret_x(c, fonts.size[t]);
}
// ==== registration
/* installed as HOST_OBSERVER above */
HOST_OVERRIDE("_ZN2nn2oe18GetDesiredLanguageEv", ov_desired_language);
HOST_OVERRIDE("_ZN2nn2oe27SetPerformanceConfigurationENS0_15PerformanceModeEi", ov_performance_config);
HOST_OVERRIDE("_ZN2nn2oe15SetCpuBoostModeENS0_12CpuBoostModeE", ov_cpu_boost);
HOST_OVERRIDE("_ZN2nn2oe16GetOperationModeEv", ov_operation_mode);
HOST_OVERRIDE("_ZN2nn2oe18GetPerformanceModeEv", ov_performance_mode);
HOST_OVERRIDE("_ZN2nn2oe20SetFocusHandlingModeENS0_17FocusHandlingModeE", ov_focus_mode);
HOST_OVERRIDE("_ZN2nn2oe20GetCurrentFocusStateEv", ov_focus_state);
HOST_OVERRIDE("_ZN2nn2oe28SetResumeNotificationEnabledEb", ov_resume_enabled);
HOST_OVERRIDE("_ZN2nn2oe42SetOperationModeChangedNotificationEnabledEb", ov_operation_enabled);
HOST_OVERRIDE("_ZN2nn2oe44SetPerformanceModeChangedNotificationEnabledEb", ov_performance_enabled);
HOST_OVERRIDE("_ZN2nn6applet23GetPreviousProgramIndexEv", ov_previous_program);
HOST_OVERRIDE("_ZN2nn6applet23GetAppletResourceUserIdEv", ov_aruid);
HOST_OVERRIDE("_ZN2nn2am23GetAppletResourceUserIdEv", ov_aruid);
HOST_OVERRIDE("_ZN2nn2oe21TryPopLaunchParameterEPmPvm", ov_launch_parameter);
HOST_OVERRIDE("_ZN2nn2oe24GetExpectedVolumeBalanceEPfS1_", ov_get_balance);
HOST_OVERRIDE("_ZN2nn2oe24SetExpectedVolumeBalanceEff", ov_set_balance);
HOST_OVERRIDE("_ZN2nn2oe25GetProgramTotalActiveTimeEv", ov_active_time);
HOST_OVERRIDE("_ZN2nn2oe38SetUserInactivityDetectionTimeExtendedEb", ov_inactivity_extended);
HOST_OVERRIDE("_ZN2nn2oe44SetUserInactivityDetectionTimeExtendedUnsafeEb", ov_inactivity_extended);
HOST_OVERRIDE("_ZN2nn2oe17FinishStartupLogoEv", ov_finish_logo);
HOST_OVERRIDE("_ZN2nn2oe25TryPopNotificationMessageEPj", ov_try_pop);
HOST_OVERRIDE("_ZN2nn2oe22PopNotificationMessageEv", ov_pop);
HOST_OVERRIDE("_ZN2nn5prepo10PlayReport4SaveEv", ov_report_save);
HOST_OVERRIDE("_ZN2nn5prepo10PlayReport4SaveERKNS_7account3UidE", ov_report_save);
HOST_OVERRIDE("_ZN2nn5prepo28RequestImmediateTransmissionEv", ov_result_ok);
HOST_OVERRIDE("_ZN2nn5prepo10PlayReport4SendERKNS_7account3UidE", ov_report_save);
HOST_OVERRIDE("_ZN2nn5prepo12AsyncContext18GetCompletionEventEPNS_2os11SystemEventE", ov_report_event);
HOST_OVERRIDE("_ZN2nn5prepo12AsyncContext9GetResultEv", ov_result_ok);
HOST_OVERRIDE("_ZN2nn5prepo12AsyncContextD1Ev", ov_nothing);
HOST_OVERRIDE("_ZN2nn5prepo30GetOverSaveFrequencyLimitEventEv", ov_report_limit_event);
HOST_OVERRIDE("_ZN2nn5prepo27AddInternetConnectionStatusEPNS0_10PlayReportEPKcS4_S4_", ov_result_ok);
HOST_OVERRIDE("_ZN2nn3nfp10InitializeEv", ov_nfp_initialize);
HOST_OVERRIDE("_ZN2nn3nfp8FinalizeEv", ov_nfp_finalize);
HOST_OVERRIDE("_ZN2nn3nfp8GetStateEv", ov_nfp_state);
HOST_OVERRIDE("_ZN2nn3nfp11ListDevicesEPNS0_12DeviceHandleEPii", ov_nfp_list);
HOST_OVERRIDE("_ZN2nn3nfp29AttachAvailabilityChangeEventEPNS_2os15SystemEventTypeE", ov_nfp_availability);
HOST_OVERRIDE("_ZN2nn3nfp14GetDeviceStateERKNS0_12DeviceHandleE", ov_nfp_device_state);
HOST_OVERRIDE("_ZN2nn3nfp5MountERKNS0_12DeviceHandleENS0_9ModelTypeENS0_11MountTargetE", ov_nfp_mount);
HOST_OVERRIDE("_ZN2nn3nfp9GetNpadIdEPjRKNS0_12DeviceHandleE", ov_nfp_get_npad_id);
HOST_OVERRIDE("_ZN2nn3nfp19AttachActivateEventEPNS_2os15SystemEventTypeERKNS0_12DeviceHandleE", ov_nfp_attach_activate);
HOST_OVERRIDE("_ZN2nn3nfp21AttachDeactivateEventEPNS_2os15SystemEventTypeERKNS0_12DeviceHandleE", ov_nfp_attach_deactivate);
HOST_OVERRIDE("_ZN2nn3nfp14StartDetectionERKNS0_12DeviceHandleE", ov_nfp_start_detection);
HOST_OVERRIDE("_ZN2nn3nfp13StopDetectionERKNS0_12DeviceHandleE", ov_nfp_stop_detection);
HOST_OVERRIDE("_ZN2nn3nfp10GetTagInfoEPNS0_7TagInfoERKNS0_12DeviceHandleE", ov_nfp_get_tag_info);
HOST_OVERRIDE("_ZN2nn3nfp12GetModelInfoEPNS0_9ModelInfoERKNS0_12DeviceHandleE", ov_nfp_get_model_info);
HOST_OVERRIDE("_ZN2nn3err9ShowErrorENS_6ResultE", ov_show_error);
HOST_OVERRIDE("nndiagStartup", ov_diag_startup);
HOST_OVERRIDE("_ZN2nn3aoc10InitializeEv", ov_aoc_initialize);
HOST_OVERRIDE("_ZN2nn4nifm10InitializeEv", ov_nifm_initialize);
HOST_OVERRIDE("_ZN2nn4nifm18IsNetworkAvailableEv", ov_nifm_available);
HOST_OVERRIDE("_ZN2nn4nifm27SubmitNetworkRequestAndWaitEv", ov_nifm_request);
HOST_OVERRIDE("_ZN2nn4nifm20CancelNetworkRequestEv", ov_nifm_request);
HOST_OVERRIDE("_ZN2nn4nifm31HandleNetworkRequestErrorResultEv", ov_nifm_error);
HOST_OVERRIDE("_ZN2nn4nifm22GetCurrentProxySettingEPNS0_12ProxySettingE", ov_nifm_error);
HOST_OVERRIDE("_ZN2nn3aoc16ListAddOnContentEPiii", ov_aoc_list);
HOST_OVERRIDE("_ZN2nn3aoc17CountAddOnContentEv", ov_aoc_count);
HOST_OVERRIDE("_ZN2nn3err26InitializeContextWriterApiEv", ov_err_context);
HOST_OVERRIDE("_ZN2nn4pctl10InitializeEv", ov_pctl_initialize);
HOST_OVERRIDE("_ZN2nn4diag34InitializeApplicationAbortObserverEv", ov_abort_observer);
HOST_OBSERVER("_ZN2nn2os21AllocateAddressRegionEPmm", obs_address_region);
HOST_OBSERVER("_ZN2nn2os19AllocateMemoryPagesEmm", obs_memory_pages);
HOST_OBSERVER("nnosInitialize", obs_os_initialize);
HOST_OBSERVER("nninitInitializeSdkModule", obs_sdk_initialize);
HOST_OBSERVER("nninitStartup", obs_startup);
HOST_OBSERVER("nnMain", obs_main);
HOST_OVERRIDE("_ZN2nn7account10InitializeEv", ov_account_initialize);
HOST_OVERRIDE("_ZN2nn7account22TryOpenPreselectedUserEPNS0_10UserHandleE", ov_open_user);
HOST_OVERRIDE("_ZN2nn7account9CloseUserERKNS0_10UserHandleE", ov_close_user);
HOST_OVERRIDE("_ZN2nn7account9GetUserIdEPNS0_3UidERKNS0_10UserHandleE", ov_user_id);
HOST_OVERRIDE("_ZN2nn7account16GetUserExistenceEPbRKNS0_3UidE", ov_user_existence);
HOST_OVERRIDE("_ZN2nn7account11GetNicknameEPNS0_8NicknameERKNS0_3UidE", ov_nickname);
HOST_OVERRIDE("_ZN2nn4time10InitializeEv", ov_time_initialize);
HOST_OVERRIDE("_ZN2nn4time8FinalizeEv", ov_time_finalize);
HOST_OVERRIDE("_ZN2nn4time13IsInitializedEv", ov_time_ready);
HOST_OVERRIDE("_ZN2nn4time23StandardUserSystemClock14GetCurrentTimeEPNS0_9PosixTimeE", ov_current_time);
HOST_OVERRIDE("_ZN2nn4time26StandardNetworkSystemClock14GetCurrentTimeEPNS0_9PosixTimeE", ov_current_time);
HOST_OVERRIDE("_ZN2nn4time19StandardSteadyClock3nowEv", ov_steady_now);
HOST_OVERRIDE("_ZN2nn4time14ToCalendarTimeEPNS0_12CalendarTimeEPNS0_22CalendarAdditionalInfoERKNS0_9PosixTimeE",
ov_to_calendar);
HOST_OVERRIDE("_ZN2nn4time19ToCalendarTimeInUtcERKNS0_9PosixTimeE", ov_to_calendar_utc);
HOST_OVERRIDE("_ZN2nn4time18ToPosixTimeFromUtcERKNS0_12CalendarTimeE", ov_to_posix_utc);
HOST_OVERRIDE("_ZN2nn4time13ClockSnapshotC1Ev", ov_snapshot_init);
HOST_OVERRIDE("_ZN2nn4time13ClockSnapshot29CreateWithStandardSystemClockEPS1_", ov_snapshot_create);
HOST_OVERRIDE("_ZN2nn4time20CalculateSpanBetweenEPNS_8TimeSpanERKNS0_13ClockSnapshotES5_", ov_span_between);
HOST_OVERRIDE("_ZN2nn4time48CalculateStandardUserSystemClockDifferenceByUserERKNS0_13ClockSnapshotES3_",
ov_user_difference);
HOST_OVERRIDE("_ZN2nn2pl22GetSharedFontLoadStateENS0_14SharedFontTypeE", ov_font_state);
HOST_OVERRIDE("_ZN2nn2pl20GetSharedFontAddressENS0_14SharedFontTypeE", ov_font_address);
HOST_OVERRIDE("_ZN2nn2pl17GetSharedFontSizeENS0_14SharedFontTypeE", ov_font_size);