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 <noreply@anthropic.com>
This commit is contained in:
921
docs/re/disc-atlas.html
Normal file
921
docs/re/disc-atlas.html
Normal file
@@ -0,0 +1,921 @@
|
||||
<title>Sylpheed Disc Atlas</title>
|
||||
|
||||
<style>
|
||||
/* ───────────────────────────────────────────────────────────────────────────
|
||||
Palette. Three of these hex values are literal constants in the codebase:
|
||||
--ground is ComposeOptions::default().backdrop = [14,14,20], the colour the
|
||||
UI compositor paints behind every reassembled screen; --warn and --refuted
|
||||
are the viewer's own egui warning/error colours, rgb(224,168,86) and
|
||||
rgb(224,86,122). --confirm is the cyan glow measured off the intro video.
|
||||
Neutrals are biased blue to sit with the ground rather than against it.
|
||||
─────────────────────────────────────────────────────────────────────────── */
|
||||
:root {
|
||||
--ground: #0e0e14;
|
||||
--confirm: #3fb6c6;
|
||||
--warn: #b8792c;
|
||||
--refuted: #c23f61;
|
||||
--accent: #c23f61;
|
||||
|
||||
--bg: #eceef3;
|
||||
--surface: #f6f7fa;
|
||||
--surface-2: #e2e5ee;
|
||||
--text: #16171f;
|
||||
--text-muted: #5c6076;
|
||||
--text-faint: #878ca3;
|
||||
--rule: #c9cddb;
|
||||
--rule-soft: #dbdfe9;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root:not([data-theme="light"]) {
|
||||
--confirm: #4fd0e0;
|
||||
--warn: #e0a856;
|
||||
--refuted: #e0567a;
|
||||
--accent: #e0567a;
|
||||
|
||||
--bg: #0e0e14;
|
||||
--surface: #16171f;
|
||||
--surface-2: #1e2029;
|
||||
--text: #e8e9ef;
|
||||
--text-muted: #9296ab;
|
||||
--text-faint: #6d7186;
|
||||
--rule: #2b2e3a;
|
||||
--rule-soft: #22242e;
|
||||
}
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
--confirm: #4fd0e0;
|
||||
--warn: #e0a856;
|
||||
--refuted: #e0567a;
|
||||
--accent: #e0567a;
|
||||
|
||||
--bg: #0e0e14;
|
||||
--surface: #16171f;
|
||||
--surface-2: #1e2029;
|
||||
--text: #e8e9ef;
|
||||
--text-muted: #9296ab;
|
||||
--text-faint: #6d7186;
|
||||
--rule: #2b2e3a;
|
||||
--rule-soft: #22242e;
|
||||
}
|
||||
|
||||
/* Type roles: mono for every label and datum (this subject is hex and paths),
|
||||
serif for prose so the two never blur into each other. */
|
||||
:root {
|
||||
--mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo,
|
||||
Consolas, "Liberation Mono", monospace;
|
||||
--serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
|
||||
Georgia, serif;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: var(--serif);
|
||||
font-size: 17px;
|
||||
line-height: 1.6;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
.wrap {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
padding: 0 24px 120px;
|
||||
}
|
||||
|
||||
/* Prose stays near 66ch; panels break out to the full width. */
|
||||
.col {
|
||||
max-width: 66ch;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.1em;
|
||||
}
|
||||
|
||||
/* ── Masthead ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
header.mast {
|
||||
border-bottom: 1px solid var(--rule);
|
||||
padding: 72px 0 34px;
|
||||
margin-bottom: 44px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--mono);
|
||||
font-size: clamp(30px, 5.4vw, 52px);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.04;
|
||||
margin: 0 0 22px;
|
||||
text-wrap: balance;
|
||||
}
|
||||
h1 .dim { color: var(--text-faint); font-weight: 400; }
|
||||
|
||||
.standfirst {
|
||||
font-size: 20px;
|
||||
line-height: 1.5;
|
||||
color: var(--text-muted);
|
||||
max-width: 60ch;
|
||||
margin: 0;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
/* ── Stat strip ───────────────────────────────────────────────────────────── */
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
|
||||
gap: 1px;
|
||||
background: var(--rule-soft);
|
||||
border: 1px solid var(--rule-soft);
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
.stat { background: var(--bg); padding: 16px 18px; }
|
||||
.stat .n {
|
||||
font-family: var(--mono);
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
display: block;
|
||||
line-height: 1.1;
|
||||
}
|
||||
.stat .l {
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.11em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
margin-top: 7px;
|
||||
display: block;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ── Sections ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
section { margin: 78px 0 0; }
|
||||
|
||||
h2 {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text);
|
||||
margin: 0 0 6px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 14px;
|
||||
}
|
||||
h2 .idx { color: var(--accent); font-weight: 400; }
|
||||
h2 .rest { color: var(--text-faint); font-weight: 400; letter-spacing: 0.1em; }
|
||||
|
||||
h3 {
|
||||
font-family: var(--mono);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.03em;
|
||||
margin: 42px 0 4px;
|
||||
}
|
||||
|
||||
p { margin: 0; }
|
||||
.col > p + p { margin-top: 0; }
|
||||
|
||||
a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 2px; }
|
||||
a:focus-visible, button:focus-visible { outline: 2px solid var(--confirm); outline-offset: 3px; }
|
||||
|
||||
code, .m {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.86em;
|
||||
background: var(--surface-2);
|
||||
padding: 1px 5px;
|
||||
border-radius: 2px;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
strong { font-weight: 700; }
|
||||
em { font-style: italic; color: var(--text); }
|
||||
|
||||
/* ── Confidence chips ─────────────────────────────────────────────────────── */
|
||||
|
||||
.chip {
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
padding: 2px 7px;
|
||||
border: 1px solid currentColor;
|
||||
border-radius: 2px;
|
||||
white-space: nowrap;
|
||||
display: inline-block;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.c-ok { color: var(--confirm); }
|
||||
.c-part { color: var(--warn); }
|
||||
.c-no { color: var(--refuted); }
|
||||
.c-none { color: var(--text-faint); }
|
||||
|
||||
/* ── Panels (full-width breakouts) ────────────────────────────────────────── */
|
||||
|
||||
.panel {
|
||||
margin: 30px 0 0;
|
||||
border: 1px solid var(--rule);
|
||||
background: var(--surface);
|
||||
}
|
||||
.panel-head {
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
letter-spacing: 0.13em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
padding: 11px 16px;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
}
|
||||
.panel-body { padding: 4px 0; overflow-x: auto; }
|
||||
|
||||
/* The schematic is a screen, not a page element: it keeps the game's own
|
||||
ground in both themes, the way an instrument panel does. */
|
||||
.screen {
|
||||
background: var(--ground);
|
||||
border: 1px solid var(--rule);
|
||||
margin: 30px 0 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.screen svg { display: block; min-width: 760px; width: 100%; height: auto; }
|
||||
|
||||
figcaption {
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
line-height: 1.65;
|
||||
color: var(--text-faint);
|
||||
margin-top: 12px;
|
||||
max-width: 76ch;
|
||||
}
|
||||
figure { margin: 0; }
|
||||
|
||||
/* ── Tables ───────────────────────────────────────────────────────────────── */
|
||||
|
||||
.tbl-scroll { overflow-x: auto; margin: 26px 0 0; }
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
font-family: var(--mono);
|
||||
font-size: 12.5px;
|
||||
min-width: 620px;
|
||||
}
|
||||
th {
|
||||
text-align: left;
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--text-faint);
|
||||
font-weight: 700;
|
||||
padding: 0 14px 9px 0;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
white-space: nowrap;
|
||||
}
|
||||
td {
|
||||
padding: 8px 14px 8px 0;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; padding-right: 22px; }
|
||||
tbody tr:hover { background: var(--surface); }
|
||||
td .note { color: var(--text-muted); font-family: var(--serif); font-size: 13.5px; }
|
||||
|
||||
/* ── Mermaid ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
.dia {
|
||||
margin: 24px 0 0;
|
||||
padding: 18px 16px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--rule-soft);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.dia pre.mermaid { margin: 0; text-align: left; }
|
||||
|
||||
/* ── Callout ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
.note-box {
|
||||
border-left: 2px solid var(--accent);
|
||||
padding: 2px 0 2px 18px;
|
||||
margin: 26px 0 0;
|
||||
color: var(--text-muted);
|
||||
max-width: 64ch;
|
||||
}
|
||||
.note-box .lbl {
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.13em;
|
||||
text-transform: uppercase;
|
||||
color: var(--accent);
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
ul.plain { margin: 0; padding-left: 1.15em; display: flex; flex-direction: column; gap: 0.55em; }
|
||||
ul.plain li::marker { color: var(--text-faint); }
|
||||
|
||||
/* ── Chain (numbered hops — the numbering is the hop order, which is real) ─── */
|
||||
|
||||
.hops { display: flex; flex-direction: column; gap: 0; margin: 24px 0 0; }
|
||||
.hop {
|
||||
display: grid;
|
||||
grid-template-columns: 34px 1fr;
|
||||
gap: 14px;
|
||||
padding: 13px 0;
|
||||
border-bottom: 1px solid var(--rule-soft);
|
||||
align-items: baseline;
|
||||
}
|
||||
.hop:first-child { border-top: 1px solid var(--rule); }
|
||||
.hop .n {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.hop .b { font-family: var(--mono); font-size: 12.5px; line-height: 1.7; }
|
||||
.hop .b .d { color: var(--text-muted); font-family: var(--serif); font-size: 14px; display: block; margin-top: 3px; }
|
||||
|
||||
footer {
|
||||
margin-top: 96px;
|
||||
padding-top: 26px;
|
||||
border-top: 1px solid var(--rule);
|
||||
font-family: var(--mono);
|
||||
font-size: 11.5px;
|
||||
color: var(--text-faint);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
* { animation: none !important; transition: none !important; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="wrap">
|
||||
|
||||
<header class="mast">
|
||||
<p class="eyebrow">Project Sylpheed · Arc of Deception — reverse-engineering reference</p>
|
||||
<h1>Sylpheed Disc Atlas</h1>
|
||||
<p class="standfirst">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat"><span class="n">41</span><span class="l">IPFB archives</span></div>
|
||||
<div class="stat"><span class="n">26 443</span><span class="l">archive entries</span></div>
|
||||
<div class="stat"><span class="n">166</span><span class="l">.xpr packages</span></div>
|
||||
<div class="stat"><span class="n">97</span><span class="l">.wmv cutscenes</span></div>
|
||||
<div class="stat"><span class="n">9 519</span><span class="l">sound banks</span></div>
|
||||
<div class="stat"><span class="n">3</span><span class="l">hash functions</span></div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="col">
|
||||
<p>
|
||||
Nothing on this disc is found by path at runtime. Every lookup is a
|
||||
<em>hash</em>, 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.
|
||||
</p>
|
||||
<p>
|
||||
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 <em>do not</em> resolve get their own section, because a port
|
||||
has to survive them.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">01</span> <span>The join algebra</span> <span class="rest">three hashes, three rules</span></h2>
|
||||
|
||||
<div class="col">
|
||||
<p>
|
||||
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 <strong>case sensitivity</strong>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="tbl-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Hash</th><th>Keys</th><th>Case</th><th>Verified</th><th></th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>name_hash</strong></td>
|
||||
<td>IPFB archive TOC entries — a file path inside a <span class="m">.pak</span></td>
|
||||
<td>insensitive</td>
|
||||
<td class="num">recovers paths</td>
|
||||
<td><span class="chip c-ok">confirmed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>tag_hash</strong></td>
|
||||
<td>IDXD record names and field keys</td>
|
||||
<td><em>sensitive</em></td>
|
||||
<td class="num">1 271 462 / 1 271 462</td>
|
||||
<td><span class="chip c-ok">confirmed</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>ixud_hash</strong></td>
|
||||
<td>IXUD localised-text record and field keys</td>
|
||||
<td><em>sensitive</em></td>
|
||||
<td class="num">628 165 / 628 165</td>
|
||||
<td><span class="chip c-ok">confirmed</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<span class="lbl">The trap that cost the most</span>
|
||||
A 24-bit modulus cannot uniquely name an identifier of eight characters or
|
||||
more. Exhaustive preimage search recovers <span class="m">"Stage01"</span>
|
||||
from its own hash, but at seven characters one real target already has
|
||||
<strong>1 176</strong> preimages. Forty-two field keys on this disc are
|
||||
hash-only with no stored name, and they are <em>provably</em> 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.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">02</span> <span>The four layers</span> <span class="rest">disc → archive → container → domain</span></h2>
|
||||
|
||||
<div class="col">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<figure>
|
||||
<div class="screen">
|
||||
<svg viewBox="0 0 1000 640" role="img" aria-label="Four-layer schematic of the Project Sylpheed disc: media, archives, container formats and domain chains, joined by three hash functions.">
|
||||
<defs>
|
||||
<marker id="ar" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
||||
<path d="M 0 0 L 10 5 L 0 10 z" fill="#4f5468"/>
|
||||
</marker>
|
||||
<style>
|
||||
.bl { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10px; letter-spacing: .16em; fill: #6d7186; text-transform: uppercase; }
|
||||
.bx { fill: #16171f; stroke: #2b2e3a; stroke-width: 1; }
|
||||
.bxa { fill: #16171f; stroke: #4fd0e0; stroke-width: 1; }
|
||||
.tt { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 12px; fill: #e8e9ef; }
|
||||
.ts { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10px; fill: #6d7186; }
|
||||
.tn { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 11px; fill: #4fd0e0; }
|
||||
.jn { font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 10.5px; fill: #e0a856; letter-spacing: .06em; }
|
||||
.ln { stroke: #4f5468; stroke-width: 1; }
|
||||
.rl { stroke: #22242e; stroke-width: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- band rules -->
|
||||
<line class="rl" x1="0" y1="118" x2="1000" y2="118"/>
|
||||
<line class="rl" x1="0" y1="268" x2="1000" y2="268"/>
|
||||
<line class="rl" x1="0" y1="418" x2="1000" y2="418"/>
|
||||
|
||||
<!-- ── BAND 1 · disc media ── -->
|
||||
<text class="bl" x="24" y="40">Disc</text>
|
||||
<g>
|
||||
<rect class="bx" x="130" y="30" width="160" height="58" rx="2"/>
|
||||
<text class="tt" x="145" y="54">default.xex</text>
|
||||
<text class="ts" x="145" y="70">executable + PE</text>
|
||||
|
||||
<rect class="bx" x="302" y="30" width="160" height="58" rx="2"/>
|
||||
<text class="tt" x="317" y="54">dat/</text>
|
||||
<text class="ts" x="317" y="70">37 archives</text>
|
||||
|
||||
<rect class="bx" x="474" y="30" width="160" height="58" rx="2"/>
|
||||
<text class="tt" x="489" y="54">hidden/</text>
|
||||
<text class="ts" x="489" y="70">DefTables · MiscBin</text>
|
||||
|
||||
<rect class="bx" x="646" y="30" width="160" height="58" rx="2"/>
|
||||
<text class="tt" x="661" y="54">dat/movie/</text>
|
||||
<text class="ts" x="661" y="70">97 wmv · 6 paks</text>
|
||||
|
||||
<rect class="bx" x="818" y="30" width="160" height="58" rx="2"/>
|
||||
<text class="tt" x="833" y="54">resource3d/</text>
|
||||
<text class="ts" x="833" y="70">166 xpr · 1.4 GB</text>
|
||||
</g>
|
||||
|
||||
<!-- join 1 -->
|
||||
<line class="ln" x1="382" y1="88" x2="382" y2="176" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="554" y1="88" x2="554" y2="176" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="726" y1="88" x2="726" y2="176" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="898" y1="88" x2="898" y2="220" marker-end="url(#ar)"/>
|
||||
<text class="jn" x="130" y="112">name_hash keys the TOC — case-insensitive</text>
|
||||
|
||||
<!-- ── BAND 2 · archives ── -->
|
||||
<text class="bl" x="24" y="190">Archive</text>
|
||||
<g>
|
||||
<rect class="bxa" x="130" y="176" width="560" height="72" rx="2"/>
|
||||
<text class="tt" x="146" y="200">IPFB · .pak index + .p00….pNN data segments</text>
|
||||
<text class="ts" x="146" y="218">12-byte TOC entry { name_hash, offset, comp_size } · Z1/zlib payloads</text>
|
||||
<text class="tn" x="146" y="236">41 archives · 26 443 entries</text>
|
||||
|
||||
<rect class="bx" x="706" y="176" width="272" height="72" rx="2"/>
|
||||
<text class="tt" x="722" y="200">raw files (not archived)</text>
|
||||
<text class="ts" x="722" y="218">.wmv video · .xpr resource packages</text>
|
||||
<text class="tn" x="722" y="236">263 files</text>
|
||||
</g>
|
||||
|
||||
<!-- join 2 -->
|
||||
<line class="ln" x1="200" y1="248" x2="200" y2="292" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="410" y1="248" x2="410" y2="292" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="620" y1="248" x2="620" y2="292" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="842" y1="248" x2="842" y2="292" marker-end="url(#ar)"/>
|
||||
<text class="jn" x="130" y="264">payload magic decides the format — nothing declares a type</text>
|
||||
|
||||
<!-- ── BAND 3 · containers ── -->
|
||||
<text class="bl" x="24" y="330">Container</text>
|
||||
<g>
|
||||
<rect class="bxa" x="130" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="140" y="316">IDXD</text><text class="ts" x="140" y="332">tables</text><text class="tn" x="140" y="347">6325</text>
|
||||
|
||||
<rect class="bxa" x="215" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="225" y="316">T8aD</text><text class="ts" x="225" y="332">2D tex</text><text class="tn" x="225" y="347">4525</text>
|
||||
|
||||
<rect class="bxa" x="300" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="310" y="316">RATC</text><text class="ts" x="310" y="332">UI bundle</text><text class="tn" x="310" y="347">2985</text>
|
||||
|
||||
<rect class="bxa" x="385" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="395" y="316">IXUD</text><text class="ts" x="395" y="332">text</text><text class="tn" x="395" y="347">1104</text>
|
||||
|
||||
<rect class="bxa" x="470" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="480" y="316">LSTA</text><text class="ts" x="480" y="332">sprites</text><text class="tn" x="480" y="347">64</text>
|
||||
|
||||
<rect class="bxa" x="555" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="565" y="316">SLB</text><text class="ts" x="565" y="332">XMA1</text><text class="tn" x="565" y="347">9519</text>
|
||||
|
||||
<rect class="bxa" x="640" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="650" y="316">XBG7</text><text class="ts" x="650" y="332">mesh</text><text class="tn" x="650" y="347">6294</text>
|
||||
|
||||
<rect class="bxa" x="725" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="735" y="316">XPR2</text><text class="ts" x="735" y="332">tex pkg</text><text class="tn" x="735" y="347">166</text>
|
||||
|
||||
<rect class="bxa" x="810" y="292" width="77" height="62" rx="2"/>
|
||||
<text class="tt" x="820" y="316">TTF</text><text class="ts" x="820" y="332">fonts</text><text class="tn" x="820" y="347">54</text>
|
||||
|
||||
<rect class="bx" x="895" y="292" width="77" height="62" rx="2" stroke="#e0567a"/>
|
||||
<text class="tt" x="905" y="316">ISB</text><text class="ts" x="905" y="332">PRT · BIN</text>
|
||||
<text class="ts" x="905" y="347" fill="#e0567a">no parser</text>
|
||||
</g>
|
||||
|
||||
<!-- join 3 -->
|
||||
<line class="ln" x1="200" y1="354" x2="200" y2="442" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="440" y1="354" x2="440" y2="442" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="680" y1="354" x2="680" y2="442" marker-end="url(#ar)"/>
|
||||
<line class="ln" x1="900" y1="354" x2="900" y2="442" marker-end="url(#ar)"/>
|
||||
<text class="jn" x="130" y="410">tag_hash / ixud_hash key records + fields — values are asset NAMES, hashed back up</text>
|
||||
|
||||
<!-- ── BAND 4 · domains ── -->
|
||||
<text class="bl" x="24" y="480">Domain</text>
|
||||
<g>
|
||||
<rect class="bx" x="130" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="141" y="466">Stage</text><text class="ts" x="141" y="482">28 missions</text><text class="ts" x="141" y="496">10 sub-tables</text>
|
||||
|
||||
<rect class="bx" x="252" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="263" y="466">Unit</text><text class="ts" x="263" y="482">159 fields</text><text class="ts" x="263" y="496">→ turret slot</text>
|
||||
|
||||
<rect class="bx" x="374" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="385" y="466">Arsenal</text><text class="ts" x="385" y="482">59 items</text><text class="ts" x="385" y="496">4-hop chain</text>
|
||||
|
||||
<rect class="bx" x="496" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="507" y="466">Audio</text><text class="ts" x="507" y="482">5798 cues</text><text class="ts" x="507" y="496">5 families</text>
|
||||
|
||||
<rect class="bx" x="618" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="629" y="466">Cutscene</text><text class="ts" x="629" y="482">104 slots</text><text class="ts" x="629" y="496">4 bindings</text>
|
||||
|
||||
<rect class="bx" x="740" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="751" y="466">UI screen</text><text class="ts" x="751" y="482">965 builds</text><text class="ts" x="751" y="496">decl + place</text>
|
||||
|
||||
<rect class="bx" x="862" y="442" width="112" height="66" rx="2"/>
|
||||
<text class="tt" x="873" y="466">Save</text><text class="ts" x="873" y="482">545 bytes</text><text class="ts" x="873" y="496">GDHA + zlib</text>
|
||||
</g>
|
||||
|
||||
<!-- the cross-domain edge: the save indexes the arsenal's string order -->
|
||||
<path class="ln" d="M 918 508 L 918 560 L 430 560 L 430 508" fill="none" stroke-dasharray="3 3" marker-end="url(#ar)"/>
|
||||
<text class="jn" x="560" y="578">the save's 54-byte blob is indexed by strings.tbl order — not weapon.tbl</text>
|
||||
|
||||
<text class="ts" x="130" y="614" fill="#4f5468">Counts measured on the retail extract. Container counts exclude sound.pak and entries over 8 MB.</text>
|
||||
</svg>
|
||||
</div>
|
||||
<figcaption>
|
||||
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
|
||||
<span class="m">0.0</span> rather than as an error.
|
||||
</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">03</span> <span>Archives</span> <span class="rest">where the mass actually sits</span></h2>
|
||||
|
||||
<div class="col">
|
||||
<p>
|
||||
Two archives hold most of the disc. <span class="m">sound.pak</span> is
|
||||
1.07 GB across five data segments — more than every other archive
|
||||
combined — and <span class="m">GP_HANGAR_ARSENAL.pak</span> is large for a
|
||||
menu because it is <em>stage-scoped</em>: 168 of its objects are 28
|
||||
missions × 6 languages, each a complete Hangar configuration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="tbl-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Archive</th><th class="num">Entries</th><th class="num">Stored</th><th>What it is</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>sound.pak</td><td class="num">9 519</td><td class="num">1.07 GB</td><td><span class="note">every XMA1 bank — music, jingles, SFX, both voice languages</span></td></tr>
|
||||
<tr><td>GP_HANGAR_ARSENAL.pak</td><td class="num">1 538</td><td class="num">67.2 MB</td><td><span class="note">28 stages × 6 languages of Hangar config, plus item text</span></td></tr>
|
||||
<tr><td>GP_MAIN_GAME_{D,E,F,I,J,S}</td><td class="num">1 119 ea.</td><td class="num">4.6 MB ea.</td><td><span class="note">the mission data set — stages, units, routes, dialogue</span></td></tr>
|
||||
<tr><td>GP_READY_ROOM.pak</td><td class="num">1 106</td><td class="num">67.6 MB</td><td><span class="note">largest UI pak; ISL script bytecode + link map</span></td></tr>
|
||||
<tr><td>GP_MAIN_GAME_*2D <span class="chip c-no">unnamed</span></td><td class="num">711 ea.</td><td class="num">15.2 MB ea.</td><td><span class="note">six paks, 0 % of names recoverable — see §06</span></td></tr>
|
||||
<tr><td>DefTables.pak <span class="m">hidden/</span></td><td class="num">1 465</td><td class="num">—</td><td><span class="note">definition tables; 804 of 1 465 names resolved</span></td></tr>
|
||||
<tr><td>movie/{eng,jpn,deu,esp,fra,ita}</td><td class="num">117 ea.</td><td class="num">1.8 MB ea.</td><td><span class="note">subtitles, telop overlays and fonts — <em>not</em> voice</span></td></tr>
|
||||
<tr><td>tables.pak</td><td class="num">79</td><td class="num">330 KB</td><td><span class="note">the registries: sound cues, the cutscene manifest</span></td></tr>
|
||||
<tr><td>MiscBin.pak <span class="m">hidden/</span></td><td class="num">40</td><td class="num">—</td><td><span class="note">0 names resolved</span></td></tr>
|
||||
<tr><td>fonts.pak</td><td class="num">3</td><td class="num">2.4 MB</td><td><span class="note">the three shipped typefaces</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<span class="lbl">One entry lies about its size</span>
|
||||
<span class="m">Static.slb</span>, the sound-effect bank, sits at the highest
|
||||
offset in <span class="m">sound.pak</span> and declares
|
||||
<strong>616 768 bytes more than the disc holds</strong>. This is not a bad
|
||||
extract — <span class="m">sound.p04</span> 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 <em>only</em> there.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">04</span> <span>Container formats</span> <span class="rest">what each blob turns into</span></h2>
|
||||
|
||||
<div class="tbl-scroll">
|
||||
<table>
|
||||
<thead>
|
||||
<tr><th>Magic</th><th>Content</th><th>State</th><th>Reach</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><strong>IDXD</strong></td><td><span class="note">self-describing record/field table — the game's whole data layer</span></td><td><span class="chip c-ok">confirmed</span></td><td class="num">7 750 / 7 750 objects</td></tr>
|
||||
<tr><td><strong>IXUD</strong></td><td><span class="note">the same container, UTF-16BE, offsets in <em>chars</em></span></td><td><span class="chip c-ok">confirmed</span></td><td class="num">1 104 / 1 104 objects</td></tr>
|
||||
<tr><td><strong>T8aD</strong></td><td><span class="note">2D texture — a list of arbitrary sub-rectangles, not a tile grid</span></td><td><span class="chip c-ok">confirmed</span></td><td class="num">19 216 / 19 216</td></tr>
|
||||
<tr><td><strong>RATC</strong></td><td><span class="note">UI bundle; children are sprites, layout records and primitives</span></td><td><span class="chip c-ok">confirmed</span></td><td class="num">10 144 / 10 148 refs</td></tr>
|
||||
<tr><td><strong>LSTA</strong></td><td><span class="note">sprite display list — count covers T8aD <em>and</em> PRMD</span></td><td><span class="chip c-ok">confirmed</span></td><td class="num">64 / 64</td></tr>
|
||||
<tr><td><strong>XBG7</strong></td><td><span class="note">mesh; index pool then vertex pool, layout declared per sub-mesh</span></td><td><span class="chip c-part">99.25 %</span></td><td class="num">6 247 / 6 294</td></tr>
|
||||
<tr><td><strong>XPR2</strong></td><td><span class="note">texture package — de-tile, A8R8G8B8 and DXT1</span></td><td><span class="chip c-part">partial</span></td><td class="num">channel order confirmed</td></tr>
|
||||
<tr><td><strong>SLB</strong></td><td><span class="note">XACT bank of XMA1 sub-waves; two layouts, one headerless</span></td><td><span class="chip c-part">35/36 shared</span></td><td class="num"><span class="m">JNGL_001</span> fails</td></tr>
|
||||
<tr><td><strong>GDHA</strong></td><td><span class="note">save file — zlib payload, chunk stream, round-trips byte-identically</span></td><td><span class="chip c-part">~11 ❔ fields</span></td><td class="num">545 bytes</td></tr>
|
||||
<tr><td>TTF / OTF</td><td><span class="note">stock OpenType</span></td><td><span class="chip c-ok">confirmed</span></td><td class="num">54</td></tr>
|
||||
<tr><td><strong>ISB</strong></td><td><span class="note">ISL mission-script bytecode</span></td><td><span class="chip c-part">partial</span></td><td class="num">builtins documented</td></tr>
|
||||
<tr><td><strong>PRT</strong></td><td><span class="note">telop — on-screen text overlay for cutscenes</span></td><td><span class="chip c-none">no parser</span></td><td class="num">22 bound</td></tr>
|
||||
<tr><td><strong>BIN</strong></td><td><span class="note">collision meshes, <span class="m">CollisionSet_S<NN>.bin</span></span></td><td><span class="chip c-none">no parser</span></td><td class="num">documented only</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">05</span> <span>The domain chains</span> <span class="rest">what references what</span></h2>
|
||||
|
||||
<div class="col">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3>Mission</h3>
|
||||
<div class="col">
|
||||
<p>
|
||||
One IDXD object per stage in every language pak. Its
|
||||
<span class="m">StageResource</span> record is the hub: nineteen fields
|
||||
naming the 3D packages, the enemy roster, the routes, the collision set
|
||||
and the localised objective strings.
|
||||
</p>
|
||||
</div>
|
||||
<div class="dia">
|
||||
<pre class="mermaid">
|
||||
%%{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<br/>BG_<place>.xpr"]
|
||||
C --> E["EnumUnit_S<NN>.tbl"]
|
||||
C --> F["UnitGroup_S<NN>.tbl<br/>squadron roster"]
|
||||
C --> G["Route_S<NN>.tbl<br/>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<br/>ISL bytecode"]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<h3>Arsenal — the four-hop trap</h3>
|
||||
<div class="col">
|
||||
<p>
|
||||
The Hangar lists 59 weapons; the disc's <span class="m">Weapon</span> table
|
||||
has 131, and the two name sets overlap in <strong>zero</strong> values.
|
||||
That is not a mismatch to be reconciled — it is the wrong join. An arsenal
|
||||
item names a <em>hardpoint slot</em>, and the slot carries the weapon.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hops">
|
||||
<div class="hop"><span class="n">01</span><span class="b">loadout record <span style="color:var(--text-faint)">— flight position × pilot, e.g. Rhino2-Ellen</span><span class="d">Names <code>Arm1</code> / <code>Arm2</code> / <code>Arm3</code> / <code>Nose</code> — which are not items either.</span></span></div>
|
||||
<div class="hop"><span class="n">02</span><span class="b">per-slot allow-list record<span class="d">Its only <em>named</em> field is <code>Type</code>; the candidate items live in its positional, unnamed fields, in order.</span></span></div>
|
||||
<div class="hop"><span class="n">03</span><span class="b">arsenal item <span style="color:var(--text-faint)">→ .PlayerWeapon</span><span class="d">Resolves to <code>Turret_050</code> — a slot on the player craft's own unit table. 59 of 59 do; 0 of 59 name a weapon.</span></span></div>
|
||||
<div class="hop"><span class="n">04</span><span class="b">turret slot <span style="color:var(--text-faint)">→ .WeaponID</span><span class="d">And <em>this</em> is the <code>Weapon</code> record.</span></span></div>
|
||||
</div>
|
||||
|
||||
<h3>Audio</h3>
|
||||
<div class="col">
|
||||
<p>
|
||||
One IDXD object in <span class="m">tables.pak</span> is the whole cue
|
||||
index. Its <span class="m">SOUNDS</span> 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.
|
||||
</p>
|
||||
</div>
|
||||
<div class="dia">
|
||||
<pre class="mermaid">
|
||||
%%{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<br/>cue name → sound id"]
|
||||
S --> F["FILES<br/>5 135 bank paths"]
|
||||
F --> H{{"name_hash"}}
|
||||
H --> P["sound.pak<br/>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"]
|
||||
</pre>
|
||||
</div>
|
||||
<div class="note-box">
|
||||
<span class="lbl">Two things the paths do not tell you</span>
|
||||
The 36 music, jingle and SFX banks sit at the table <em>root</em> with no
|
||||
language component, so both <span class="m">sounds.tbl</span> files name
|
||||
them. And a <span class="m">.slb</span> 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.
|
||||
</div>
|
||||
|
||||
<h3>Cutscene</h3>
|
||||
<div class="col">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
<div class="dia">
|
||||
<pre class="mermaid">
|
||||
%%{init: {'theme':'base','themeVariables':{'primaryColor':'#1e2029','primaryTextColor':'#e8e9ef','primaryBorderColor':'#4fd0e0','lineColor':'#8a8fa3','fontFamily':'ui-monospace, Menlo, monospace','fontSize':'13px'}}}%%
|
||||
flowchart LR
|
||||
MF["cutscene manifest<br/>tables.pak"] --> MV["MOVIE<br/>dat/movie/<name>.wmv"]
|
||||
MF --> TL["TELOP<br/><lang>.pak+*.prt"]
|
||||
MF --> SB["SUBTITLE<br/><lang>.pak+SUBTITLE_*.tbl"]
|
||||
MF --> VT["VOICETRACK<br/>cue name"]
|
||||
SB --> IX["IXUD caption keys"]
|
||||
IX --> TX["GP_MAIN_GAME_<lang><br/>8 800 text keys"]
|
||||
VT --> SC["sound cue → .slb region"]
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<h3>UI screen</h3>
|
||||
<div class="col">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
<p>
|
||||
Two things are <em>not</em> what they look like. The resting position is
|
||||
neither the first nor the last keyframe — it is the plateau. And the
|
||||
declaration table is <strong>not</strong> 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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3>Save</h3>
|
||||
<div class="col">
|
||||
<p>
|
||||
545 bytes: a <span class="m">GDHA</span> container, a zlib payload, then a
|
||||
chunk stream. The 54-byte develop blob has the alphabet <span class="m">0
|
||||
locked / 2 developable / 4 developed</span>, and is indexed by
|
||||
<span class="m">strings.tbl</span> order — the display order <em>plus</em>
|
||||
the cut items only the localisation file lists. <span class="m">weapon.tbl</span>'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.
|
||||
</p>
|
||||
</div>
|
||||
<div class="note-box">
|
||||
<span class="lbl">The stale-summary trap</span>
|
||||
The in-game Details panel reads a <em>summary copy</em> 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.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">06</span> <span>References that go nowhere</span> <span class="rest">a port has to survive these</span></h2>
|
||||
|
||||
<div class="col">
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="tbl-scroll">
|
||||
<table>
|
||||
<thead><tr><th>Reference</th><th>Where</th><th>Status</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="m">pmbase.t32</span></td><td><span class="note">named by <span class="m">pmbase.rat</span> in all four <span class="m">GP_STAGE_CLEAR</span> language builds</span></td><td><span class="chip c-no">nowhere on disc</span></td></tr>
|
||||
<tr><td><span class="m">SUBTITLE_S12B.tbl</span></td><td><span class="note">bound by the manifest, in all six languages</span></td><td><span class="chip c-no">resolves in none</span></td></tr>
|
||||
<tr><td><span class="m">logo1…logo4</span>, <span class="m">SYLPH_HD720p_8M-CBR_2ch</span></td><td><span class="note">manifest-bound movies</span></td><td><span class="chip c-no">no .wmv</span></td></tr>
|
||||
<tr><td><span class="m">dat\GP_TEST\</span></td><td><span class="note">a debug archive the script manifest points at</span></td><td><span class="chip c-no">not shipped</span></td></tr>
|
||||
<tr><td>6 × <span class="m">GP_MAIN_GAME_*2D</span></td><td><span class="note">711 entries each; eleven other paks are at 100 % by the same method</span></td><td><span class="chip c-no">0 % named</span></td></tr>
|
||||
<tr><td><span class="m">GP_READY_ROOM.pak</span></td><td><span class="note">the largest UI pak on the disc</span></td><td><span class="chip c-no">6 of 1 106 named</span></td></tr>
|
||||
<tr><td>42 ISL script-symbol keys</td><td><span class="note">hash-only field keys in <span class="m"><lang>\script\ID.tbl</span></span></td><td><span class="chip c-no">provably unrecoverable</span></td></tr>
|
||||
<tr><td>9 of 101 cutscenes</td><td><span class="note">no English transcript resolves</span></td><td><span class="chip c-part">unexplained</span></td></tr>
|
||||
<tr><td><span class="m">JNGL_001.slb</span></td><td><span class="note">headerless bank; payload is not a whole number of XMA1 packets</span></td><td><span class="chip c-part">does not decode</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="note-box">
|
||||
<span class="lbl">Why the 2D paks matter more than they look</span>
|
||||
Six archives at <em>exactly</em> 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.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2><span class="idx">07</span> <span>Still open</span></h2>
|
||||
|
||||
<div class="col">
|
||||
<ul class="plain">
|
||||
<li><strong>UI paint order from the bundle.</strong> Solved at runtime, not statically — and static is what a port needs.</li>
|
||||
<li><strong>The <span class="m">.prt</span> telop format.</strong> 22 cutscenes bind one; no parser exists.</li>
|
||||
<li><strong>Collision meshes.</strong> <span class="m">CollisionSet_S<NN>.bin</span> is documented and unparsed.</li>
|
||||
<li><strong>47 XBG7 resources</strong> still miss — mostly pose/proxy composites and damage variants, not a threshold away.</li>
|
||||
<li><strong>~11 GHAD save fields</strong> unnamed, and difficulty-versus-stage undecided: three fields hold the value 2.</li>
|
||||
<li><strong>The naming of the 2D and Ready Room archives</strong>, which is the largest single block of unreachable content on the disc.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer>
|
||||
Counts measured on the retail extract (USA/Europe, En/Ja). Container-format
|
||||
counts exclude <span class="m">sound.pak</span> and entries over 8 MB.<br/>
|
||||
Confidence follows the corpus convention — confirmed · partial · refuted —
|
||||
and is per-claim, never per-document.
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
Reference in New Issue
Block a user