Files
Sylpheed/docs/re/structures/mission-script-ssb.md
Sylpheed RE agent 66a8a5de2d re: the mission scripts found -- Stage\StageNN.ssb in GP_MAIN_GAME_<L>.pak
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.
2026-08-25 12:12:59 +00:00

135 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# `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](../mission-phase-advance.md) 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: S01S16, S18S29.
**S17 is absent**, and that agrees with two entirely independent findings:
* the table sweep in [stage-definition-table](stage-definition-table.md) — 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 is `MISSION17`
that 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](../mission-phase-advance.md) that *nothing in the
executable parses `Route_*_p<N>*`: the parsing never happens because the
compiler already resolved them, and the script simply refers to the ones it
wants. The `_pN` convention 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** in `stage\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.pak` does not hold it** — zero hits across its 40 entries,
and zero across `DefTables.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 six `GP_MAIN_GAME_<L>.pak`.
Nothing is DLC, generated, or in `$SystemUpdate`.
## ❔ Still open: the instruction encoding
* **ISL (the `ScriptPhase` VM) — dispatcher confirmed**, not decoded:
`0x822635D4` reads a big-endian u32 and takes the opcode from its **low byte**,
`cmplwi 0x18` ⇒ 25 opcodes, jump table `0x822635FC`.
* **The mission-level stream at `+0x24` is 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 in `0..0x18`, i.e.
plausible ISL entry points, two per phase. But `1883`'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.