mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
947 lines
42 KiB
C
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(¬ify_resume, (int32_t)(arg_x(c, 0) & 0xff)); }
|
|
static void ov_operation_enabled(Cpu *c) { plat_xchg32(¬ify_operation, (int32_t)(arg_x(c, 0) & 0xff)); }
|
|
static void ov_performance_enabled(Cpu *c) { plat_xchg32(¬ify_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);
|