diff --git a/docs/re/disc-atlas.html b/docs/re/disc-atlas.html new file mode 100644 index 0000000..6f24526 --- /dev/null +++ b/docs/re/disc-atlas.html @@ -0,0 +1,921 @@ +
Project Sylpheed · Arc of Deception — reverse-engineering reference
++ What is on the disc, and how every piece of it points at every other piece. + Four layers deep: the media, the archives, the container formats, and the + domain chains a port has to walk. +
+ ++ Nothing on this disc is found by path at runtime. Every lookup is a + hash, and the whole disc is joined together by three of them with + different rules. Read that first, or the rest of the map reads as a pile of + unrelated tables. +
++ Counts here were measured on the retail extract for this page, not copied + forward. Where a claim is contested or partial, the chip says so — and the + things that do not resolve get their own section, because a port + has to survive them. +
++ These are not interchangeable, and mixing them up is the single most + productive mistake this project has made. They differ in what they hash, + and crucially in case sensitivity. +
+| Hash | Keys | Case | Verified | |
|---|---|---|---|---|
| name_hash | +IPFB archive TOC entries — a file path inside a .pak | +insensitive | +recovers paths | +confirmed | +
| tag_hash | +IDXD record names and field keys | +sensitive | +1 271 462 / 1 271 462 | +confirmed | +
| ixud_hash | +IXUD localised-text record and field keys | +sensitive | +628 165 / 628 165 | +confirmed | +
+ Reading up from the bottom: a domain chain names an asset by string, that + string hashes into an archive TOC, the TOC yields a compressed blob, and + the blob's first four bytes say which container format it is. Every arrow + in this diagram is a hash lookup or a magic-byte test — there are no + directory scans. +
++ Two archives hold most of the disc. sound.pak is + 1.07 GB across five data segments — more than every other archive + combined — and GP_HANGAR_ARSENAL.pak is large for a + menu because it is stage-scoped: 168 of its objects are 28 + missions × 6 languages, each a complete Hangar configuration. +
+| Archive | Entries | Stored | What it is |
|---|---|---|---|
| sound.pak | 9 519 | 1.07 GB | every XMA1 bank — music, jingles, SFX, both voice languages |
| GP_HANGAR_ARSENAL.pak | 1 538 | 67.2 MB | 28 stages × 6 languages of Hangar config, plus item text |
| GP_MAIN_GAME_{D,E,F,I,J,S} | 1 119 ea. | 4.6 MB ea. | the mission data set — stages, units, routes, dialogue |
| GP_READY_ROOM.pak | 1 106 | 67.6 MB | largest UI pak; ISL script bytecode + link map |
| GP_MAIN_GAME_*2D unnamed | 711 ea. | 15.2 MB ea. | six paks, 0 % of names recoverable — see §06 |
| DefTables.pak hidden/ | 1 465 | — | definition tables; 804 of 1 465 names resolved |
| movie/{eng,jpn,deu,esp,fra,ita} | 117 ea. | 1.8 MB ea. | subtitles, telop overlays and fonts — not voice |
| tables.pak | 79 | 330 KB | the registries: sound cues, the cutscene manifest |
| MiscBin.pak hidden/ | 40 | — | 0 names resolved |
| fonts.pak | 3 | 2.4 MB | the three shipped typefaces |
| Magic | Content | State | Reach |
|---|---|---|---|
| IDXD | self-describing record/field table — the game's whole data layer | confirmed | 7 750 / 7 750 objects |
| IXUD | the same container, UTF-16BE, offsets in chars | confirmed | 1 104 / 1 104 objects |
| T8aD | 2D texture — a list of arbitrary sub-rectangles, not a tile grid | confirmed | 19 216 / 19 216 |
| RATC | UI bundle; children are sprites, layout records and primitives | confirmed | 10 144 / 10 148 refs |
| LSTA | sprite display list — count covers T8aD and PRMD | confirmed | 64 / 64 |
| XBG7 | mesh; index pool then vertex pool, layout declared per sub-mesh | 99.25 % | 6 247 / 6 294 |
| XPR2 | texture package — de-tile, A8R8G8B8 and DXT1 | partial | channel order confirmed |
| SLB | XACT bank of XMA1 sub-waves; two layouts, one headerless | 35/36 shared | JNGL_001 fails |
| GDHA | save file — zlib payload, chunk stream, round-trips byte-identically | ~11 ❔ fields | 545 bytes |
| TTF / OTF | stock OpenType | confirmed | 54 |
| ISB | ISL mission-script bytecode | partial | builtins documented |
| PRT | telop — on-screen text overlay for cutscenes | no parser | 22 bound |
| BIN | collision meshes, CollisionSet_S<NN>.bin | no parser | documented only |
+ Each of these starts from something the player can see and ends at bytes on + the disc. Where a chain has a hop that looks like it should be direct and + is not, that hop is called out — those are where a naive port breaks. +
++ One IDXD object per stage in every language pak. Its + StageResource record is the hub: nineteen fields + naming the 3D packages, the enemy roster, the routes, the collision set + and the localised objective strings. +
+
+%%{init: {'theme':'base','themeVariables':{'primaryColor':'#1e2029','primaryTextColor':'#e8e9ef','primaryBorderColor':'#4fd0e0','lineColor':'#8a8fa3','fontFamily':'ui-monospace, Menlo, monospace','fontSize':'13px','clusterBkg':'#16171f','clusterBorder':'#2b2e3a'}}}%%
+flowchart LR
+ A["GP_MAIN_GAME_<lang>.pak"] --> B["Stage_S<NN> record"]
+ B --> C["StageResource"]
+ C --> D["Stage_S<NN>.xpr
BG_<place>.xpr"]
+ C --> E["EnumUnit_S<NN>.tbl"]
+ C --> F["UnitGroup_S<NN>.tbl
squadron roster"]
+ C --> G["Route_S<NN>.tbl
FormationSet_S<NN>.tbl"]
+ C --> H["CollisionSet_S<NN>.bin"]
+ C --> I["EnumLocalString_S<NN>.tbl"]
+ B --> J["Stage\script.tbl"]
+ J --> K["StageNN.ssb
ISL bytecode"]
+
+ + The Hangar lists 59 weapons; the disc's Weapon table + has 131, and the two name sets overlap in zero values. + That is not a mismatch to be reconciled — it is the wrong join. An arsenal + item names a hardpoint slot, and the slot carries the weapon. +
+Arm1 / Arm2 / Arm3 / Nose — which are not items either.Type; the candidate items live in its positional, unnamed fields, in order.Turret_050 — a slot on the player craft's own unit table. 59 of 59 do; 0 of 59 name a weapon.Weapon record.+ One IDXD object in tables.pak is the whole cue + index. Its SOUNDS record is the only record found on + the disc that is entirely named fields — 5 798 of them — and the names are + the join key. Cue ids are partitioned by family with no overlap. +
+
+%%{init: {'theme':'base','themeVariables':{'primaryColor':'#1e2029','primaryTextColor':'#e8e9ef','primaryBorderColor':'#4fd0e0','lineColor':'#8a8fa3','fontFamily':'ui-monospace, Menlo, monospace','fontSize':'13px'}}}%%
+flowchart LR
+ M["script message id"] --> S["SOUNDS
cue name → sound id"]
+ S --> F["FILES
5 135 bank paths"]
+ F --> H{{"name_hash"}}
+ H --> P["sound.pak
9 519 entries"]
+ P --> X["XMA1 sub-waves"]
+ S -.-> C1["VOICE 1500–7331"]
+ S -.-> C2["SE 1–901"]
+ S -.-> C3["DEMO 8000–8408"]
+ S -.-> C4["BR 8500–8600"]
+ S -.-> C5["BGM 1001–1109"]
+
+ + One manifest record binds four assets per slot. Measured for this page: + 104 slots over 101 distinct movies, 99 with a subtitle track, 99 with a + voice track, 22 with a telop overlay. +
+
+%%{init: {'theme':'base','themeVariables':{'primaryColor':'#1e2029','primaryTextColor':'#e8e9ef','primaryBorderColor':'#4fd0e0','lineColor':'#8a8fa3','fontFamily':'ui-monospace, Menlo, monospace','fontSize':'13px'}}}%%
+flowchart LR
+ MF["cutscene manifest
tables.pak"] --> MV["MOVIE
dat/movie/<name>.wmv"]
+ MF --> TL["TELOP
<lang>.pak+*.prt"]
+ MF --> SB["SUBTITLE
<lang>.pak+SUBTITLE_*.tbl"]
+ MF --> VT["VOICETRACK
cue name"]
+ SB --> IX["IXUD caption keys"]
+ IX --> TX["GP_MAIN_GAME_<lang>
8 800 text keys"]
+ VT --> SC["sound cue → .slb region"]
+
+ + One archive per screen; each RATC bundle is one context × language build. + A bundle's declaration table lists every element with its parent and pivot; + the placement region right after it gives each element a keyframe group. + Both the tutorial pause menu and the title main menu rebuild + pixel-accurately from the disc alone. +
++ Two things are not what they look like. The resting position is + neither the first nor the last keyframe — it is the plateau. And the + declaration table is not the paint order: a per-draw + capture of the running title screen paints element 13 first and elements + 0 and 1 late. The real order is a second, reordered child array the screen + object keeps at runtime — deriving it from the bundle is still open. +
++ 545 bytes: a GDHA container, a zlib payload, then a + chunk stream. The 54-byte develop blob has the alphabet 0 + locked / 2 developable / 4 developed, and is indexed by + strings.tbl order — the display order plus + the cut items only the localisation file lists. weapon.tbl's + id list is not the index space; that it is also 54 long is a coincidence, + and the two agree only to index 32. +
++ These are not gaps in the reverse engineering. They are properties of the + shipped disc, and each one was found by a check that expected the opposite. +
+| Reference | Where | Status |
|---|---|---|
| pmbase.t32 | named by pmbase.rat in all four GP_STAGE_CLEAR language builds | nowhere on disc |
| SUBTITLE_S12B.tbl | bound by the manifest, in all six languages | resolves in none |
| logo1…logo4, SYLPH_HD720p_8M-CBR_2ch | manifest-bound movies | no .wmv |
| dat\GP_TEST\ | a debug archive the script manifest points at | not shipped |
| 6 × GP_MAIN_GAME_*2D | 711 entries each; eleven other paks are at 100 % by the same method | 0 % named |
| GP_READY_ROOM.pak | the largest UI pak on the disc | 6 of 1 106 named |
| 42 ISL script-symbol keys | hash-only field keys in <lang>\script\ID.tbl | provably unrecoverable |
| 9 of 101 cutscenes | no English transcript resolves | unexplained |
| JNGL_001.slb | headerless bank; payload is not a whole number of XMA1 packets | does not decode |