re: the 60 duplicated functions are static-object constructors

Each of the 60 builds a 40-element array of 24-byte string objects: addis
0x820B + addi fetches the literal, addi r30,r11,12944 fixes the array
(0x828E3290 for the first), bl sub_8217FA08(dest, literal, -1) assigns, the
rest of the object is zeroed, addi r3,r30,24 steps on.

Why they have no callers: each appears exactly twice in the image - once in
the .pdata unwind table, where EVERY function appears as (address, prolog/
length word) and reading it as a registry is a trap - and once as a slot of a
null-terminated 182-entry function-pointer array at 0x82870018-0x828702EC,
170 of whose targets have zero direct xrefs. That is the static-initialiser
list. Zero callers here means "static ctor", not "dead code".

The teardown side confirms it: 39-43 functions sit between consecutive clones
(median 40), the dominant size in 0x8281xxxx-0x8284xxxx is 11 instructions
(2280 of them, about 60 x 40), and three consecutive ones call sub_823F3D68
on 0x828E3290, +24, +48 - one destructor thunk per element, same stride, same
global, all zero-xref.

rot_n001 and rou_e202 ARE on the disc: a raw byte sweep finds rot_n001 26
times in 7 archives (DefTables included) and rou_e202 6 in 6, exactly one per
GP_MAIN_GAME_<lang> pack. Controls: rou_e201 151/8, rou_e901 917/8, mob_n500
46/8. Narrows the earlier claim to "not GameResourceID values".

Not settled: what reads the arrays. An exact-address search returns 2347
functions outside the 60 - too broad to be a consumer list; the globals sit in
a dense data region and that route has no power.

All fifteen artefacts byte-identical.
This commit is contained in:
Sylpheed RE agent
2026-08-28 00:59:34 +00:00
parent 105ccab038
commit 692a8045f0
2 changed files with 97 additions and 0 deletions

View File

@@ -5617,3 +5617,46 @@ byte-identical on a second run. Written up in
**Both of the solver's named false-positive modes are now closed — one real
(`r0`), one refuted (the 64K flag).** What remains unmeasured is the 146 rows the
positive test is silent on.
---
## ✅🟡 2026-08-28 — the 60 duplicated functions are static-object constructors
Item (a), continued from the duplication finding. **What they are is settled;
what reads them is not.**
✅ **Each of the 60 is a static-object constructor building a 40-element array of
24-byte string objects.** `addis r11, r0, 0x820B` + `addi r4, r11, -9584` fetches
the literal, `addi r30, r11, 12944` fixes the array (`0x828E3290` for the first),
`bl sub_8217FA08(dest, literal, -1)` assigns, `stw` zeroes `+4…+20`, and
`addi r3, r30, 24` steps to the next element.
**Why they have no callers.** Each clone appears exactly **twice** in the image:
once in the **`.pdata` unwind table** — where every function appears as
`(address, prolog/length word)`; reading that as a registry is a trap I nearly
fell into — and once as a slot of a **null-terminated 182-entry function-pointer
array at `0x82870018``0x828702EC`**, of whose targets **170 have zero direct
xrefs**. That is the static-initialiser list, dispatched indirectly and run before
`main`. **Zero callers here means "static ctor", not "dead code".**
**The teardown side confirms the shape.** Between consecutive clones sit
**3943 functions (median 40)**, and the dominant size in the whole
`0x8281xxxx``0x8284xxxx` region is **11 instructions, 2 280 of them** ≈ 60 × 40.
Three consecutive ones call `sub_823F3D68` on `0x828E3290`, `+24`, `+48` — one
destructor thunk per element, same stride, same global, all zero-xref. So the unit
is **one ctor + 40 one-line dtor thunks, emitted 60 times**.
**`rot_n001` and `rou_e202` are on the disc after all.** A raw byte sweep over
every decompressed pak entry: **`rot_n001` 26 occurrences in 7 archives**
(`DefTables` included), **`rou_e202` 6 in 6** — exactly one per
`GP_MAIN_GAME_<lang>` pack and none in `DefTables`. Controls in the same sweep:
`rou_e201` 151/8, `rou_e901` 917/8, `mob_n500` 46/8. Neither is image-only;
`rou_e202` is just the roster's rarest member. Withdraws the "two names the image
carries that the disc never uses" reading — they are not `GameResourceID` values,
which is a narrower statement than I made.
🟡 **Not settled: what reads the arrays.** An exact-address search finds **2 347**
functions outside the 60 that materialise one of the 60 global addresses — far too
broad to be a consumer list. The globals sit in a dense data region, so that route
has no power; the consumer needs a different one. Also unsettled: what selects 40
of the disc's 172 `rou_*` ids, and which disc field holds `rot_n001`/`rou_e202`.