mission-phase-advance.md recorded the bytecode as not on the disc. It is; the earlier grep failed because every pak entry is Z1+zlib, so no name is plaintext. The loader resolves table KEYS, not a filename: GamePart name -> GP_SCRIPT -> script.tbl (name_hash 0x75FE4656), whose SCRIPTS record is a 40-field manifest mapping MISSION1..MISSION29 to StageNN.ssb. Verified: Stage02.ssb is 226,596 bytes, md5 aff69b5a..., byte-identical across all six language paks. 28 scripts exist; S17 is the missing one -- which agrees with the table sweep (S17 in none of the five families) AND with the loader guard sub_8225EC78 'if (n == 16 || n > 32) return', proving that mission number is 0-based. Three independent routes to the same conclusion. Header decoded from ScriptMission::Load rather than guessed: version, code offset 0x24, two symbol tables. Stage02 symtab1 = 326 symbols (197 message ids, 119 route names, 10 subobjectives), symtab2 = 122 (111 unit ids, 11 characters). Two loops close: the 111 unit ids are exactly the 111 UnitGroup_S02 squadrons, and the route names appear as SCRIPT SYMBOLS -- which is why nothing in the executable parses Route_*_p<N>* names. The _pN convention is the script's. Refuted: the .embsec_ sections are code (32,368 instructions, 108 functions), not script; MiscBin.pak and DefTables.pak have zero hits. Open: the 25 ISL opcodes and 147 built-ins are not decoded, and the mission-level stream at +0x24 is only partly read.
6.2 KiB
Stage\StageNN.ssb — the mission scripts, found
Status: ✅ located, container header and both symbol tables decoded, verified against the loader's own disassembly; 🟡 the mission-level instruction stream is only partly read; ❔ the 25 ISL opcodes and 147 built-ins are not yet decoded.
mission-phase-advance established that a phase
ends when its script says so, and recorded the bytecode as "not on the disc
under any obvious name". It is on the disc — the earlier search failed
because every pak entry is Z1 + zlib, so no plaintext name is greppable.
✅ How the loader names it — a table key, not a filename
sub_8225EE20 never opens a file. It resolves a chain of table keys:
0x8225F090 vtbl[1]() on the game-part object -> group index
0x8225F0AC index into 0x820A1630 (29 GamePart names) -> "GP_MAIN_GAME" [17]
0x8225F0D4 prefix "game:\" (0x820A1864)
0x8225F168 look up key GP_SCRIPT (0x820A8244)
0x8225F1B8 look up section SCRIPTS (0x820A823C); 0x824A9088 = fatal if absent
0x8225F1DC sub_824483D0 enumerates that section against the MISSION*/PRT name arrays
dat/tables.pak → name_hash("eng\GP_MAIN_GAME.tbl") carries
GP_SCRIPT.TABLE = script.tbl, and
name_hash("Stage\script.tbl") = 0x75FE4656 is the manifest — an IDXD whose
SCRIPTS record has 40 fields:
MISSION1 = Stage01.ssb … MISSION29 = Stage29.ssb
MISSION_{START,END,UPDATE,FAILED,RESTART}_PRT = pgmsg_*.prt
DIALOG_MESSAGE, DIALOG_LOCAL_STRING, FONT, FONT_SIZE, TEXT_POS, TEXT_LINES
Verified here: the record is present and reads exactly that.
✅ 28 scripts, and the missing one is S17 — a three-way convergence
name_hash("Stage\\StageNN.ssb") resolves for 28 stages: S01–S16, S18–S29.
S17 is absent, and that agrees with two entirely independent findings:
- the table sweep in stage-definition-table — S17 appears in none of the five per-stage table families;
- the loader's own guard,
sub_8225EC78:if (n == 16 || n > 32) return. Index 16 skipped ⇒ the mission number there is 0-based ⇒ it isMISSION17that is skipped.
Three routes to the same conclusion, from data, from a hash lookup and from
control flow. MISSION30..33 exist as literals in the executable but appear in
no manifest and on no disc.
Stage 02, verified byte-for-byte:
dat/GP_MAIN_GAME_E.pak key 0x5C2D6E8B = name_hash("Stage\Stage02.ssb")
226,596 bytes decompressed md5 aff69b5ade4c3c061a132ee5b88ac5a1
Byte-identical across all six language paks, so the scripts are not localised.
✅ Container header (big-endian, 0x24 bytes)
Field meanings taken from ScriptMission::Load (sub_82260568), not guessed:
| off | Stage02 | meaning |
|---|---|---|
+0x00 |
00000002 |
version, always 2 |
+0x04 |
00000000 |
→ [ScriptMission+36] (0x822605A4) |
+0x08 |
00000024 |
code offset → [+24] code base, [+28] pc (0x822605AC) |
+0x0C |
00034C80 |
symbol table 1 → [+44] (0x822605C0) |
+0x10 |
00036D60 |
symbol table 2 |
+0x14…0x23 |
FF×16 |
four -1 slots, patched at runtime |
Both tables are u32 count; u32 offsets[count], offsets relative to the word
after count, each record u32 type; char name[] NUL-padded to 4. A zero
offset is an unused slot.
✅ The symbol tables tie the script to the data tables
Stage 02, parsed here:
| table | count | slots used | types |
|---|---|---|---|
| symtab1 | 359 | 326 | 6 ×197 message ids, 1 ×119 route names, 7 ×10 SUBOBJ_*/eff_* |
| symtab2 | 122 | 122 | 2 ×111 unit ids, 8 ×11 characters |
Two things fall out, and both close loops elsewhere:
- Type 1 symbols are
Route_TCN001_p1F-style names — so the script holds the route names as symbols. That reconciles the finding in mission-phase-advance that *nothing in the executable parsesRoute_*_p<N>*: the parsing never happens because the compiler already resolved them, and the script simply refers to the ones it wants. The_pNconvention is real, and it is the script's, not a runtime parser's. - Type 2 is 111 unit ids (
ADN101,TCN328,NP_Amalthea) — exactly the 111 squadron records instage\UnitGroup_S02.tbl. The script addresses squadrons one-for-one.
Phase-tagged message ids give the phase count directly: Stage 02 carries
S02_P1_*, S02_P2_* and S02_P3_* → 3 phases, matching the stage record.
Stage 01 has two, Stage 29 has one.
🔴 Refuted along the way
- The
.embsec_sections are not the payload. All seven (129,472 B total) decode as 32,368 PPC instructions in 108 functions, each starting with a real prologue (mflr r12; bl <save-regs>). They are code — overlays/math — not script. hidden/MiscBin.pakdoes not hold it — zero hits across its 40 entries, and zero acrossDefTables.pak's 1,465.- Sweeping all 41 paks / 26,443 TOC entries, the manifest strings appear in
exactly one entry per pak,
0x75FE4656, in the sixGP_MAIN_GAME_<L>.pak. Nothing is DLC, generated, or in$SystemUpdate.
❔ Still open: the instruction encoding
- ISL (the
ScriptPhaseVM) — dispatcher confirmed, not decoded:0x822635D4reads a big-endian u32 and takes the opcode from its low byte,cmplwi 0x18⇒ 25 opcodes, jump table0x822635FC. - The mission-level stream at
+0x24is not pinned. Its first 48 words form three near-identical groups — one per Stage 02 phase — each ending in a pair of offsets that land on regions where ≥80 % of low bytes are in0..0x18, i.e. plausible ISL entry points, two per phase. But1883's operand is not uniformly a code pointer: two of them land on IEEE floats (44 8E 80 00= 1140.0,43 B4 00 00= 360.0). Whether offsets are file- or code-base-relative (±0x24) is unsettled.
The next step for the port is decoding the 25 ISL opcodes and the 147
built-ins (0x8227226C) against Stage02.ssb, looking for calls to built-ins
6 (0x82272540) and 62 (0x82272558) — the two that set
[ScriptPhase+196] and so end a phase. That yields the actual per-phase clear
condition for every stage.