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).
This commit is contained in:
47
docs/TASK.md
Executable file
47
docs/TASK.md
Executable file
@@ -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('<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`.
|
||||||
Reference in New Issue
Block a user