Files
boxsim-fileformat-rev/boxsim/container.py
T
magdevandClaude Fable 5 4737a5bae8 Add boxsim CLI: pure Python codec, netlist extraction, KiCad project generation
Replaces the bpj2ini.py prototype with a packaged tool. The A2F codec is now
reimplemented in Python, so neither Boxsim.exe nor a CPU emulator is needed to
decode a container; the emulator survives as a development-only oracle that the
codec is differentially tested against.

Verified:
- 134 containers decode byte-identically to Boxsim's own decoder, 270 pre-2.0
  files rejected by both implementations (tools/difftest.py --self-test)
- Default.BPJ: 22 components, 14 nets, no dangling terminal
- 22 of 22 parts resolve to library symbols; one series inferred from a
  sibling, two DC resistance deviations reported
- ERC clean and the netlist KiCad extracts from the generated sheet is an
  identical partition to the one derived from the Boxsim grid
- container build byte-identical to the host build
- 32 tests passing, ruff clean

Decode time went 69 s -> 14.5 s (binding emulator hooks to trap addresses)
-> 1.6 s (pure Python).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-29 11:10:43 +02:00

36 lines
979 B
Python

"""The MyIni2CK container: magic, XOR scrambling, A2F compression.
Boxsim uses one container for project files (`.bpj`) and driver files (`.bch`).
"""
from __future__ import annotations
from . import a2f
from .krypt import unscramble
MAGIC = b"MyIni2CK"
class ContainerError(ValueError):
pass
def is_container(raw: bytes) -> bool:
return raw[: len(MAGIC)] == MAGIC
def decode(raw: bytes) -> bytes:
"""Decode a Boxsim container to its raw INI payload."""
if not is_container(raw):
raise ContainerError(
f"not a MyIni2CK file (magic {raw[:8]!r}, expected {MAGIC!r})"
)
# The compressed layer reads its own 8 byte header back, so hand it the
# magic followed by the descrambled body exactly as Boxsim does.
return a2f.expand(MAGIC + unscramble(raw[len(MAGIC) :]))
def decode_text(raw: bytes) -> str:
"""Decode to text. Boxsim writes Windows-1252 with CRLF line ends."""
return decode(raw).decode("cp1252")