mission-phase-advance.md still carried "🔴 Not settled: where the script bytecode
lives". It was settled, in a sibling file I had never opened:
structures/mission-script-ssb.md, which says in its own opening that this file
"recorded the bytecode as not on the disc under any obvious name. It is on the
disc."
Verified independently before correcting: name_hash resolves Stage\script.tbl (the
manifest, 838 B compressed) and 28 Stage\StageNN.ssb records -- S01-S16, S18-S29 --
all in dat/GP_MAIN_GAME_S.pak, with Stage\Stage17.ssb absent, matching the loader
guard sub_8225EC78 (n == 16 || n > 32).
Recorded WHY the last three iterations' searches could not have worked, since that
is the reusable part: MISSION1..33 and MISSION_*_PRT are manifest FIELD KEYS, not
record names. The records are named Stage\StageNN.ssb and the manifest maps
between them, so probing the record namespace with field-key names cannot hit --
2148 hashes over 41 paks and 26443 records returned zero for that reason alone.
Two side findings survive: the .embsec_ sections hold PPC code, not bytecode; and
the archive lookup keys by tag_hash (0x00FFFFDF) while the pak record index uses
name_hash -- not interchangeable.
0x82448AA0 and 0x82448C50 are not strcmp. Both pass the name to 0x82447DF0 and
use the result as a key: the first binary-searches a table of 16-byte records
(x16 for the end, /16 for the count, >>1 for the midpoint), the second packs the
hash into a three-word key and calls 0x8244E338.
Decoded 0x82447DF0 from the disassembly as ((sum of extsb bytes) & 0xFF) << 24 |
(rolling mod 0x00FFFFDF) -- i.e. tag_hash. tools/re-capture/unitgroup.py::tag_hash
already documents itself as "a transcription of sub_82447DF0", so this was in the
corpus; the useful part is that it identifies which hash the ARCHIVE uses.
That exposed a defect in my previous sweep: it probed name_hash (0x00FFF9D7,
lowercased) only, while the archive keys by tag_hash (0x00FFFFDF, case-sensitive).
Re-ran with BOTH: 1380 names -> 2148 distinct hashes, 41 paks, 26443 records.
STILL ZERO. The pak-record hypothesis is now refuted with the right hash rather
than merely unsupported.
Left open: the XEX's compressed/encrypted region (default.xex never decrypted
here), plus two untried static threads -- the second pair of SCRIPTS/GP_SCRIPT
references at 0x82262374 / 0x822622bc, in a different function, and tracing
[r31+80] back to whoever opened the archive being name-tested.
My earlier negative was under-scoped: it probed 35 paks, missing hidden/resource3d/
and dat/movie/. Redone across every *.pak on the disc -- 41 archives, 26443
indexed records, 768 distinct name hashes -- still ZERO hits. The scripts are not
a pak record under any of those names.
Located the three loader strings by VA (the .pe is a flat VA dump, VA = 0x82000000
+ offset) and pulled their xrefs from sylpheed.db:
SCRIPTS 0x820a823c <- 0x8225f1b8, 0x82262374
GP_SCRIPT 0x820a8244 <- 0x8225f168, 0x822622bc
MISSION1 0x820a8264 <- 0x8225eed8
Disassembling sub_8225EE20 shows both archive-name references call THE SAME
routine 0x82448AA0 with (object, string) and test the result with cmpi -- so the
loader COMPARES a name against an already-open archive rather than building a path
like dat\GP_SCRIPT.pak. That fits GP_SCRIPT being a name an archive reports,
which is why no such file exists to find.
Next concrete steps recorded: identify 0x82448AA0 (strcmp/strstr/hash-compare) and
its neighbours 0x82448C50 / 0x8216F218, and trace [r31+80] -- the object being
name-tested -- back to whoever opened it. That names the container.
Both candidates this file named for the missing phase-script bytecode were tested
statically, from the flat-VA .pe and the extracted paks.
REFUTED: the seven .embsec_ sections hold PPC CODE. Parsed from the section table
(offset 592, 40 bytes apart) they total 129472 bytes, matching this file's own
"~130 KB" estimate, so they are the right sections -- but six of seven begin
7d8802a6 (mflr r12) and all carry the standard prologue (stwu r1,-N(r1), std
r30,-16(r1), bl). That is not bytecode for a 147-builtin VM.
Also recorded: ".embsec_P" from `strings` is a false lead -- the name field is
exactly ".embsec_" and the P is byte 0x50 of the following VirtualSize (0x1350 =
4944, the fifth section's size).
CORRECTION: this file says grepping for MISSION_START_PRT "returns nothing". That
grep was over the DISC EXTRACTION; all five MISSION_*_PRT names are present in the
executable image, in an .rdata table reading "SCRIPTS" "GP_SCRIPT" "script load
cancel\n" "MISSION1".."MISSION33".
NARROWED: the paks are name-hash addressed, so names can be probed rather than
eyeballed. 616 distinct hashes -- MISSION1..33 and the five MISSION_*_PRT under
prefixes SCRIPTS\, GP_SCRIPT\, scripts\, script\, SCRIPT\ and none, with suffixes
.prt/.PRT/.scr/.bin and none -- across all 35 paks: ZERO hits.
Left open: the XEX's compressed/encrypted region, or a name outside those guesses.
GP_SCRIPT is the strongest remaining thread -- referenced by code, absent from disc.
Closes the open question from mission-phase-membership.md. The static sweep
found no trigger because there is none in the data: each phase ends when its own
per-mission script says so.
[ScriptMission+40] is the 1/2/3 phase ordinal -- initialised at 0x822606B0 and
incremented at exactly one site, 0x822609F8-0x82260A00 (checked: only one stw to
40(rN) in the whole state machine). Its guard is [ScriptPhase+196] != 0, and
that flag has only two writers, vtable slots 0 and 1, reached only from built-ins
6 and 62 of the phase-script VM's 147-entry command table. Built-in 39 sets
[phase+300]=2, which ends the mission instead of advancing.
Refutes all four candidate triggers: no kill counter, timer, trigger volume or
message event appears on the path. Any such condition lives inside the script,
which is why three phases of one stage can have three different clear conditions.
CScriptInterpreter::ChangePhase (sub_822FF330, opcode 995) is the consumer, not
the cause; it writes a second phase mirror at [*(0x828F35F8)+236] -- a probe
target.
Not settled: the script bytecode is not on the disc under any obvious name.
Tooling correction: the .pe is not stale, it is a flat VA image (offset = VA -
0x82000000), verified 7/7 against the DB. Also: instructions.raw is an INTEGER,
and decoding it as a hex string silently compares nothing -- that nearly got
this correction recorded backwards.