Files

947 lines
42 KiB
C

// Adapted from MacNativeGames games/totk/runtime/services.c (lines 208-302), applet_host.c, account.c, time_host.c and
// shared_font.c @ b75be8cf (MIT, Copyright (c) 2026 MacNativeGames contributors).
// 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:
// TOTK_LANG=<Switch language code> the game's language as given ("ja", "en-GB", "zh-Hant", ...)
// TOTK_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
// TOTK_OPERATION_MODE=docked|handheld operation and performance mode reported to the game (default docked)
// TOTK_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
// TOTK_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 // MacNativeGames' 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)
// TOTK_OPERATION_MODE, read once: 1 handheld (OperationMode 0, PerformanceMode Normal 0), 0 docked (1, Boost 1).
int ov_handheld(void) {
static volatile int32_t v = -1;
int32_t h = v;
if (h < 0) {
const char *e = cfg_value("TOTK_OPERATION_MODE");
if (!strcmp(e, "docked")) h = 0;
else if (!strcmp(e, "handheld")) h = 1;
else rt_fatal("TOTK_OPERATION_MODE must be docked or handheld, not '%s'", e);
if (plat_cas32(&v, h, -1) == -1)
rt_log("oe: operation mode reported to the game: %s", h ? "handheld (performance normal)"
: "docked (performance boost)");
}
return (int)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, "pt"))
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" : "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 (TOTK_LANG, else TOTK_LOCALE, else the Windows user locale). TotK
// 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 totk.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"};
const char *code = cfg_get("TOTK_LANG"); // no fixed default: TOTK_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("TOTK_LANG '%s' is not a Switch language code", code);
}
const char *tag = cfg_get("TOTK_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);
}
static void ov_oe_initialize(Cpu *c) { (void)c; rt_log("oe: application environment: a local host process"); }
// 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 TOTK_ASSUME_FOCUS=1, the default, keeps it at 1, as MacNativeGames does; 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("TOTK_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
// 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 (MNG services.c host_aoc_list): 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);
}
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.
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);
if (size <= 0) {
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: new local account in %s", 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 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). TotK 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,
// as in MNG. 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. TOTK_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("TOTK_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 totk.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 (MNG extract_shared_fonts.py bfttf_size).
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 TOTK_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
HOST_OVERRIDE("_ZN2nn2oe10InitializeEv", ov_oe_initialize);
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("_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("_ZN2nn3aoc16ListAddOnContentEPiii", ov_aoc_list);
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);