diff --git a/crates/xenia-kernel/src/silph_synth.rs b/crates/xenia-kernel/src/silph_synth.rs index 14c21a0..01f5643 100644 --- a/crates/xenia-kernel/src/silph_synth.rs +++ b/crates/xenia-kernel/src/silph_synth.rs @@ -56,9 +56,10 @@ const SILPH_WORKER_ENTRIES: [u32; 4] = [ /// Round 0x440 up to a page-ish so the ctx alloc never straddles a page /// boundary in heap_alloc's bookkeeping. Round 20 grew the alloc from 0x500 /// to 0x800 to make room for a synthesised sub-object at +0x300 and its -/// 32-slot vtable at +0x500 (= ctx + 0x500..0x580). Both are required because -/// the worker bodies dereference `[ctx+0x2C]` to obtain a sub-object pointer -/// and then virtual-dispatch through its slot 15. +/// 32-slot vtable at +0x500 (= ctx + 0x500..0x580). Round 21 retains the +/// embedded sub-object but drops the synthesized vtable (we now point at +/// canary's real XEX-resident sub-vtable directly), so the 0x500..0x580 +/// region is unused but harmless. const SILPH_CTX_SIZE: u32 = 0x800; /// Offset within the ctx allocation of the synthetic sub-object referenced @@ -67,21 +68,20 @@ const SILPH_CTX_SIZE: u32 = 0x800; /// `heap_alloc` covers everything. const SILPH_SUBOBJ_OFFSET: u32 = 0x300; -/// Offset within the ctx allocation of the synthetic sub-object vtable. -const SILPH_SUBVTBL_OFFSET: u32 = 0x500; - -/// Number of slots in the synthetic sub-vtable. The worker bodies access -/// slots 15 and 17 (per audit-059 round 19); 32 slots is generous and gives -/// headroom for any downstream vtable index we haven't surveyed yet. -const SILPH_SUBVTBL_SLOTS: u32 = 32; - -/// Trivial `li r3, 0; blr` stub at VA `0x8216CAA4` in the XEX `.text` -/// section — the first 4-byte-aligned standalone instance located by the -/// round-20 PE scan (preceded by a `blr` ending the previous function). -/// We populate every slot of the synthetic sub-vtable with this address so -/// any virtual call through the sub-object idles, returning NULL work and -/// keeping the worker live. -const SILPH_STUB_RETURN_NULL_VA: u32 = 0x8216_CAA4; +/// XEX `.rdata` VA of canary's real sub-object vtable (audit-059 round 21). +/// Discovered by: +/// 1. Probing canary at `pc=0x82506B08` (= `sub_82506B08`, method 35 of +/// the WorkerCtx vtable, the first sub-object method called by every +/// `sub_82506528/58/88/B8` worker entry). +/// 2. Capturing `[ctx+0x2C]` from the JIT-prolog dump (= sub-object VA +/// in canary's heap). +/// 3. Re-running with `--audit_jit_prolog_mem_dump=` to read +/// `[sub-object + 0]` = sub-vtable VA = **`0x8200A168`**. +/// PE inspection confirms slot 15 (called via `[r11+0x3C]` at +/// `sub_82506B08+0x44`) = `sub_824FCCC8` and slot 17 (`[r11+0x44]` at +/// `sub_82506B08+0x70`) = `sub_824FCE38`. Both are real game methods in +/// the same `.text` region as the rest of the worker dispatch surface. +const SILPH_SUB_VTABLE_SOURCE_VA: u32 = 0x8200_A168; /// Round-19 XEX-resident wrapper constant observed at `[ctx+0x30]` in every /// canary ctx (audit-059 round 7). Same value for all four ctxes — opaque @@ -153,22 +153,20 @@ pub fn spawn_silph_workers( // +0x30 — XEX-resident wrapper constant 0xBE568F00 (round 7). Opaque // but identical across all four canary ctxes. let subobj_ptr = ctx + SILPH_SUBOBJ_OFFSET; - let subvtbl_ptr = ctx + SILPH_SUBVTBL_OFFSET; mem.write_u32(ctx + 0x2C, subobj_ptr); mem.write_u32(ctx + 0x30, SILPH_CTX_FIELD_30_CONST); - // ---- Synthetic sub-object at +0x300 ---- - // Round-19 disassembly shows worker bodies only touch the sub-object's - // vtable (slots 15/17) and treat the rest as opaque. Zero-fill is - // sufficient. The only required field is `[+0]` = sub-vtable pointer. - mem.write_u32(subobj_ptr, subvtbl_ptr); - - // ---- Synthetic sub-vtable at +0x500 ---- - // Every slot points at SILPH_STUB_RETURN_NULL_VA so any virtual call - // through the sub-object idles, returning NULL work without crashing. - for slot in 0..SILPH_SUBVTBL_SLOTS { - mem.write_u32(subvtbl_ptr + slot * 4, SILPH_STUB_RETURN_NULL_VA); - } + // ---- Embedded sub-object at +0x300 ---- + // Round-21 pivot: instead of synthesising a stub vtable that returns + // NULL from every slot, point `[sub_object + 0]` directly at canary's + // real XEX-resident sub-vtable VA. The vtable bytes are part of the + // same static image both engines map, so referring to it costs zero + // guest memory and gives the workers a working virtual-method surface + // (slot 15 = sub_824FCCC8, slot 17 = sub_824FCE38, plus 29 other real + // methods). Round-19 disassembly shows worker bodies only touch the + // sub-object's vtable; the rest of the sub-object is opaque so we + // leave it zero-filled. + mem.write_u32(subobj_ptr, SILPH_SUB_VTABLE_SOURCE_VA); // ---- 4× X_KEVENT auto-reset at +0x54/+0x64/+0x74/+0x84, state = 0 ---- // X_DISPATCH_HEADER layout (canary xobject.h:35):