diff --git a/docs/TASK.md b/docs/TASK.md new file mode 100755 index 0000000..2cbb7fe --- /dev/null +++ b/docs/TASK.md @@ -0,0 +1,47 @@ +# SAND .wbt — DONE (format fully cracked & BSON-verified) + +## Result + +The `.wbt` walker save format is solved end-to-end and confirmed by parsing the decoded +output as valid BSON. All 5 on-disk files decode and parse cleanly; decode→re-encrypt→gzip +reproduces the original file byte-for-byte. + +### Pipeline +- Save: object → Newtonsoft **BSON** → **XOR** → **gzip** +- Load: gunzip → un-XOR → BSON parse + +### Cipher (the part the prior session got wrong) +- Real key (6 bytes): `70 DD 1F 2A 0B 4A` +- Key index resets every `0xA000` (40960-byte) chunk: + `decoded[i] = raw[i] XOR KEY[(i % 0xA000) % 6]` +- Verified by disassembling `XorCryptography.Encrypt` (GameAssembly.dll RVA 0xBE9B40) and + by the fact the output parses as BSON. Key recovered from known BSON plaintext + (doc-length header + literal `"textureSize"` field name), not from texture-zero guessing. + +### Decoded payload = one BSON document +Keys: `textureSize`(512), `textureRawData`(512×512×4 RGBA), `walker`(WalkerBlueprintDto: +Id/UniqueId/Version/Chassis/Compartments[]/Connections[]/3 hashes), `format`, `iconVersion`, +`firstNameIndex`(int32, 0-based), `secondNameIndex`(int32, 0-based), `creationTime`(datetime), +`name`(null), `isBackup`(bool). + +## What was wrong before (corrected in memory) +- 12-byte key `4A72D8122A094F7DDD1D2F06` + align 0x29 — WRONG (keystream XOR texture pixel). +- `name_index.json` `0x60..0x7F`→word table — INVALID (read from mis-decrypted noise). +- "envelope header / nonce at EOF-26" — non-existent; it's all just BSON. + +## Only remaining unknown +Map `firstNameIndex` / `secondNameIndex` integers → actual words by dumping the Unity +Localization StringTables `WalkerFirstName` / `WalkerSecondName` (AssetRipper/UABE on the +StreamingAssets `aa/` bundle). The byte format itself needs nothing further. + +## Quick reference +```python +import gzip +from bson import decode +KEY = bytes.fromhex('70dd1f2a0b4a'); CHUNK = 0xA000 +raw = gzip.decompress(open('.wbt','rb').read()) +dec = bytes(raw[i] ^ KEY[(i % CHUNK) % 6] for i in range(len(raw))) +doc = decode(dec) # valid BSON +``` +Tools: `~/sand_tools/sand.py` (updated key), venv at `~/sand_tools/venv` (pymongo). +GameAssembly.dll: `/mnt/d/SteamLibrary/steamapps/common/Sand Playtest/GameAssembly.dll`.