Format: 8-byte magic, XOR layer (table-shuffled 3-LCG PRNG, seeds 123/456), custom LZ+Huffman codec (A2F). Decompression runs the original ExpandA2F code from Boxsim.exe under Unicorn. Decodes .bpj and .bch files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boxsim .bpj / .bch file format (reverse engineered)
Boxsim (Visaton, Delphi, codec and container "MyIniFile" by UG 2004) stores projects (.bpj) and driver files (.bch) in a three-layer container. Reverse engineered 2026-09-28 from Boxsim 2.1 DE (Boxsim.exe, no prior public documentation existed).
Container layout
| Layer | Content |
|---|---|
| 1 | 8-byte plaintext magic MyIni2CK |
| 2 | Everything after the magic is XOR-encrypted with a position-based PRNG keystream, seeds (123, 456), identical for every file |
| 3 | The decrypted body is a custom LZ + canonical-Huffman stream ("A2F" codec, CompressA2F/ExpandA2F at VA 0xa1013c/0xa11880) |
| 4 | The decompressed payload is plain INI-style text: key=<TAB>value lines, CRLF, #Section headers, German decimal commas, arrays as ;-separated values |
Layer 2: the Krypt PRNG
Table-shuffled combination of three LCGs (found at VA 0xa12be4 init, 0xa12c9c next byte). All arithmetic mod 2^32:
init(s1=123, s2=456):
s3 = (s1 mod 0xFFFE) * (s2 mod 0x7F23)
repeat 256 times: step(); table[i] = s1 + s2
step():
s1 = s1 * 0x10B95 + 1
s2 = s2 * 0x10BA9 + 1 (reset to 0 if >= 0xFFFFFFFF)
s3 = s3 * 0x1155D + 1
next_byte():
step()
idx = s3 and 0xFF
out = table[idx]; table[idx] = s1 + s2
return out mod 256
plaintext[i] = filebyte[8+i] XOR next_byte().
Layer 3: the A2F codec
Sliding-window LZ (1 MB window, flush keeps the last 0x3FFF8 bytes) with a 356-symbol canonical Huffman alphabet: 0..255 literals, 0x100 end-of-stream, 0x101 rebuild code-length tables (adaptive), 0x102 special, rest match lengths/distances. Decoding uses a 65536-entry direct lookup table; initial state gives every symbol a 9-bit code. Not re-implemented: bpj2ini.py runs the original ExpandA2F machine code under Unicorn instead, which is exact by construction.
Crossover encoding (#Weiche<n> section)
The crossover editor grid serializes as _Part<N>_* keys. Only non-empty slots are written:
| Key | Meaning |
|---|---|
_Part<N>_typ |
0 empty, 1 wire, 2 resistor, 3 capacitor, 4 inductor, 5 driver, 6 amplifier input |
_Part<N>_value |
Component value in SI base units (Ohm, Farad, Henry) |
_Part<N>_Rvalue |
Series resistance of an inductor in Ohm |
_Part<N>_bem |
Part label, e.g. MKT-A, SP 0,6mm, Keramik 10W |
_Part<N>_chassisnr |
Driver index (0-based) for typ 5 |
_Part<N>_verpolt |
Polarity flag (-1 / 1) for typ 5 and 6 |
_Part368..375_nod1/nod2 |
Extra node-to-node connections |
Other sections: #General (nAmps, nChassis), #AktFilter<n> (active filters), #Chassis<n> (TSP set plus measured frequency/impedance/phase arrays), #Einbau<n> (enclosure: Typ 1 closed with Vb in m³, etc.), #Schallwand<n> (baffle geometry in m), #GemGeh (shared enclosure).
Tool
.venv/bin/python bpj2ini.py <input.bpj|.bch> [output.ini]
Needs unicorn and pefile (see .venv/) and Boxsim.exe (path via BOXSIM_EXE env, default: the bundled download under boxsim_dl/). Works for .bpj and .bch alike.