63 lines
3.1 KiB
Markdown
63 lines
3.1 KiB
Markdown
# 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:
|
||
|
|
|
||
|
|
```text
|
||
|
|
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
|
||
|
|
|
||
|
|
```bash
|
||
|
|
.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.
|