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).
115 lines
8.7 KiB
C
115 lines
8.7 KiB
C
// oe state notification unit test: when not consumed, each type keeps only its first queue position; after a pop a new change is queued again.
|
|
// The three messages only signal "query the current state" and carry no historical value. Expected order/state are hand-written, not generated by the implementation.
|
|
#ifndef HLE_OE_SOURCE
|
|
#define HLE_OE_SOURCE "../hle_oe.c"
|
|
#endif
|
|
#include HLE_OE_SOURCE
|
|
#include <signal.h>
|
|
#include <sys/wait.h>
|
|
uint64_t now_ns(void) { return 0; }
|
|
void dm_audio_suspend(int on) { (void)on; } // stubs for the bg item of DM_OE_SCRIPT (hle_audio.c / hle_os.c / hle_nvn.c)
|
|
void guest_clock_freeze_for(int reason, int on) { (void)reason; (void)on; }
|
|
void dm_gfx_suspend(int on) { (void)on; }
|
|
// The notification event (hle_thread.c binds a kernel event): the unit test only tracks whether it is signalled
|
|
static int ev_bound, ev_sig;
|
|
void hle_bind_system_event(void *g, int autoclear) { (void)g; (void)autoclear; ev_bound = 1; }
|
|
void hle_signal_system_event(void *g) { (void)g; ev_sig = 1; }
|
|
void hle_clear_system_event(void *g) { (void)g; ev_sig = 0; }
|
|
static int checks, fails;
|
|
#define CHECK(c, s) do { checks++; int ok = !!(c); fails += !ok; printf("%s %s\n", ok ? "✓" : "✗", s); } while (0)
|
|
static void reset(void) { g_mqh = g_mqn = 0; g_focus = 1; g_opmode = 1; g_ntf_opmode = g_ntf_perfmode = 0; }
|
|
static int pop_is(uint32_t want) { uint32_t m = 999; return h_oe_TryPopMsg(&m) && m == want; }
|
|
static int empty(void) { uint32_t m = 999; return !h_oe_TryPopMsg(&m) && m == 999; }
|
|
static int child_kind;
|
|
static void abort_exit(int sig) { (void)sig; dprintf(2, "[RED] t_oe child kind=%d: 10 s watchdog timeout\n", child_kind); _exit(97); } // the old-version die in the child process can be checked, avoiding a crash report or core
|
|
static void *focus_burst(void *p) { (void)p; for (int i = 0; i < 17; i++) { dm_focus_set(2); dm_focus_set(1); } return NULL; }
|
|
static void *mode_burst(void *p) { (void)p; for (int i = 0; i < 17; i++) dm_set_opmode(i % 2); return NULL; }
|
|
static pthread_mutex_t stage_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
static pthread_cond_t stage_cond = PTHREAD_COND_INITIALIZER;
|
|
static int stage, interleaved_ok;
|
|
static void stage_set(int n) { pthread_mutex_lock(&stage_lock); stage = n; pthread_cond_broadcast(&stage_cond); pthread_mutex_unlock(&stage_lock); }
|
|
static void stage_wait(int n) { pthread_mutex_lock(&stage_lock); while (stage < n) pthread_cond_wait(&stage_cond, &stage_lock); pthread_mutex_unlock(&stage_lock); }
|
|
static void *consume_interleaved(void *p) {
|
|
(void)p; stage_wait(1); int ok = pop_is(15); stage_set(2); // popped but not yet queried: let the producer update exactly here
|
|
stage_wait(3); interleaved_ok = ok && h_oe_GetFocus() == 1 && pop_is(15) && pop_is(30) && pop_is(31) && empty() && h_oe_GetOpMode() == 0 && h_oe_GetPerfMode() == 0;
|
|
return NULL;
|
|
}
|
|
static int child_burst(int kind) {
|
|
fflush(stdout); pid_t p = fork(); if (p < 0) return 0;
|
|
if (!p) {
|
|
child_kind = kind; signal(SIGABRT, abort_exit); signal(SIGALRM, abort_exit); alarm(10); reset();
|
|
g_ntf_opmode = g_ntf_perfmode = 1;
|
|
if (kind == 0) { focus_burst(NULL); _exit(h_oe_GetFocus() == 1 && pop_is(15) && empty() ? 0 : 1); }
|
|
if (kind == 1) { mode_burst(NULL); _exit(h_oe_GetOpMode() == 0 && h_oe_GetPerfMode() == 0 && pop_is(30) && pop_is(31) && empty() ? 0 : 1); }
|
|
if (kind == 3) {
|
|
pthread_t c; pthread_create(&c, NULL, consume_interleaved, NULL);
|
|
dm_focus_set(2); stage_set(1); stage_wait(2);
|
|
dm_focus_set(1); dm_set_opmode(0); stage_set(3); pthread_join(c, NULL);
|
|
_exit(interleaved_ok ? 0 : 1);
|
|
}
|
|
pthread_t f, m; pthread_create(&f, NULL, focus_burst, NULL); pthread_create(&m, NULL, mode_burst, NULL);
|
|
pthread_join(f, NULL); pthread_join(m, NULL);
|
|
uint32_t msg; int nf = 0, no = 0, np = 0, order = 1;
|
|
while (h_oe_TryPopMsg(&msg)) { if (msg == 15) nf++; else if (msg == 30) no++; else if (msg == 31) { order &= no == 1; np++; } else order = 0; }
|
|
_exit(nf == 1 && no == 1 && np == 1 && order && h_oe_GetFocus() == 1 && h_oe_GetOpMode() == 0 ? 0 : 1);
|
|
}
|
|
int st; if (waitpid(p, &st, 0) != p) return 0;
|
|
return WIFEXITED(st) && WEXITSTATUS(st) == 0;
|
|
}
|
|
int main(void) {
|
|
unsetenv("DM_OE_SCRIPT"); reset();
|
|
CHECK(empty(), "empty queue at start: returns false and leaves the output alone");
|
|
dm_focus_set(1); dm_set_opmode(1); CHECK(empty(), "same state sends no notification");
|
|
dm_focus_set(3); CHECK(h_oe_GetFocus() == 3, "background state 3 can be queried before popping");
|
|
CHECK(pop_is(15) && empty(), "background change notification 15 exactly once");
|
|
dm_focus_set(2); dm_focus_set(1);
|
|
CHECK(pop_is(15) && empty() && h_oe_GetFocus() == 1, "unpopped focus-lost/resume merged, final focus queried as 1");
|
|
reset(); g_ntf_opmode = g_ntf_perfmode = 1;
|
|
dm_focus_set(2); dm_set_opmode(0); dm_focus_set(3); dm_set_opmode(1);
|
|
CHECK(pop_is(15) && pop_is(30) && pop_is(31) && empty(), "first positions of the three kinds FIFO=15,30,31, repeats do not move them");
|
|
CHECK(h_oe_GetFocus() == 3 && h_oe_GetOpMode() == 1 && h_oe_GetPerfMode() == 1, "repeated notifications keep no stale value, latest 3/1/1 queried");
|
|
reset(); g_ntf_opmode = g_ntf_perfmode = 1;
|
|
dm_set_opmode(0); dm_focus_set(2); dm_set_opmode(1);
|
|
CHECK(pop_is(30) && pop_is(31) && pop_is(15) && empty(), "when the mode changes first FIFO=30,31,15, the pair order is unchanged");
|
|
reset(); g_ntf_opmode = g_ntf_perfmode = 1;
|
|
dm_focus_set(2); CHECK(pop_is(15), "focus notification pops first");
|
|
dm_set_opmode(0); dm_focus_set(1);
|
|
CHECK(pop_is(30) && pop_is(31) && pop_is(15) && empty(), "a resume after the pop notifies again and queues after the pending mode messages");
|
|
CHECK(h_oe_GetFocus() == 1, "current focus after resume=1");
|
|
reset(); dm_set_opmode(0);
|
|
CHECK(empty() && h_oe_GetOpMode() == 0, "notifications off only update state, nothing queued");
|
|
h_oe_SetOpModeNtf(0x100); h_oe_SetPerfModeNtf(0x101); dm_set_opmode(1);
|
|
CHECK(pop_is(31) && empty(), "notification bool reads only the low 8 bits, performance alone sends only 31");
|
|
h_oe_SetOpModeNtf(0x101); h_oe_SetPerfModeNtf(0x100); dm_set_opmode(0);
|
|
CHECK(pop_is(30) && empty(), "operation mode alone sends only 30");
|
|
reset(); mq_push(16); mq_push(16);
|
|
CHECK(pop_is(16) && pop_is(16) && empty(), "per-occurrence event 16 not merged, both kept in FIFO");
|
|
reset(); g_ntf_opmode = g_ntf_perfmode = 1; dm_set_opmode(0);
|
|
CHECK(pop_is(30), "of the mode pair 30 popped first, 31 not yet");
|
|
dm_set_opmode(1);
|
|
CHECK(pop_is(31) && pop_is(30) && empty() && h_oe_GetOpMode() == 1, "popping across a new change: old 31 keeps its place, popped 30 queued again, final mode 1");
|
|
CHECK(child_burst(0), "34 focus changes across the old 32 slots, no abort, final resume 1 / one 15");
|
|
CHECK(child_burst(1), "17 mode changes across the old 32 slots, no abort, final 0 / 30,31");
|
|
CHECK(child_burst(2), "two concurrent producers, exactly one of each kind, 30 before 31, final focus 1 / mode 0");
|
|
reset(); g_ntf_opmode = g_ntf_perfmode = 1; int rotated = 1;
|
|
for (int i = 0; i < 31; i++) { dm_focus_set(2 - i % 2); rotated &= pop_is(15) && empty(); }
|
|
CHECK(rotated, "31 pops one at a time, moving the ring head to the last slot");
|
|
dm_focus_set(1); dm_set_opmode(0); dm_focus_set(3); dm_set_opmode(1);
|
|
CHECK(pop_is(15) && pop_is(30) && pop_is(31) && empty(), "after wrapping across 32 slots still deduplicated by 15,30,31 in first-seen order");
|
|
CHECK(child_burst(3), "new change after the consumer pops and before it queries: the new resume is not missed, final focus 1 / mode 0");
|
|
// The notification event follows the queue, as the am service's does: signalled while a message is queued, cleared when the last one is popped. Monster Hunter Rise's
|
|
// MessageSuperviseThread (main+0x6bc8fc4) waits on it with WaitAny, which does not clear it, then pops until the queue is empty: a signal left behind made it spin on a core
|
|
reset(); g_ntf_opmode = g_ntf_perfmode = 1;
|
|
h_oe_GetMsgEv();
|
|
CHECK(ev_bound && !ev_sig, "notification event: bound on first use, not signalled while the queue is empty");
|
|
dm_focus_set(2);
|
|
CHECK(ev_sig, "notification event: signalled when a notification is queued");
|
|
CHECK(pop_is(15) && !ev_sig, "notification event: cleared when the last notification is popped");
|
|
dm_focus_set(1); dm_set_opmode(0);
|
|
CHECK(pop_is(15) && ev_sig, "notification event: still signalled after a pop that leaves notifications queued");
|
|
CHECK(pop_is(30) && ev_sig && pop_is(31) && !ev_sig, "notification event: cleared only when the queue is empty");
|
|
ev_sig = 1;
|
|
CHECK(empty() && !ev_sig, "notification event: a pop from an empty queue clears a signal left behind");
|
|
printf("== oe %d checks, %d failed ==\n", checks, fails); return fails != 0;
|
|
}
|