Files
SandTools/docs/TASK.md
DownloadPizza 2e886f31f0 docs: .wbt save-format write-up
TASK.md — the solved .wbt format (BSON+XOR+gzip), recovered key, and
the one remaining open item (name-index -> word mapping).
2026-06-11 14:43:57 +02:00

2.2 KiB
Executable File
Raw Blame History

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 BSONXORgzip
  • 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

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.