Support modern encrypted/hashed data.idx (ChaCha20)#1
Open
michaelowens wants to merge 3 commits into
Open
Conversation
The modern rose-online client encrypts data.idx (whole file) and optionally individual vfs entries with ChaCha20-IETF using two distinct hardcoded keys, recovered from the trose binary: - idx key @ .rodata:0x004521c0 (chacha20_xor_idx / vfs_idx_decrypt) - entry key @ .rodata:0x004524c0 (chacha20_xor_inplace / vfs_decrypt_buffer) src/crypto.zig exposes decryptIdx() and decryptVfsEntry(); the vfs extraction example gains an optional --decrypt flag and decrypts per-entry when is_encrypted is set. Verified against a live install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
IDX.read now loads the whole index, auto-detects ChaCha20 encryption
(decrypting in place when the header is not plaintext), and auto-detects
the entry format:
- named (older): length-prefixed plaintext path + offset/size/block_size
+ 3 deleted/compressed/encrypted bools + version/checksum
(the bools were previously skipped, misaligning entries)
- hashed (newer): 32-byte path hash + offset/size + u64 mtime + block_size
+ is_encrypted; file-table header is 4 bytes shorter
VFSFileMetadata gains path_hash/mtime and `encrypted`/`hashed_paths` flags
on IDX. The vfs extractor names hashed entries by their hash hex and
decrypts per-entry content when is_encrypted is set. Verified against the
live install (49716 entries, last entry ends exactly at rose.vfs EOF) and
the bundled plaintext fixture; all tests pass on zig 0.11.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The modern hashed index keys entries by BLAKE3(lower(slashify(path))). crypto.vfsPathHash exposes that recipe, and examples/recover.zig uses it to extract a hashed VFS while recovering original (lowercased) file names from a wordlist of known paths (e.g. an older plaintext data.idx); unmatched entries go to _unmapped/<hash>. Verified against the live install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This was a test using ghidra and claude to automate disassembly and investigate the VFS extraction in the official trose client.
Summary
The modern ROSE Online client encrypts
data.idxand changed its on-diskentry layout, so the existing parser fails (
OutOfMemory/EndOfStream)on current installs. This branch adds the decryption and updates the IDX
parser to handle both the legacy and modern formats automatically.
Both ChaCha20 keys were recovered from the
troseclient binary andverified against a live install.
Changes
src/crypto.zig(new): ChaCha20-IETF helpers with the two hardcodedclient keys.
decryptIdx()— whole-data.idxkey (.rodata:0x004521c0).decryptVfsEntry()— per-entry content key (.rodata:0x004524c0).src/files/idx.zig:IDX.readnow loads the whole index, auto-detectsencryption (decrypt-in-place when the header isn't plaintext) and the entry
format:
deleted/compressed/encrypted bools + version/checksum. Reading the 3 bools
fixes a latent 3-byte misalignment.
u64mtime +block_size + is_encrypted (file-table header is 4 bytes shorter).
VFSFileMetadatagainspath_hash/mtime;IDXgainsencrypted/hashed_paths.examples/vfs.zig: per-entry decrypt whenis_encrypted(plus a--decryptoverride); hashed entries are named by their hash hex.examples/idx.zig: prints encryption/hash status and per-entry details.build.zig.zon: pinminimum_zig_version = "0.11.0".Notes
The modern index stores path hashes, not filenames, so extraction yields
hash-named files. Recovering real names needs the path-hash algorithm (future
work). Writing the modern format is intentionally unsupported (hashes can't be
reconstructed) and returns
error.ModernIdxWriteUnsupported.Test plan
zig test src/rosetools.zig— all 12 tests pass on Zig 0.11(incl. a synthetic encrypted+hashed round-trip needing no game data).
idxexample on a live encrypted install: 49716 entries; last entryoffset + sizeequalsrose.vfssize exactly.idxexample on the bundled plaintext fixture: 43270 entries with realpaths; round-trip write test still passes.
🤖 Generated with Claude Code