mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-26 21:36:06 -04:00
b6ea3a2747
- extract.py: one-command full-disc extraction (27 files: 24 Track-1 verbatim
+ 3 .DA as raw CD-DA audio from tracks 2-4) over the frozen iso9660.py
- cd_archive.py + pac.py: .CD TOC walker + PAC splitter, {index}.{type} naming
(no collisions; 447 sub-files, 1189 entries), SQV detection (98)
- lzss.py: game-semantics decoder (pos==0 terminator, -1 bias) + 5 unit tests;
138 type-4 decoded clean (0 warnings)
- manifest.py: deterministic JSON-Lines manifest (1801 artifacts) + --verify
- crosscheck.py + tools/brave-CUE/posix_shim.h: build CUE's brave from source
(GPL untouched), byte-compare; 1484 raw identical, 138/138 type-4 F6
- docs/formats.md: F5 counts (SC02=43), F2 refuted, F6 resolved, F1 type-7=139
- docs/effort-map.md + CLAUDE.md: Effort-map check rule (R7), Ultracode fix
- .gitignore: /extracted/* + !manifest/EXE exceptions; brave _build/
- install gcc 13.3.0 for the oracle
- milestone: one command extracts the full disc; MAIN.CD=49 sub-files; decoder
agrees byte-for-byte with CUE up to the game pos==0 terminator; zero ROM
staged; project version 1.1.0 -> 1.2.0
93 lines
3.3 KiB
Python
93 lines
3.3 KiB
Python
"""Standalone unit tests for the game-semantics LZSS decoder (lzss.py).
|
|
|
|
Run::
|
|
|
|
python3 tools/bfm_extract/test_lzss.py
|
|
|
|
Synthetic streams hand-encoded from docs/formats.md §4 to exercise literals, the
|
|
``pos == 0`` terminator, a back-reference match, and the load-bearing -1 ring
|
|
bias. No disc required. Real-data byte-for-byte validation against CUE's brave
|
|
happens in T6 (crosscheck.py).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from .lzss import decompress
|
|
except ImportError: # pragma: no cover - exercised only as a loose script
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
from lzss import decompress # type: ignore
|
|
|
|
|
|
def test_literals_then_terminator():
|
|
# flag 0x07 = literal,literal,literal,match; 'A','B','C'; then 00 00 (pos=0).
|
|
stream = bytes([0x07, 0x41, 0x42, 0x43, 0x00, 0x00])
|
|
r = decompress(stream)
|
|
assert r.data == b"ABC", r.data
|
|
assert r.terminated is True
|
|
assert r.input_consumed == 6, r.input_consumed
|
|
assert r.warnings == (), r.warnings
|
|
|
|
|
|
def test_backreference_and_minus1_bias():
|
|
# 'A','B' literals, then a match (pos=1, len-field=0) which via the -1 bias
|
|
# copies ring[0],ring[1] = 'A','B'; then 00 00 terminates. Output "ABAB".
|
|
# flag 0x03 = literal,literal,match,match.
|
|
stream = bytes([0x03, 0x41, 0x42, 0x01, 0x00, 0x00, 0x00])
|
|
r = decompress(stream)
|
|
assert r.data == b"ABAB", r.data
|
|
assert r.terminated is True
|
|
assert r.input_consumed == 7, r.input_consumed
|
|
assert r.warnings == (), r.warnings
|
|
|
|
|
|
def test_longer_match_overlapping_copy():
|
|
# 'X' literal, then a match pos=1 with len-field=2 -> length=3 -> 4 bytes.
|
|
# The match reads ring[(pos-1+i)] = ring[i] while the write pointer (r=1)
|
|
# advances alongside, so the single 'X' propagates: an overlapping/RLE-style
|
|
# copy. The 4 match bytes are all 'X'. Output = "X" + "XXXX" = "XXXXX".
|
|
# Validates both (code>>10)+1 length and the self-referential ring copy.
|
|
# code = (2 << 10) | 1 = 0x0801 -> low 0x01, high 0x08.
|
|
# flag 0x01 = literal, match, match-terminator.
|
|
stream = bytes([0x01, 0x58, 0x01, 0x08, 0x00, 0x00])
|
|
r = decompress(stream)
|
|
assert r.data == b"XXXXX", r.data
|
|
assert r.terminated is True
|
|
assert r.warnings == (), r.warnings
|
|
|
|
|
|
def test_no_terminator_warns():
|
|
# one literal 'Z', then input ends with no pos==0 match.
|
|
stream = bytes([0x01, 0x5A])
|
|
r = decompress(stream)
|
|
assert r.data == b"Z", r.data
|
|
assert r.terminated is False
|
|
assert any("no pos==0 terminator" in w for w in r.warnings), r.warnings
|
|
|
|
|
|
def test_trailing_nonzero_after_terminator_warns():
|
|
# flag 0x00 -> first token is a match; 00 00 terminates immediately; a stray
|
|
# 0xFF follows within the buffer -> length cross-check warning.
|
|
stream = bytes([0x00, 0x00, 0x00, 0xFF])
|
|
r = decompress(stream)
|
|
assert r.data == b"", r.data
|
|
assert r.terminated is True
|
|
assert r.input_consumed == 3, r.input_consumed
|
|
assert any("after terminator" in w for w in r.warnings), r.warnings
|
|
|
|
|
|
def _run() -> int:
|
|
tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")]
|
|
for t in tests:
|
|
t()
|
|
print(f" [PASS] {t.__name__}")
|
|
print(f"\n{len(tests)} lzss tests passed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(_run())
|