From 5cdff5e515bb361cf66c07a7b992efcd5daf540d Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Fri, 28 Aug 2026 16:51:45 +0200 Subject: [PATCH] docs/re: the disc atlas -- one map of how the assets reference each other A reference page covering the four layers (disc media, IPFB archives, container formats, domain chains) and the three hashes that join them: name_hash for archive TOC keys (case-insensitive), tag_hash for IDXD records and fields (case-sensitive), ixud_hash for localised text. Counts were measured on the retail extract for this page rather than copied forward from the corpus -- 41 archives, 26443 entries, 166 xpr, 97 wmv -- and where they overlap with docs/re they reproduce it independently (104 cutscene slots / 101 movies / 99 / 99 / 22). It gives the dangling references their own section, because they are properties of the shipped disc and a port has to survive them: pmbase.t32, SUBTITLE_S12B, the four boot logos, GP_TEST, the six 2D paks at exactly 0% named, and the 42 provably unrecoverable ISL symbol keys. Also published as an artifact for reading. Co-Authored-By: Claude Opus 5 --- docs/re/disc-atlas.html | 921 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 921 insertions(+) create mode 100644 docs/re/disc-atlas.html diff --git a/docs/re/disc-atlas.html b/docs/re/disc-atlas.html new file mode 100644 index 00000000..6f24526a --- /dev/null +++ b/docs/re/disc-atlas.html @@ -0,0 +1,921 @@ +Sylpheed Disc Atlas + + + +
+ +
+

Project Sylpheed · Arc of Deception — reverse-engineering reference

+

Sylpheed Disc Atlas

+

+ 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. +

+ +
+
41IPFB archives
+
26 443archive entries
+
166.xpr packages
+
97.wmv cutscenes
+
9 519sound banks
+
3hash functions
+
+
+ +
+

+ 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. +

+
+ + +
+

01 The join algebra three hashes, three rules

+ +
+

+ 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. +

+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
HashKeysCaseVerified
name_hashIPFB archive TOC entries — a file path inside a .pakinsensitiverecovers pathsconfirmed
tag_hashIDXD record names and field keyssensitive1 271 462 / 1 271 462confirmed
ixud_hashIXUD localised-text record and field keyssensitive628 165 / 628 165confirmed
+
+ +
+ The trap that cost the most + A 24-bit modulus cannot uniquely name an identifier of eight characters or + more. Exhaustive preimage search recovers "Stage01" + from its own hash, but at seven characters one real target already has + 1 176 preimages. Forty-two field keys on this disc are + hash-only with no stored name, and they are provably unrecoverable + — not merely unrecovered. Everything else carries its name inline: an IDXD + field's middle word points at its own name string, which is why the + containers are self-describing and the hash almost never has to be inverted. +
+
+ + +
+

02 The four layers disc → archive → container → domain

+ +
+

+ 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. +

+
+ +
+
+ + + + + + + + + + + + + + + Disc + + + default.xex + executable + PE + + + dat/ + 37 archives + + + hidden/ + DefTables · MiscBin + + + dat/movie/ + 97 wmv · 6 paks + + + resource3d/ + 166 xpr · 1.4 GB + + + + + + + + name_hash keys the TOC — case-insensitive + + + Archive + + + IPFB · .pak index + .p00….pNN data segments + 12-byte TOC entry { name_hash, offset, comp_size } · Z1/zlib payloads + 41 archives · 26 443 entries + + + raw files (not archived) + .wmv video · .xpr resource packages + 263 files + + + + + + + + payload magic decides the format — nothing declares a type + + + Container + + + IDXDtables6325 + + + T8aD2D tex4525 + + + RATCUI bundle2985 + + + IXUDtext1104 + + + LSTAsprites64 + + + SLBXMA19519 + + + XBG7mesh6294 + + + XPR2tex pkg166 + + + TTFfonts54 + + + ISBPRT · BIN + no parser + + + + + + + + tag_hash / ixud_hash key records + fields — values are asset NAMES, hashed back up + + + Domain + + + Stage28 missions10 sub-tables + + + Unit159 fields→ turret slot + + + Arsenal59 items4-hop chain + + + Audio5798 cues5 families + + + Cutscene104 slots4 bindings + + + UI screen965 buildsdecl + place + + + Save545 bytesGDHA + zlib + + + + + the save's 54-byte blob is indexed by strings.tbl order — not weapon.tbl + + Counts measured on the retail extract. Container counts exclude sound.pak and entries over 8 MB. + +
+
+ The three amber lines are the joins. Each is a hash lookup, and each is a + place a port can silently take the wrong branch: an archive TOC miss + returns nothing, and a field the disc never values reads as + 0.0 rather than as an error. +
+
+
+ + +
+

03 Archives where the mass actually sits

+ +
+

+ 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. +

+
+ +
+ + + + + + + + + + + + + + + + +
ArchiveEntriesStoredWhat it is
sound.pak9 5191.07 GBevery XMA1 bank — music, jingles, SFX, both voice languages
GP_HANGAR_ARSENAL.pak1 53867.2 MB28 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.pak1 10667.6 MBlargest UI pak; ISL script bytecode + link map
GP_MAIN_GAME_*2D unnamed711 ea.15.2 MB ea.six paks, 0 % of names recoverable — see §06
DefTables.pak hidden/1 465definition 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.pak79330 KBthe registries: sound cues, the cutscene manifest
MiscBin.pak hidden/400 names resolved
fonts.pak32.4 MBthe three shipped typefaces
+
+ +
+ One entry lies about its size + Static.slb, the sound-effect bank, sits at the highest + offset in sound.pak and declares + 616 768 bytes more than the disc holds. This is not a bad + extract — sound.p04 matches the ISO's own directory + record — and a sweep of every archive finds this entry over-running and no + other. The last entry's size field is an allocation size. A reader must + allow a short read there, and only there. +
+
+ + +
+

