D2R (Diablo II Resurrected) has no profile and its code could not run here: the host-call svcs its cuts emitted only died, the touch-run-v4 rule set needed a missing dm/PRODUCT, and the host files its hooks called were never in this repository. Removed with it: - rewriter: D2R cuts, observation gates and parameter slots, the touch-run-v4 rule set and private gates, the boot list, the cut scope column and the KRES / KASPECT key slots (fixups.tsv keeps the four thread slots; no params.tsv any more); dm/tools/product.sh - host: D2R save edits, add-on content, render-scale controller, time() takeover and Terror Zone clock, crash decoding at D2R offsets, host-call registry and stubs, i18n, UI-screen, shell-app loader, zero-stripped archives, boot profiling hooks with no implementation; D2R headers, tests and scripts; dm/ios/Sources/dm_touch_controls; dm/third_party/libdeflate - graphics: runtime extrapolation, world freeze, iPad UI shift, shop drag image, brightness LUT, visual ruler, bloom / FXAA shader hashes, DM_GFXUSE, the eco-fps setting, the loading-screen gate; shader_map.py, nometal_run.py, nvn_slots.py, nvn_slots.tsv and the PC-texture samples The legacy frame step (frame_step legacy, DM_FRAME_STEP) is retired: the Breath of the Wild and Tears of the Kingdom stubs lose their mode test. Breath of the Wild's interval-controller cuts stay: the framework keeps the controller's interval while the game's VFR override holds. patch_nso.py output is byte-identical for all 15 games on this machine except the two frame-step stubs; all 18 profiles build; the host tests pass (their Makefile now builds the general tests only).
99 lines
5.7 KiB
C
99 lines
5.7 KiB
C
// Import resolution (implemented ones go to the host, the rest go to named stubs) and host gate dispatch.
|
||
#include "dm.h"
|
||
#include <mach-o/getsect.h>
|
||
#include <mach-o/ldsyms.h>
|
||
#include <dlfcn.h>
|
||
#include <time.h>
|
||
|
||
typedef struct { const char *name; void *fn; } hle_ent;
|
||
|
||
// ---------- Unimplemented imports: named stubs (the statically assembled stub table in entry.S, the k-th = movz x9,#k; b unimpl_entry) ----------
|
||
#define MAX_STUB 4096 // Matches the .rept count in entry.S
|
||
static unsigned g_nstub;
|
||
static const char *g_stub_name[MAX_STUB];
|
||
static uint8_t g_stub_seen[MAX_STUB];
|
||
extern uint64_t unimpl_stubs[MAX_STUB]; // 8 bytes per stub
|
||
|
||
uint64_t unimpl_c(unsigned idx, uint64_t *regs) {
|
||
if (!g_stub_seen[idx]) { // once per stub (a counter in the byte wrapped every 256 calls: Ace Attorney Investigations Collection calls nnProfilerRecordHeartbeat every frame)
|
||
g_stub_seen[idx] = 1;
|
||
LOG("UNIMPL %s (x0=%#llx x1=%#llx x2=%#llx)", g_stub_name[idx], regs[0], regs[1], regs[2]);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
// Per module, one cache of symbol number → resolved address
|
||
static uint64_t g_cache[MAX_MOD][8192];
|
||
|
||
uint64_t resolve_import(module_t *m, const char *name) {
|
||
static const hle_ent *sec; static unsigned long secsz;
|
||
if (!sec) sec = (const hle_ent *)getsectiondata(&_mh_execute_header, "__DATA", "swhle", &secsz);
|
||
for (unsigned i = 0; i < secsz / sizeof(hle_ent); i++)
|
||
if (!strcmp(sec[i].name, name)) {
|
||
if (g_flag.imports) { Dl_info di; dladdr(sec[i].fn, &di); LOG("import %s -> %s", name, di.dli_sname); }
|
||
void *hlecount_wrap(const char *, const char *, void *); return (uint64_t)hlecount_wrap("HLE", name, sec[i].fn); // With DM_HLECOUNT it is a counting stub (including the libc passthrough table)
|
||
}
|
||
// Reuse the stub if one was already allocated for the same name
|
||
void *hlecount_wrap(const char *, const char *, void *);
|
||
for (unsigned k = 0; k < g_nstub; k++)
|
||
if (!strcmp(g_stub_name[k], name)) return (uint64_t)hlecount_wrap("STUB", name, (void *)&unimpl_stubs[k]);
|
||
if (g_nstub >= MAX_STUB) die("stub overflow");
|
||
unsigned k = g_nstub++;
|
||
g_stub_name[k] = name;
|
||
(void)m;
|
||
return (uint64_t)hlecount_wrap("STUB", name, (void *)&unimpl_stubs[k]);
|
||
}
|
||
|
||
// The host implements this import (the swhle table), without allocating a stub for it
|
||
int hle_implements(const char *name) {
|
||
unsigned long secsz; const hle_ent *sec = (const hle_ent *)getsectiondata(&_mh_execute_header, "__DATA", "swhle", &secsz);
|
||
for (unsigned i = 0; sec && i < secsz / sizeof(hle_ent); i++) if (!strcmp(sec[i].name, name)) return 1;
|
||
return 0;
|
||
}
|
||
|
||
void stubs_report(void) { LOG("stubs=%u", g_nstub); }
|
||
|
||
// ---------- Host gate dispatch ----------
|
||
// The rewriter replaces every svc #n / mrs cntpct with a call to dm_gate; here we remember, by gate block return point (absolute address), what it originally was.
|
||
// Lookup: open-addressing hash, one hit (it used to do module_of + a linear compare over 110 entries on every gate call, keyed by in-module offset, which would collide across modules).
|
||
enum { G_SVC, G_CNTPCT, G_CNTVCT, G_OBS };
|
||
// Observation gates (inserted offline from the rewriter's OBS table): number → read-only callback; with no callback attached the gate returns after one pass in and out
|
||
static void (*g_obs_fn[OBS_MAX])(const uint64_t *regs); static int g_obs_have[OBS_MAX];
|
||
void gate_obs_set(int id, void (*fn)(const uint64_t *regs)) { if (id <= 0 || id >= OBS_MAX) die("observation gate number out of range %d", id); g_obs_fn[id] = fn; }
|
||
int gate_obs_present(int id) { return id > 0 && id < OBS_MAX && g_obs_have[id]; }
|
||
typedef struct { uint64_t ret; int kind, arg; uint64_t orig; } gate_t;
|
||
#define GATE_BITS 12 // 4096 slots, gate count ≤ 1024, load factor ≤ 1/4
|
||
static gate_t g_gate[1u << GATE_BITS];
|
||
static int g_ngate;
|
||
static inline unsigned gate_h(uint64_t a) { return (unsigned)(((a >> 2) * 0x9E3779B97F4A7C15ull) >> (64 - GATE_BITS)); }
|
||
|
||
void gate_register(uint64_t ret_abs, const char *kind, int arg, uint64_t orig) {
|
||
int k = !strcmp(kind, "svc") ? G_SVC : !strcmp(kind, "cntpct") ? G_CNTPCT : !strcmp(kind, "cntvct") ? G_CNTVCT : !strcmp(kind, "obs") ? G_OBS : -1;
|
||
if (k < 0) die("unknown gate kind %s", kind);
|
||
if (k == G_OBS) { if (arg <= 0 || arg >= OBS_MAX) die("observation gate number out of range %d", arg); g_obs_have[arg] = 1; }
|
||
if (++g_ngate > (1 << GATE_BITS) / 4) die("gate overflow");
|
||
unsigned i = gate_h(ret_abs);
|
||
while (g_gate[i].ret) { if (g_gate[i].ret == ret_abs) die("gate registered twice %#llx", ret_abs); i = (i + 1) & ((1u << GATE_BITS) - 1); }
|
||
g_gate[i] = (gate_t){ret_abs, k, arg, orig};
|
||
}
|
||
|
||
uint64_t svc_dispatch(int num, uint64_t *regs, uint64_t orig_addr); // hle_svc.c
|
||
|
||
uint64_t gate_dispatch(uint64_t ret_addr, uint64_t *regs) {
|
||
for (unsigned i = gate_h(ret_addr);; i = (i + 1) & ((1u << GATE_BITS) - 1)) {
|
||
const gate_t *g = &g_gate[i];
|
||
if (g->ret == ret_addr) switch (g->kind) {
|
||
case G_CNTPCT: case G_CNTVCT: {
|
||
uint64_t t = guest_ns() * 12 / 625; // 19.2 MHz system counter; original instruction mrs xN, cntpct_el0, arg = N
|
||
regs[g->arg] = t;
|
||
return t;
|
||
}
|
||
case G_OBS: if (g_obs_fn[g->arg]) g_obs_fn[g->arg](regs); return 0; // Registers are read-only; the x0–x8 that dm_gate writes back are the original values
|
||
default: { // register the gate frame on SVC entry / exit: it is the context of a paused thread (hle_thread.c)
|
||
void thr_svc_enter(uint64_t *, uint64_t), thr_svc_leave(void);
|
||
thr_svc_enter(regs, g->orig); uint64_t r = svc_dispatch(g->arg, regs, g->orig); thr_svc_leave(); return r; }
|
||
}
|
||
if (!g->ret) die("unknown gate at %#llx", ret_addr);
|
||
}
|
||
}
|