mirror of
https://github.com/valentderah/totk-pc-recomp.git
synced 2026-11-11 01:41:02 +00:00
A host thread adds each second in which the window has the focus and the game presents frames, keeps the total in <profile>/playtime.txt (every 30 s and at exit) and the Game tab of the settings menu shows it with this run's share. Texts in English, Russian, Brazilian Portuguese and Simplified Chinese. Closes #720. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
228 lines
8.8 KiB
C
228 lines
8.8 KiB
C
// Adapted from MacNativeGames games/totk/runtime/services.c @ b75be8c (MIT, Copyright (c) 2026 MacNativeGames
|
|
// contributors), lines 304-382.
|
|
// The SVC entry of the lifted code: preemption checkpoint, trace, then the thread and
|
|
// synchronization SVCs (kernel.c), IPC (ipc.c), memory (memory.c), Break, OutputDebugString, GetInfo and ExitProcess. The 21 SVCs
|
|
// TotK has but never reaches on MNG's boot path stop with a named fatal, the caller and the guest backtrace.
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "fs.h"
|
|
#include "host.h"
|
|
#include "kernel.h"
|
|
#include "vk.h"
|
|
#include "platform/platform.h"
|
|
|
|
#define R_OK 0u
|
|
#define ST_FREE 0u
|
|
#define ST_STACK 0xBu
|
|
#define PERM_RW 3u
|
|
#define PROGRAM_ID 0x0100f2c0115b6000ull
|
|
|
|
static uint64_t g_svcs;
|
|
void (*volatile svc_test_exit_hook)(unsigned code);
|
|
|
|
RtStats rt_stats(void) {
|
|
return (RtStats){__atomic_load_n(&g_svcs, __ATOMIC_RELAXED), kern_threads_created(), hostfn_calls()};
|
|
}
|
|
|
|
static int readable(uint64_t a, uint64_t n) { return plat_mem_readable(a, n); }
|
|
|
|
void kern_backtrace(const Cpu *c) {
|
|
char d[160];
|
|
rt_logf(" lr %s\n", ldr_describe(c->x[30], d, sizeof d));
|
|
uint64_t fp = c->x[29];
|
|
for (int n = 0; n < 32 && fp && !(fp & 7) && readable(fp, 16); n++) {
|
|
uint64_t next = LD64(fp), lr = LD64(fp + 8);
|
|
rt_logf(" guest frame %2d: %s\n", n, ldr_describe(lr, d, sizeof d));
|
|
if (next <= fp || next - fp > (64u << 20)) break;
|
|
fp = next;
|
|
}
|
|
}
|
|
|
|
static const struct { uint32_t imm; const char *name; } unimplemented[] = {
|
|
{0x13, "MapSharedMemory"}, {0x14, "UnmapSharedMemory"},
|
|
{0x23, "SendAsyncRequestWithUserBuffer"}, {0x28, "ReturnFromException"}, {0x2A, "FlushEntireDataCache"},
|
|
{0x2F, "GetLastThreadInfo"}, {0x32, "SetThreadActivity"}, {0x33, "GetThreadContext3"},
|
|
{0x36, "SynchronizePreemptionState"}, {0x40, "CreateSession"}, {0x41, "AcceptSession"},
|
|
{0x43, "ReplyAndReceive"}, {0x44, "ReplyAndReceiveWithUserBuffer"}, {0x48, "MapPhysicalMemoryUnsafe"},
|
|
{0x49, "UnmapPhysicalMemoryUnsafe"}, {0x4B, "CreateCodeMemory"}, {0x50, "CreateSharedMemory"},
|
|
{0x51, "MapTransferMemory"}, {0x52, "UnmapTransferMemory"}, {0x70, "CreatePort"}, {0x72, "ConnectToPort"},
|
|
};
|
|
|
|
static __attribute__((noreturn)) void svc_fatal(Cpu *c, uint32_t imm, uint64_t pc, const char *why) {
|
|
const char *name = "unknown";
|
|
for (unsigned i = 0; i < sizeof unimplemented / sizeof *unimplemented; i++)
|
|
if (unimplemented[i].imm == imm) name = unimplemented[i].name;
|
|
char at[160];
|
|
fflush(stdout);
|
|
rt_logf("FATAL: svc #%#x %s at %s: %s\n x0 %#llx x1 %#llx x2 %#llx x3 %#llx\n", imm, name,
|
|
ldr_describe(pc, at, sizeof at), why, (unsigned long long)c->x[0], (unsigned long long)c->x[1],
|
|
(unsigned long long)c->x[2], (unsigned long long)c->x[3]);
|
|
kern_backtrace(c);
|
|
rt_fatal("svc #%#x %s is not implemented", imm, name);
|
|
}
|
|
|
|
// QueryMemory sees the guest main thread's stack, which memory.c does not know: Stack inside it, free ranges
|
|
// around it clipped (the way memory.c clips moved stacks).
|
|
static void main_stack_query(uint64_t a, RtMemoryInfo *r) {
|
|
uint64_t mb, ms;
|
|
kern_main_stack(&mb, &ms);
|
|
if (!ms) return;
|
|
uint64_t me = mb + ms;
|
|
if (a >= mb && a < me) {
|
|
*r = (RtMemoryInfo){.base = mb, .size = ms, .state = ST_STACK, .perm = PERM_RW};
|
|
return;
|
|
}
|
|
if (r->state != ST_FREE) return;
|
|
uint64_t lo = r->base, hi = r->base + r->size;
|
|
if (me <= a && me > lo) lo = me;
|
|
if (mb > a && mb < hi) hi = mb;
|
|
r->base = lo;
|
|
r->size = hi - lo;
|
|
}
|
|
|
|
static int on_main_stack(uint64_t a, uint64_t n) {
|
|
uint64_t mb, ms;
|
|
kern_main_stack(&mb, &ms);
|
|
return ms && a < mb + ms && mb < a + n;
|
|
}
|
|
|
|
static uint64_t user_exception_context(void) {
|
|
static void *page;
|
|
void *p = __atomic_load_n(&page, __ATOMIC_ACQUIRE);
|
|
if (!p) {
|
|
void *mine = mem_kernel_alloc(0x1000, 0x1000), *expected = NULL;
|
|
if (__atomic_compare_exchange_n(&page, &expected, mine, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) p = mine;
|
|
else {
|
|
mem_kernel_free(mine, 0x1000);
|
|
p = expected;
|
|
}
|
|
}
|
|
return (uint64_t)(uintptr_t)p;
|
|
}
|
|
|
|
// GetInfo(w1 id, w2 handle, x3 sub id -> x1)
|
|
static void get_info(Cpu *c, uint64_t pc) {
|
|
uint32_t id = (uint32_t)c->x[1];
|
|
uint64_t v = 0;
|
|
if (mem_info(id, &v)) {
|
|
c->x[0] = R_OK;
|
|
c->x[1] = v;
|
|
return;
|
|
}
|
|
switch (id) {
|
|
case 0: v = 7; break; // core mask
|
|
case 1: v = 0x0ffffffff0000000ull; break; // priority mask
|
|
case 8: v = 0; break; // debugger attached
|
|
case 10: v = 0; break; // idle tick count
|
|
case 11: // random entropy
|
|
if (!plat_random(&v, sizeof v)) rt_fatal("GetInfo 11: no random bytes from the system");
|
|
break;
|
|
case 18: v = PROGRAM_ID; break;
|
|
case 20: v = user_exception_context(); break; // the SDK keeps e.g. the dying message at +0x1C0
|
|
case 23: v = 1; break;
|
|
default: {
|
|
char why[64];
|
|
snprintf(why, sizeof why, "GetInfo id %u (handle %#x, sub id %#llx)", id, (uint32_t)c->x[2],
|
|
(unsigned long long)c->x[3]);
|
|
svc_fatal(c, 0x29, pc, why);
|
|
}
|
|
}
|
|
c->x[0] = R_OK;
|
|
c->x[1] = v;
|
|
}
|
|
|
|
static void svc_body(Cpu *c, uint32_t imm, uint64_t pc);
|
|
|
|
void rt_svc(Cpu *c, uint32_t imm, uint64_t pc) {
|
|
kern_checkpoint();
|
|
if (__builtin_expect(!kern_host_track || !c->thread, 1)) {
|
|
svc_body(c, imm, pc);
|
|
return;
|
|
}
|
|
void *t = c->thread; // kern_host_track: the SVC counted in the thread's host depth (hostfn.c hostfn_tracked)
|
|
int *d = kern_host_depth(t);
|
|
__atomic_store_n(d, *d + 1, __ATOMIC_RELAXED);
|
|
svc_body(c, imm, pc);
|
|
int n = *d - 1;
|
|
__atomic_store_n(d, n, __ATOMIC_RELAXED);
|
|
if (!n && kern_preempt_pending(t)) kern_host_left(t);
|
|
}
|
|
|
|
static void svc_body(Cpu *c, uint32_t imm, uint64_t pc) {
|
|
__atomic_fetch_add(&g_svcs, 1, __ATOMIC_RELAXED);
|
|
if (rt_trace & RT_TRACE_SVC) {
|
|
char at[160];
|
|
rt_log("svc %#x at %s x0=%#llx x1=%#llx x2=%#llx x3=%#llx", imm, ldr_describe(pc, at, sizeof at),
|
|
(unsigned long long)c->x[0], (unsigned long long)c->x[1], (unsigned long long)c->x[2],
|
|
(unsigned long long)c->x[3]);
|
|
}
|
|
if (kern_svc(c, imm)) return;
|
|
uint64_t *x = c->x;
|
|
switch (imm) {
|
|
case 0x15: case 0x1F: case 0x21: case 0x22:
|
|
if (ipc_svc(c, imm)) return;
|
|
svc_fatal(c, imm, pc, "not handled by ipc.c");
|
|
case 0x01: { // SetHeapSize(x1 size -> x1 address)
|
|
uint64_t a = 0;
|
|
uint32_t r = mem_set_heap_size(x[1], &a);
|
|
x[0] = r;
|
|
if (!r) x[1] = a;
|
|
return;
|
|
}
|
|
case 0x02: x[0] = mem_set_permission(x[0], x[1], (uint32_t)x[2]); return;
|
|
case 0x03: x[0] = mem_set_attribute(x[0], x[1], (uint32_t)x[2], (uint32_t)x[3]); return;
|
|
case 0x04: // MapMemory(x0 dst, x1 src, x2 size)
|
|
x[0] = on_main_stack(x[0], x[2]) ? KERN_R_INVALID_STATE_MEM : mem_map(x[0], x[1], x[2]);
|
|
return;
|
|
case 0x05: x[0] = mem_unmap(x[0], x[1], x[2]); return;
|
|
case 0x06: { // QueryMemory(x0 MemoryInfo*, x2 address -> w1 page info)
|
|
RtMemoryInfo mi;
|
|
uint32_t r = mem_query(x[2], &mi);
|
|
main_stack_query(x[2], &mi);
|
|
if (!r && x[0]) memcpy((void *)(uintptr_t)x[0], &mi, sizeof mi);
|
|
x[0] = r;
|
|
x[1] = 0;
|
|
return;
|
|
}
|
|
case 0x2C: x[0] = mem_map_physical(x[0], x[1]); return;
|
|
case 0x2D: x[0] = mem_unmap_physical(x[0], x[1]); return;
|
|
case 0x26: { // Break(x0 reason, x1 info, x2 info size); bit 31 of the reason: notification only
|
|
uint32_t result = 0;
|
|
if (x[1] && x[2] >= 4 && readable(x[1], 4)) memcpy(&result, (const void *)(uintptr_t)x[1], 4);
|
|
char at[160];
|
|
rt_log("guest Break reason %#llx result %#x at %s", (unsigned long long)x[0], result,
|
|
ldr_describe(pc, at, sizeof at));
|
|
if (x[0] & 0x80000000ull) {
|
|
x[0] = R_OK;
|
|
return;
|
|
}
|
|
svc_fatal(c, imm, pc, "Break (the SDK aborted)");
|
|
}
|
|
case 0x27: { // OutputDebugString(x0 text, x1 size)
|
|
uint64_t n = x[1];
|
|
if (n > 65536) svc_fatal(c, imm, pc, "debug string longer than 64 KiB");
|
|
if (n) {
|
|
fflush(stdout);
|
|
rt_log_raw((const char *)(uintptr_t)x[0], n);
|
|
}
|
|
x[0] = R_OK;
|
|
return;
|
|
}
|
|
case 0x29: get_info(c, pc); return;
|
|
case 0x07: { // ExitProcess, as closing the window (gfx_window_closed): pipeline cache, shader records and
|
|
// profile saved, logs flushed, exit code 0
|
|
char at[160];
|
|
rt_log("guest ExitProcess at %s; exiting", ldr_describe(pc, at, sizeof at));
|
|
fs_save_flush();
|
|
vk_pipeline_save();
|
|
playtime_save();
|
|
prof_write();
|
|
fflush(NULL);
|
|
if (svc_test_exit_hook) return svc_test_exit_hook(0);
|
|
plat_exit(0);
|
|
}
|
|
}
|
|
svc_fatal(c, imm, pc, "not implemented");
|
|
}
|