04 Container formats what each blob turns into

+ +
+ + + + + + + + + + + + + + + + + + + +
MagicContentStateReach
IDXDself-describing record/field table — the game's whole data layerconfirmed7 750 / 7 750 objects
IXUDthe same container, UTF-16BE, offsets in charsconfirmed1 104 / 1 104 objects
T8aD2D texture — a list of arbitrary sub-rectangles, not a tile gridconfirmed19 216 / 19 216
RATCUI bundle; children are sprites, layout records and primitivesconfirmed10 144 / 10 148 refs
LSTAsprite display list — count covers T8aD and PRMDconfirmed64 / 64
XBG7mesh; index pool then vertex pool, layout declared per sub-mesh99.25 %6 247 / 6 294
XPR2texture package — de-tile, A8R8G8B8 and DXT1partialchannel order confirmed
SLBXACT bank of XMA1 sub-waves; two layouts, one headerless35/36 sharedJNGL_001 fails
GDHAsave file — zlib payload, chunk stream, round-trips byte-identically~11 ❔ fields545 bytes
TTF / OTFstock OpenTypeconfirmed54
ISBISL mission-script bytecodepartialbuiltins documented
PRTtelop — on-screen text overlay for cutscenesno parser22 bound
BINcollision meshes, CollisionSet_S<NN>.binno parserdocumented only
+
+
+ + +
+

05 The domain chains what references what

+ +
+

+ 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. +

+
+ +

Mission

+
+

+ 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"] +
+
+ +

Arsenal — the four-hop trap

+
+

+ 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. +

+
+
+
01loadout record  — flight position × pilot, e.g. Rhino2-EllenNames Arm1 / Arm2 / Arm3 / Nose — which are not items either.
+
02per-slot allow-list recordIts only named field is Type; the candidate items live in its positional, unnamed fields, in order.
+
03arsenal item  → .PlayerWeaponResolves to Turret_050 — a slot on the player craft's own unit table. 59 of 59 do; 0 of 59 name a weapon.
+
04turret slot  → .WeaponIDAnd this is the Weapon record.
+
+ +

Audio

+
+

+ 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"] +
+
+
+ Two things the paths do not tell you + The 36 music, jingle and SFX banks sit at the table root with no + language component, so both sounds.tbl files name + them. And a .slb need not hold the track its name + claims: the movie voices are one continuous stream chunked into TOC entries + whose boundaries do not line up with the cues. +
+ +

Cutscene

+
+

+ 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"] +
+
+ +

UI screen

+
+

+ 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. +

+
+ +

Save

+
+

+ 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. +

+
+
+ The stale-summary trap + The in-game Details panel reads a summary copy held in the container + header, not the payload. Edit the payload alone and the panel keeps showing + the old values — which reads exactly like a failed parse. +
+
+ + +
+

06 References that go nowhere a port has to survive these

+ +
+

+ 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. +

+
+ +
+ + + + + + + + + + + + + +
ReferenceWhereStatus
pmbase.t32named by pmbase.rat in all four GP_STAGE_CLEAR language buildsnowhere on disc
SUBTITLE_S12B.tblbound by the manifest, in all six languagesresolves in none
logo1…logo4, SYLPH_HD720p_8M-CBR_2chmanifest-bound moviesno .wmv
dat\GP_TEST\a debug archive the script manifest points atnot shipped
6 × GP_MAIN_GAME_*2D711 entries each; eleven other paks are at 100 % by the same method0 % named
GP_READY_ROOM.pakthe largest UI pak on the disc6 of 1 106 named
42 ISL script-symbol keyshash-only field keys in <lang>\script\ID.tblprovably unrecoverable
9 of 101 cutscenesno English transcript resolvesunexplained
JNGL_001.slbheaderless bank; payload is not a whole number of XMA1 packetsdoes not decode
+
+ +
+ Why the 2D paks matter more than they look + Six archives at exactly 0.0 %, all six, 711 entries each — while + eleven menu paks hit 100 % in the same run. That control is what makes it a + finding rather than a failed guess: the naming method works, and these + archives are outside it. Whatever names their contents is not a path hashed + the way every other archive's is. +
+
+ + +
+

07 Still open

+ +
+
    +
  • UI paint order from the bundle. Solved at runtime, not statically — and static is what a port needs.
  • +
  • The .prt telop format. 22 cutscenes bind one; no parser exists.
  • +
  • Collision meshes. CollisionSet_S<NN>.bin is documented and unparsed.
  • +
  • 47 XBG7 resources still miss — mostly pose/proxy composites and damage variants, not a threshold away.
  • +
  • ~11 GHAD save fields unnamed, and difficulty-versus-stage undecided: three fields hold the value 2.
  • +
  • The naming of the 2D and Ready Room archives, which is the largest single block of unreachable content on the disc.
  • +
+
+
+ + + +