Reverse engineer Boxsim MyIni2CK container, add bpj2ini decoder
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>
This commit is contained in:
commit
adb318f57c
5 files changed
+957
No files matched your search
@@ -0,0 +1,63 @@
|
||||
# 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.
|
||||
Reference in new issue
Block a user