Entry 24
Event scripts on every volume, and how a save carries into the next
Documented in The four discs, The event interpreter, Event scripts, The memory card save.
Filestools/evscript.pytools/test_evscript.pytools/save.pytools/test_save.pydocs/engine/events.mddocs/formats/save.md
18 decoded Infection's event scripts and 16 its save. Both leaned on the symbol table. This entry makes both work from each volume's own code and follows a save from one volume into the next. The work was done by a helper agent; I re-ran its checks and read the key sites. The reference is on the events page and the save page.
Event scripts without symbols
tools/evscript.py no longer uses Infection's addresses:
- It finds
Execute's,CheckOpen's andSetCurrentOpen's switches from their code: thesltiubound, the default branch, the jump table and thejr. - It measures each case's operand count by running that case in eemu.
- It takes script and message extents from the gaps between arrays. On Infection this reproduces every script name and size and all 170 message symbols.
evscript.py check then walks every script with the volume's own
interpreter: 0 mismatches on all four. I re-ran it on MUT: 167 Execute
cases, 40 conditions, 12 tags, 196 scripts.
The interpreter grows by one case per volume (166, 167, 167, 168):
- Opcode 99 gains an operand from MUT on: a noise level.
- Opcode 168 (MUT on) sends mail 324 once any town has all three Grunty types grown up.
- Opcode 169 has a case only in Quarantine
(
QUA SLUS_205.64:0x001c52e8). Outbreak's copy of the ending script already contains the opcode, and its interpreter skips it.
Opcode 169 answers 23's question of what Quarantine loads KFED.BIN for:
- It loads
KFED.BINandKFAED.BINinto two$gpglobals. - It adds the
ending.ccsfile list and runs gcmn0x004f0a30, a routine that starts a thread and waits for it. - Then it frees both buffers.
I scanned every code word of main and the four overlays for accesses to
those two globals. The only ones are the case's own two stores and the two
loads that pass the buffers to ccFree. So the thread does not reach them
through those globals; whether it reaches them another way is not traced.
Other behaviour changes are listed on the events page:
faderesets first;call_lockclears more bits;staff_rollgrants wallpapers in OUT and QUA;- character accessors reach the three new party members.
QUA adds seven ML events and shares its other 199 scripts byte for byte with
OUT. Each volume's first story event opens on event_done 100·(v−1).
The save across volumes
tools/save.py runs each volume's own ccSaveSys and ccMcard code in
eemu, against a memory card kept in Python. So paths, sizes, sums and copies
are whatever the game does.
The main differences from 16:
- Slot files are
dhdata01..12, not ..13.MakeDirwrites twelve, and onlymcFname+4is referenced. - From Mutation on, a slot is 0x8d84 bytes.
ccSaveDatais still 0x8530 on every volume, with the same member offsets. A 0x854-byte extension follows it, holding the lists, parameters and counters of the three new party members (Tsukasa, Subaru, Sora,charTblrows 18-20). The constructor allocates it (MUT SLUS_205.62:0x00175650), and accessors route ids 18-20 there. - Each volume has its own directory (
BASLUS-2056xDOTHACK) and three icons. Mutation'smcDirNamefinally fills in the three later directories that Infection had as placeholders.
The carry-over, which save.py carry --prev runs end to end:
- The title screen offers the previous volume's saves when one has
clearFlag >= volumeNum - 1. LoadInfoPrevReqreads the previous volume's index. MUT reads Infection's, OUT reads MUT's, QUA reads OUT's.LoadDataPrevReqreads the slot intoccSaveDataat the same offsets. Mutation leaves its extension zeroed, since Infection has none. OUT and QUA copy the previous extension as it is.ccThDemocallsccStartEventConvert, which marks event100·(volumeNum−1)done, thenConvGame.ConvGameresets every character not in the party to itscharTblrow, reloads names and settings, clears the trade lists, and setsnewGameFlagto 2.
I ran INF→MUT and MUT→OUT. In both, the saved bytes load back identical apart from padding. For INF→MUT the trace shows the three copy ranges and the untouched extension.
Correction to 16: ConvGame is reachable. Infection's ccThDemo
calls it at INF demo.prg:0x00400b2c, right after ccStartEventConvert at
0x00400b20, which I read myself. The previous-volume path runs in
Infection too: volumeNum - 1 = 0 is replaced by 1, so it loads its own
directory and marks event 0 done.
tools/test_evscript.py and tools/test_save.py cover every extracted
volume and skip the rest.
what the Mutation-on blocks at ccSaveData+0x8432 and +0x8462 hold, and the extension's 3 bytes at +0x748 and 256 at +0x754; the save bit at +0x5ec8 bit 62 (see [[25]]); what fills OUT's and QUA's BSS message groups; what OUT's rewritten opcode cases (6, 8, 9, 60, 71, 72, 150) do; what the ending routine does while the KFED buffers are loaded.