Live save-to-slot-1 trace via the PCSX-Redux web API confirmed the memcard path bu00:BASLUS-00726MUSASHI (libmcrd) + the directory/title-frame staging in low RAM. The save-DATA serialization couldn't be isolated from a live before/after diff (running-game churn; the one big changed region @0x8007CD28 is an unrelated UI list). Clean route = the .mcd file / a paused capture -> deferred as Gen3-repack territory, NOT needed for the SaveLoadRoutine byte-match (code-level). SaveLoadRoutine stays the documented hard-defer (1139-ins multi-entry, Gen1 precedent, P9).
32 KiB
On-Disc File Formats — Brave Fencer Musashi (SLUS-00726 USA)
Evolvable reference layer. This file may be updated freely as RE progresses; note each change in the active phase log (
CURRENT_PHASE.md). It is the canonical specification of the disc image layout and the game's container/compression formats (.CD, PAC, LZSS, SQV). Every entry is tagged with its provenance; anything not byte-verified against the local US dump is explicitly marked UNVERIFIED or TBD. RAM addresses are owned bydocs/memory-map.md(authoritative); they are cross-referenced here, never redefined.
Primary sources reconciled in this file:
| Source | What it provides | Trust level |
|---|---|---|
Local dump Brave Fencer Musashi (USA)\ (4×.bin + .cue) |
All verified-local facts (re-parsed 2026-06-10) |
Ground truth |
tools/brave-CUE/brave.c + brave.txt (CUE, 2012-2013, GPLv3) |
Reference extractor: .CD TOC, PAC header, LZSS decoder | Authoritative for tool semantics; diverges from game semantics on LZSS termination (§4.4) |
| AiroRom wiki MIPS listing (pleonex) | Game-side LZSS decompressor, byte-verified vs local US EXE | Verified (see memory-map §2.1) |
jywjyw/bravefencer-hack (Java repacker, 2018) + doc/note.md |
LIST.CD structure, PAC details, working recompressor, overlay map | JP (SLPS-01490) unless noted — re-verify for US |
| romhacking.net thread 15730 (Vehek, Dark Fulgore, Auryn, 2013-2014) | LZSS termination/bias discoveries, US datapoints | Reported, partially re-verified |
1. Disc layout
1.1 BIN/CUE — 4 tracks
From the local .cue and track binaries (verified-local):
| Track | Mode | File size (bytes) | Sectors (2352 B raw) | Content |
|---|---|---|---|---|
| 01 | MODE2/2352 | 364,846,944 | 155,122 | Data (ISO9660 volume MUSASHI) |
| 02 | AUDIO (CD-DA) | 10,678,080 | 4,540 | ST01_13A.DA (see §1.4); INDEX 00 at 00:00:00, INDEX 01 at 00:02:00 |
| 03 | AUDIO (CD-DA) | 7,328,832 | 3,116 | ST01_13B.DA; same index layout |
| 04 | AUDIO (CD-DA) | 33,167,904 | 14,102 | DUMMY_DA.DA; same index layout |
Volume space size in the PVD = 176,880 sectors = 155,122 + 4,540 + 3,116 + 14,102 exactly (the ISO volume spans all four tracks) (verified-local).
1.2 Track 1 sector geometry
- Raw sectors are 2352 bytes (MODE2). User data (2048 bytes) begins at raw-sector offset +24 (12-byte sync + 4-byte header + 8-byte Mode 2 Form 1 subheader).
- Byte offset of LBA n's user data within
(Track 1).bin=n * 2352 + 24. - PVD at LBA 16; root directory record at LBA 22 (2048 bytes) (verified-local).
1.3 ISO root inventory — 27 files, no directories
Re-parsed from the raw image 2026-06-10 (verified-local). All names carry the ;1 version
suffix on disc; LBA = 2048-byte-sector index from track start; sizes are exact byte lengths
from the directory records.
| File | LBA | Size (bytes) | Notes |
|---|---|---|---|
| SYSTEM.CNF | 23 | 68 | Boot config (§1.5) |
| SLUS_007.26 | 24 | 413,696 | PS-X EXE (§1.6) |
| MUSA.ID | 226 | 9 | Disc-ID check file; contents MUSASHI\r\n |
| LIST.CD | 227 | 4,096 | Concatenated .CD TOCs (§2.3) |
| MAIN.CD | 229 | 5,988,352 | Resident/global archive, 49 sub-files (§2.2) |
| LOGOA.STR | 3,153 | 3,080,192 | FMV (out of scope, §7) |
| LOGOB.STR | 4,657 | 3,080,192 | FMV |
| SC01.CD | 6,161 | 20,078,592 | Chapter 1 scenario archive |
| ST01.STR | 15,965 | 12,795,904 | FMV |
| OPEN.STR | 22,213 | 22,724,608 | FMV |
| SC02.CD | 33,309 | 22,126,592 | Chapter 2 scenario archive |
| ST02.STR | 44,113 | 21,430,272 | FMV |
| SC03.CD | 54,577 | 64,569,344 | Chapter 3 scenario archive (largest) |
| ST03.STR | 86,105 | 8,028,160 | FMV |
| SHOPS01.STR | 90,025 | 7,536,640 | FMV |
| OUT.STR | 93,705 | 1,867,776 | FMV |
| SC04.CD | 94,617 | 19,425,280 | Chapter 4 scenario archive |
| ST04.STR | 104,102 | 17,825,792 | FMV |
| SC05.CD | 112,806 | 16,498,688 | Chapter 5 scenario archive |
| ST05.STR | 120,862 | 9,240,576 | FMV |
| SC06.CD | 125,374 | 23,928,832 | Chapter 6 scenario archive |
| ST06.STR | 137,058 | 18,612,224 | FMV |
| SC07.CD | 146,146 | 12,734,464 | Chapter 7 scenario archive |
| END.STR | 152,364 | 5,341,184 | FMV |
| ST01_13A.DA | 155,272 | 8,990,720 | CD-DA pointer → track 2 (§1.4) |
| ST01_13B.DA | 159,812 | 6,074,368 | CD-DA pointer → track 3 |
| DUMMY_DA.DA | 162,928 | 28,573,696 | CD-DA pointer → track 4 (padding audio) |
Observations (verified-local):
- 12 STR files (not 13 as one earlier planning summary said — that was a miscount;
this table is the corrected, re-verified inventory). Tally: 12 STR + 3
.DA+ 12 others (SYSTEM.CNF, SLUS_007.26, MUSA.ID, LIST.CD, MAIN.CD, SC01–SC07.CD) = 27. - STR movies are interleaved between the chapter archives in ascending LBA order (seek-distance optimization).
- All
.CDsizes are exact multiples of 2048. - Layout is contiguous: e.g. SLUS_007.26 occupies LBA 24–225 (202 sectors), MUSA.ID follows at 226; MAIN.CD ends exactly where LOGOA.STR begins.
1.4 .DA files = directory entries pointing into the CD-DA tracks
Verified by arithmetic in-session (2026-06-10): each .DA extent LBA equals the first
INDEX 01 sector of an audio track, and each size equals (track sectors − 150 pregap) × 2048
(the directory records use 2048 B/sector accounting even though the underlying sectors are
2352-byte audio):
| File | Extent LBA | Track start (cumulative) | + 150 pregap | Size check |
|---|---|---|---|---|
| ST01_13A.DA | 155,272 | track 2 @ 155,122 | = 155,272 ✓ | (4,540−150)×2048 = 8,990,720 ✓ |
| ST01_13B.DA | 159,812 | track 3 @ 159,662 | = 159,812 ✓ | (3,116−150)×2048 = 6,074,368 ✓ |
| DUMMY_DA.DA | 162,928 | track 4 @ 162,778 | = 162,928 ✓ | (14,102−150)×2048 = 28,573,696 ✓ |
So the .DA files contain no data inside track 1; they are the ISO-level handles the game
uses to seek CD-DA audio. They are out of scope for the extraction pipeline (§7).
1.5 SYSTEM.CNF (68 bytes, verified-local)
BOOT = cdrom:\SLUS_007.26;1
TCB = 4
EVENT = 16
STACK = 801ffff0
1.6 SLUS_007.26 — PS-X EXE header (verified-local)
| Field | Value | Notes |
|---|---|---|
| magic | PS-X EXE |
|
pc0 |
0x80010000 | Entry point |
gp0 |
0x00000000 | No $gp in header ⇒ likely −G0; confirm in Phase 6 |
t_addr |
0x80010000 | Image spans 0x80010000–0x80074800 |
t_size |
0x64800 | File = 0x800 header + 0x64800 text = 0x65000 = 413,696 bytes ✓ |
sp |
0x801FFFF0 | Matches SYSTEM.CNF STACK |
The EXE is tiny (~400 KB) relative to a ~340 MB game: the bulk of code/script lives inside
the .CD archives as LZSS-compressed PAC type-4 overlays (§3, §4; memory-map §4).
1.7 The \DEBUG.BIN;1 ghost reference
The EXE's CD-path string table (CdPathTable, base 0x80062C24, 23 entries, 0x30-byte
stride — see memory-map §2.2) contains the string \DEBUG.BIN;1 at 0x80063044
(verified-local), adjacent to \MUSA.ID;1 at 0x80063014. DEBUG.BIN does not exist on the
retail ISO (see §1.3). A surviving code path therefore references a debug loader file that
was stripped from the disc — a prime RE anchor (xref the string to find the debug loader;
relates to the TCRF L3 debug menu, memory-map §5).
Also note: the table covers LIST.CD, MAIN.CD, SC01–SC07.CD and ten of the STR movies, but
LOGOA.STR/LOGOB.STR and the three .DA files are absent from it — referenced elsewhere
(TBD, memory-map open question #14).
2. .CD container format
2.1 TOC structure (verified-local for MAIN.CD; brave.c is the reference parser)
A .CD file begins with a table of contents at offset 0:
| Offset | Type | Meaning |
|---|---|---|
| +0x00 | u32 LE | count — number of sub-files (MAIN.CD: 0x31 = 49) |
| +0x04 | u32 LE | zero |
| +0x08 + 8*i | u32 LE | sub-file i start, in 2048-byte sectors relative to the start of the .CD file |
| +0x0C + 8*i | u32 LE | sub-file i length in bytes (unpadded) |
- The first field of each entry is NOT a disc LBA.
brave.txtcalls it "LBA", but the code inbrave.c::Extract()proves it is file-relative (buffer + entry_sector * 0x800). Do not add the.CDfile's own disc LBA when splitting. - The TOC and every sub-file are sector-aligned (0x800), zero-padded, and contiguous; the first sub-file starts at sector 1 (offset 0x800).
- MAIN.CD and SC01–SC07.CD use this identical format — one code path in every known tool,
no per-file special-casing (brave.c; confirmed by jywjyw's
Conf.javarebuilding all 8 with the sameCdRebuilder).
2.2 Roles of each .CD file
| File | Role | Sub-file count | Provenance |
|---|---|---|---|
| LIST.CD | Concatenated TOCs of the other 8 .CDs (§2.3) — not itself a normal container |
n/a | jywjyw (JP) + partial local check |
| MAIN.CD | Global/resident archive: loader-resident script blob (FILE_010 PAC entry index 1, type 1), fonts, shared assets |
49 (0x31, verified-local US; same count in JP) | local + jywjyw |
| SC01.CD | Chapter 1 scenario archive (per-location PAC chains: scripts/overlays, graphics, VAB audio, SQV music) | 86 (0x56, verified-local US 2026-06-13; == JP) | local + jywjyw |
| SC02.CD | Chapter 2 scenario archive | 43 (verified-local US 2026-06-13 — corrects the "45" Auryn hearsay) | local |
| SC03.CD | Chapter 3 scenario archive | 140 (verified-local US 2026-06-13) | local |
| SC04.CD | Chapter 4 scenario archive | 31 (verified-local US 2026-06-13) | local |
| SC05.CD | Chapter 5 scenario archive | 30 (verified-local US 2026-06-13) | local |
| SC06.CD | Chapter 6 scenario archive | 39 (verified-local US 2026-06-13) | local |
| SC07.CD | Chapter 7 scenario archive | 29 (verified-local US 2026-06-13) | local |
Sub-files inside a .CD are either PAC chains (§3) — identified by the PAC\0 magic — or
non-PAC files, which per brave.txt are SQV (MIDI) archives (§5). Anything without either
signature is unknown (TBD).
JP reference sizes (jywjyw Conf.CD_JAPAN_SIZE, for cross-version diffing only — the US
sizes are in §1.3): MAIN 6,205,440; SC01 20,615,168; SC02 6,580,224; SC03 78,168,064;
SC04 20,764,672; SC05 16,502,784; SC06 23,908,352; SC07 12,730,368.
2.3 LIST.CD (4,096 bytes = 0x1000)
Per brave.txt: "contiene las cabeceras de todos los archivos CD" (contains the headers of
all the CD archives). The exact structure, per jywjyw's ListCdWriter.java +
CdHeader.trimBuild() (JP repacker source):
- LIST.CD = the 8 TOCs of MAIN.CD, SC01.CD … SC07.CD in that order, each trimmed to
exactly
8 + 8*Nbytes (u32 count + u32 zero + N entries — i.e. the TOC without its sector padding), concatenated back-to-back with no alignment between tables, written into a fixed 0x1000-byte (2-sector) buffer. - Purpose (jywjyw
note.md): loaded into RAM at game start so the engine can locate any sub-file in any.CDwithout re-reading each archive's header sector. US RAM cache location TBD (memory-map §3.2listCd_ramCache, open question #3).
Verification status: locally confirmed only that LIST.CD begins with an exact copy of
MAIN.CD's TOC (first bytes identical to MAIN.CD sector 0). The full
trimmed-concatenation layout (SC01–SC07 portions, their order, the no-alignment packing) is
from the JP repacker and is UNVERIFIED against the US disc — verify by recomputing the
8 trimmed TOCs from the US .CD files and diffing against LIST.CD (cheap Phase-2 check).
⚠️ CUE's BRAVE tool does not special-case LIST.CD. Because LIST.CD starts with a valid TOC, running BRAVE on it parses MAIN.CD's table and then reads garbage sector offsets inside a 4 KB file. Never feed LIST.CD to the extractor; treat it as derived data (regenerable from the other 8 TOCs).
3. PAC archive format
A .CD sub-file whose first u32 is 0x00434150 ("PAC\0" LE) is a PAC chain: a
sequence of entries, each with its own 0x800-byte header block immediately followed by its
payload, repeated until an entry's last-flag is set.
3.1 Entry header (0x800-byte block; fields per brave.c/brave.txt + jywjyw PacHeader.java)
| Offset | Type | Meaning | Status |
|---|---|---|---|
| +0x00 | u32 LE | Magic 0x00434150 = "PAC\0" |
verified (tool + local) |
| +0x04 | u8 | Type (0–8), selects payload interpretation (§3.2) | verified |
| +0x05 | u8 | Last-entry flag: 1 = last entry in chain, 0 = more follow | verified |
| +0x06 | u16 | Zero | verified |
| +0x08 | u32 LE | Unknown — partly characterized (Phase 2, 2026-06-13). Across all 1189 US PAC entries it is non-zero only for type 0 (301/301 graphics) and type 6 (29/29); it is 0 for every type-4 entry, so it is NOT a decompressed-size hint (the original F2 guess is refuted). Likely a type-specific parameter (type-0 graphics dimensions/format; type-6 unknown). jywjyw preserves it verbatim. Decode meaning via the loader (memory-map #7) | type-0/6 param (F2) |
| +0x0C | u32 LE | Length including this 0x800 header — payload = len − 0x800, payload starts at entry+0x800 |
verified |
| +0x10 … +0x7FF | — | Not interpreted by any known tool. jywjyw preserves the entire 0x800 header verbatim, implying it may contain meaningful non-zero bytes | UNVERIFIED |
3.2 Type table
Sources: brave.txt (CUE) + jywjyw note.md.
| Type | Payload | Notes |
|---|---|---|
| 0 | Graphics | Mostly 32×32 image blocks (8bpp = 64×32, 4bpp = 128×32 per jywjyw) |
| 1 | Graphics / uncompressed script | The memory-resident MAIN.CD FILE_010 entry "1.1" and the shop-dialog "2.1" scripts are type 1 |
| 2 | VAB header (.VH) |
PsyQ sound bank header |
| 3 | VAB body (.VB) |
PsyQ sound bank sample data |
| 4 | LZSS-compressed data (§4) | Location scripts containing embedded MIPS code = the game's overlays. The only compressed type |
| 5 | — | Unused per CUE ("---") |
| 6 | Unknown | TBD. jywjyw notes all of a type-6 entry's data fits within its first 0x800 bytes |
| 7 | Unknown | TBD |
| 8 | Graphics |
Only type 4 is compressed; everything else is stored raw. ("All .CD files are LZSS-compressed" — an early planning claim — is wrong; compression is a per-PAC-entry type flag, with no magic and no size heuristic.)
Observed US type distribution (Phase 2, all 1189 PAC entries, 2026-06-13): type 0 = 301, type 1 = 166, type 2 = 201, type 3 = 201, type 4 = 138, type 6 = 29, type 7 = 139, type 8 = 14; type 5 = 0 (absent, consistent with "unused"). Note types 2 and 3 are equal counts (201 each) — VAB header/body pairs. Type 7 is common (139 entries), not rare, and type 6 (29) — both remain uninterpreted (F1), extracted raw.
3.3 Chaining (brave.c::UnPAC, verbatim semantics)
pos = 0
do:
type = buf[pos+0x04]; last = buf[pos+0x05]
len = u32le(buf + pos + 0x0C) # includes the 0x800 header
payload = buf[pos+0x800 : pos+len] # len − 0x800 bytes
pos += (len + 0x7FF) & ~0x7FF # next entry at sector alignment
while not last
Entries are sector-aligned, zero-padded between payload end and the next 0x800 boundary.
4. LZSS variant (PAC type-4 payloads) — CRITICAL SECTION
Two authoritative implementations exist and they disagree on termination (§4.4):
the game's own MIPS decompressor (US EXE 0x80018730, byte-verified against the AiroRom
listing — memory-map §2.1) and CUE's brave.c::Decode() (tool reference). Everything below
is reconciled from both plus Vehek's findings in romhacking thread 15730.
4.1 Parameters
| Parameter | Value | Notes |
|---|---|---|
POS_BITS |
10 | Position field width |
N (ring size) |
1024 bytes (1 << 10) |
Game keeps the ring in scratchpad 0x1F800000 (memory-map §3.1) |
THRESHOLD |
1 (CUE's naming) | jywjyw's encoder calls it 2 — naming difference only; both agree real match length = 6-bit field + 2 |
Max match F |
65 bytes ((1 << 6) + 1 + the loop's <=) |
Copy lengths range 2..65 |
| Ring initial state | Zero-filled, write index r = 0 |
Encoder-side origin is index 1 (§4.3, §4.6) |
| Decompressed size | No size field anywhere | CUE grows the output buffer dynamically (start 128 KB, +64 KB steps); the game decodes until the terminator |
4.2 Flag engine (LSB-first with 0xFF00 sentinel)
From brave.c, verbatim:
if (!((flags >>= 1) & 0xFF00)) flags = 0xFF00 | encoded[enc_size++];
if (flags & 1) { /* literal */ } else { /* match */ }
- A flag byte is loaded into the low 8 bits with sentinel
0xFF00ORed above it; each step right-shifts once and tests bit 0. After 8 shifts the sentinel is exhausted (& 0xFF00 == 0) and the next flag byte is fetched. Net effect: flag bits are consumed LSB-first, one flag byte governs the next 8 tokens. - The game's MIPS routine implements the same order with a mask walking 0x01→0x80 against a held token byte (AiroRom listing) — equivalent semantics, verified.
- Flag bit 1 = literal, 0 = match reference.
4.3 Literal and match paths
Literal (flag bit = 1): read 1 input byte → append to output and write to
ring[r]; r = (r + 1) & 0x3FF.
Match (flag bit = 0): read u16 little-endian code, then:
pos = code & 0x3FF // low 10 bits — ABSOLUTE ring index (NOT a relative offset!)
len = (code >> 10) + 1 // THRESHOLD = 1
for i in 0 ..= len: // len+1 iterations => 2..65 output bytes
b = ring[(pos - 1 + i) & 0x3FF] // note the −1 bias
output(b); ring[r] = b; r = (r + 1) & 0x3FF
- Absolute index, not distance: the 10-bit
posaddresses the ring directly. Generic LZSS tools (relative-distance decoders) will NOT work on this format. - The −1 bias: stored positions are ring-index+1, because the game's encoder starts writing at ring index 1 (Vehek: "the starting position in the buffer appears to be 1"). An off-by-one here corrupts every decompressed overlay. Validate any new implementation by diffing a decompressed type-4 blob against a live emulator RAM dump at the overlay load address (memory-map §4, open question #1).
- The ring write pointer
radvances on every output byte (literals and match bytes alike) and is independent ofpos.
4.4 Termination — GAME vs TOOL discrepancy (load-bearing)
| Implementation | Stop condition | Consequence |
|---|---|---|
| Game (MIPS @ 0x80018730, US-verified; Vehek Aug 2014) | A match code whose low 10 bits == 0 (pos == 0) terminates the stream. Streams typically end with bytes 00 00. The PAC header length field has no effect on the game's decompressed size (Vehek-tested) |
pos == 0 is reserved ⇒ legal references are pos 1..1023, i.e. biased start indices 0..1022 |
| CUE's brave.c | No terminator check; stops when the input length (PAC len − 0x800) is consumed |
Trusts the header length; never sees pos == 0 specially |
Project rule: our extractor must implement game semantics — stop on pos == 0 —
with a length sanity cross-check: after hitting the terminator, verify input consumption
is consistent with the PAC length field (len − 0x800, allowing trailing padding), and warn
on any stream that exhausts its input without a terminator or overruns it.
Verified Phase 2 (2026-06-13, F6 RESOLVED): all 138 US type-4 streams decode cleanly
under game semantics — every one hits a pos == 0 terminator with the length cross-check
satisfied (zero warnings across the whole disc). Cross-checked byte-for-byte against
brave.c: our output is a strict prefix of brave's for all 138, and brave appends
exactly 2 bytes to each — it decodes the terminating 00 00 as a match (code 0 ⇒
pos 0, len 1 ⇒ emits ring[1023], ring[0] = 00 00) instead of stopping. So game and tool
semantics coincide on the real payload; the only divergence is brave's 2-byte over-decode
of the terminator/padding. Our game-semantics output is the correct one. No retail stream was
found where the two diverge within the payload.
Additional tool caveat: brave.c's ring[] is a global that is never re-zeroed between
files (only r resets), so CUE's tool is not a strict oracle for streams that reference
ring positions not yet written; the game expects a zero-filled ring at stream start.
4.5 Game-side streaming state machine (resumable, sector-fed)
The game's decompressor consumes input in 0x800-byte CD-sector chunks and is a resumable 5-state machine so decompression can suspend mid-token at a sector boundary. All addresses US-verified; full ledger in memory-map §2.1/§3.1:
| Item | Address (US) |
|---|---|
| Decoder entry | 0x80018730 (LzssDecodeSector); returns at 0x80018910 |
| State jump table | 0x80072A30 (00000000 FFFFFFFF then 5 state pointers) |
| State variable | 0x800C7D24 (runtime BSS) |
| Persistent state block | 0x800747A0 mask, 0x800747A4 token byte, 0x800747AC output ptr, 0x800747B0 ring index, 0x800747B4 partial 16-bit match code |
| Ring buffer | 0x1F800000 (scratchpad, 1024 B) |
| Sector staging buffer | JP 0x80078BD0–0x800793D0 — JP-only, US equivalent TBD (memory-map §3.2) |
States: 0 finish, 1 init token ring, 2 check token bit, 3 process match, 4 next token bit. The partial-code slot exists precisely because a 16-bit match code can straddle a sector boundary.
4.6 Recompression — NOT byte-stable (encoder constraints)
- LZSS parsing is ambiguous (literal-vs-match and match-length choices); jywjyw's working binary-search-tree encoder explicitly notes "recompressed 0.4 script not equals original". No recompressor is expected to reproduce Square's original bitstream.
- ⇒ Compressed type-4 payloads are opaque binary for build verification. Only the decompressed overlays can be matched; the matching build treats compressed blobs as binary data. Repacking/injection is a Gen3 parking-lot concern.
- Any future encoder MUST: (a) never emit a
pos == 0reference mid-stream — stock Okumura-style LZSS output does, and the game treats it as end-of-stream (Vehek's failed 2013 attempt); (b) terminate the stream with apos == 0pair; (c) start its ring origin at index 1 (jywjyw'sCompresser.javadoes exactly this — it also documents the encoded byte order: byte0 =pos & 0xFF, byte1 =(len-2) << 2 | pos >> 8).
5. SQV format (sequenced music archives)
.CD sub-files that are not PAC chains; signature ".sqv". Source: brave.txt (CUE) —
not yet locally re-verified against extracted US files (UNVERIFIED).
| Item | Spec |
|---|---|
| Magic | 4 bytes ".sqv" at +0x00 |
| Header u32s | Up to 3 u32 offsets to MThd (Standard MIDI File) sub-files. Each value is an offset to which 4 must be added (+4 bias); a value of 0 means the archive holds a single MThd (no further offsets) |
| Header alignment | 16 bytes, zero-padded |
| Member alignment | 4 bytes, zero-padded |
| VAB | A VAB file at fixed offset 0x7000, after the MThd data |
Pairs with PAC types 2/3 (VAB header/body) for the game's sound banks. Driver — semi-custom
(Phase 12, partial): the resident engine holds a thin Square-custom sound layer over PsyQ
libsnd. DsMix (0x800D1BD8) is a custom 2-line wrapper ({ FUN_800d1bf8(); return 1; } — ignores
its arg; NOT the stock libsnd DsMix — ghidra_psx_ldr's PsyQ-4.7 signature hit there was coincidental,
Phase-12 T1). The resident's sound cluster wraps/calls the (EXE-resident) PsyQ libsnd SEQ/VAB
primitives; the SQV→libsnd bridge is the custom glue (its bytes were matched in the Phase-12 harvest,
but the deep MThd→SEQ + VAB-load playback semantics are not exhaustively traced — deferred to Gen3 repack
work, and not needed for the byte-match). Verdict: semi-custom = Square wrappers/glue over stock PsyQ
libsnd sequencing.
6. Save / memory-card format (Q#5 — Phase 12, partial)
SaveLoadRoutine @ 0x8002B154 (EXE) is a multi-entry save/load handler blob driving the linked
PsyQ libmcrd primitives (0x8005Fxxx–0x80061xxx, e.g. 0x800603BC heavily, 0x8006023C,
0x80060AE0, 0x80061114). Its config/dispatch data is saveHeaderTemplate @ 0x80072DF0:
| Off | Bytes | Meaning |
|---|---|---|
| +0x00 | 82 67 82 85 82 92 82 8F + pad |
4-char SJIS label (Hero?) |
| +0x0C | "BASLUS-00726MUSASHI" |
memcard filename (PS1 BA + region-product + name) |
| +0x20 | SJIS BRAVE FENCER MUSASHI |
save title (BIOS memcard-manager display) |
| +0x44 | 2A 00 00 00 |
count/flag (0x2A = 42) |
| +0x54 | 0x8002B154, 0x8002B1AC, 0x8002BEA4, … (a run of 0x8002Bxxx pointers) |
handler dispatch table — addresses INTERIOR to the SaveLoadRoutine blob; the save/load/validate sub-operations |
So the Gen1 "multi-entry blob" note is this dispatch table: the header holds a vector of sub-handler addresses (the 3 known entries + more), each performing one memcard step via libmcrd.
VERIFIED (static, Phase 12): the header layout above; SaveLoadRoutine calls the linked libmcrd objects.
TBD (needs Ghidra-on-EXE + a Drew-operated PCSX-Redux save trace + PsyQ-memcard research, R17): the
save-DATA block serialization — almost certainly the Phase-3 player/progress state (0x80078Exx:
gold/HP/BP/day/hour/flags) + a checksum; capture the live memcard buffer from a real save to pin the
field order + the checksum algorithm — and matching the tractable handler entries. dumps/ram_savescreen.bin
(save screen, overlay flushed) holds the live saveHeaderTemplate for cross-check.
Runtime-confirmed (Phase 12 trace — live save to card slot 1): the memcard path is
bu00:BASLUS-00726MUSASHI (PsyQ device path bu00: = card unit 0 / slot 1, via libmcrd); the
title-frame + a directory list of 32-byte records stage in low RAM (~0x80004E00). The
save-DATA serialization (which game-state region is written + the checksum) was NOT isolated
from a live before/after RAM diff — the running game churns the diff (491 changed runs; the one
big region @0x8007CD28 is an unrelated UI list of "XXX,"-tagged records, not save data), and
BFM appears to serialize from the live game-state rather than a findable staging copy. The clean
route to the byte-exact block is the .mcd memcard file (or a paused-state capture).
Deferred as Gen3-repack territory — not needed for the SaveLoadRoutine byte-match (that
is code-level: matched from the disassembly like any other function, when attempted; the 1139-ins
multi-entry blob remains the documented hard-defer per the Gen1 precedent, P9).
6. Extractor implementation notes (Phase 2 pipeline)
- Naming collision in CUE's tool — do not copy.
brave.csaves sub-files as<file>.dir/FILE_%03d(fine) but names PAC chain entries%03dby type byte (sprintf(name, "%s.dir/%03d", filename, num)wherenum= type). Two same-type entries in one chain silently overwrite each other on extraction. Our extractor uses{index}.{type}naming (jywjyw'sCdSplitterconvention:0.4,1.1,2.1…) so every entry survives and the type remains visible. - Decode rule: decompress only type-4 entries, using game termination semantics with the length cross-check (§4.4). Save all other types raw. Save non-PAC sub-files as-is (SQV etc.).
- Never run the pipeline on LIST.CD (§2.3); instead verify LIST.CD by regenerating the trimmed-TOC concatenation from the 8 archives and diffing.
- Round-trip / repack reference (Gen3):
github.com/jywjyw/bravefencer-hack(Java/Maven, 2018) —CdHeader.java(TOC rebuild: count, entries from sector 1,addr += paddedLen/0x800),PacHeader.java(full 0x800 header incl. unknown bytes),hack/Compresser.java(game-compatible LZSS encoder; LGPL 2.1 header, adapted from JSword),hack/CdRebuilder.java(rebuilds all 8 .CDs, preserves original PAC headers viaheaders.bin, recomputes length fields, pads to 0x800, warns if a rebuilt .CD outgrows the original),hack/ListCdWriter.java(LIST.CD regeneration),dump/Uncompresser.javadump/CdSplitter.java(decoder/splitter with the{index}.{type}naming). Itsdoc/note.mdis the best third-party memory/format document (JP-based).
- Cross-validation — DONE (Phase 2,
tools/bfm_extract/crosscheck.py). brave is built from source on WSL viatools/brave-CUE/posix_shim.h(the GPL upstreambrave.c/common.incstay byte-for-byte unmodified;brave.exeis a Windows PE, unused on Linux). The harness maps brave's type-only names back to our{index}.{type}by re-walking each chain (skipping "shadowed" same-type entries brave overwrites), then byte-compares. Result on--sample all: 1484 raw payloads identical, 138/138 type-4 byte-identical up to ourpos==0terminator (brave's 2-byte over-decode logged as the expected F6 divergence; see §4.4). - Licensing:
brave.cis GPLv3 (study/port is fine; note license if vendoring);Compresser.javacarries LGPL 2.1.
7. Out of scope (for now)
| Format | Status |
|---|---|
.STR (12 files) |
Standard PS1 STR movie streams (Mode 2 Form 2 interleaved video/XA). No game-specific container suspected (UNVERIFIED — assumed standard). Not part of the extraction/matching pipeline |
.DA (3 files) |
CD-DA track handles (§1.4). Phase 2 (user scope change 2026-06-13): now extracted as raw 2352-byte/sector audio from tracks 2-4 — ST01_13A.DA=Track 2 (10,325,280 B), ST01_13B.DA=Track 3 (6,976,032 B), DUMMY_DA.DA=Track 4 (32,815,104 B). Their 2048-based ISO sizes differ from the raw audio by design. Out of scope for matching (audio), but extracted for completeness |
8. Open questions specific to formats
| # | Question | Attack |
|---|---|---|
| F1 | PAC types 6/7 semantics (CUE "???", jywjyw "??") | Phase 2: both present in US (type 6 = 29, type 7 = 139 entries), extracted raw; semantics still TBD → Ghidra PAC-header parser in the loader |
| F2 | PAC header +0x08 u32 meaning | Phase 2: size-hint hypothesis REFUTED — +0x08 == 0 for all 138 type-4; non-zero only for type 0 (301/301) and type 6 (29/29). Remaining: decode the type-0/6 meaning via the loader |
| F3 | PAC header bytes +0x10..+0x7FF — meaningful content? | Hex-survey across all extracted headers (jywjyw preserves them) — not yet done |
| F4 | LIST.CD full layout on the US disc (only the MAIN.CD-TOC prefix is locally verified) | Deferred — extractor excludes LIST.CD (§2.3); regenerating the 8 trimmed TOCs and diffing is a cheap follow-up |
| F5 | MAIN=49, SC01=86, SC02=43 (not the hearsay 45), SC03=140, SC04=31, SC05=30, SC06=39, SC07=29 — read from each US TOC (§2.2) | |
| F6 | All 138 decode clean (0 warnings) and are byte-identical to brave up to our pos==0 terminator (§4.4) |
|
| F7 | Are .sqv details (3-offset cap, +4 bias, VAB at 0x7000) correct for US files? |
Phase 2: 98 SQV sub-files detected by .sqv magic and extracted raw; header details still UNVERIFIED (Gen3 parse) |
| F8 | Where are LOGOA/LOGOB.STR and the .DA files referenced, if not in CdPathTable? |
memory-map open question #14 |
Phase 2 extraction summary (2026-06-13). One command (
tools/bfm_extract/extract.py) extracts the full disc: 24 Track-1 files verbatim + 3.DAfiles as raw CD-DA audio from tracks 2-4 (each(Track N).bin= 150-sector INDEX 00→01 pregap + audio). The 8.CDarchives split into 447 sub-files → 1189 PAC entries ({index}.{type}naming, no collisions) → 138 type-4 LZSS payloads decoded under game semantics. A deterministic JSON-Lines manifest (manifest.jsonl+manifest.sha1, 1801 artifacts) is the reproducibility contract;--verifyre-checks it. Cross-validated byte-for-byte against CUE'sbrave(built from source viatools/brave-CUE/posix_shim.h): 1484 raw payloads identical, 138/138 type-4 F6-equivalent (ours = the game-correct prefix). EXE round-trips to SHA1143dbb89….