TASK.md — the solved .wbt format (BSON+XOR+gzip), recovered key, and the one remaining open item (name-index -> word mapping).
2.2 KiB
Executable File
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.json0x60..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
import gzip
from bson import decode
KEY = bytes.fromhex('70dd1f2a0b4a'); CHUNK = 0xA000
raw = gzip.decompress(open('<file>.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.