Skip to content

Support modern encrypted/hashed data.idx (ChaCha20)#1

Open
michaelowens wants to merge 3 commits into
masterfrom
feat/modern-encrypted-idx
Open

Support modern encrypted/hashed data.idx (ChaCha20)#1
michaelowens wants to merge 3 commits into
masterfrom
feat/modern-encrypted-idx

Conversation

@michaelowens

@michaelowens michaelowens commented May 22, 2026

Copy link
Copy Markdown
Owner

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.idx and changed its on-disk
entry 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 trose client binary and
verified against a live install.

Changes

  • src/crypto.zig (new): ChaCha20-IETF helpers with the two hardcoded
    client keys.
    • decryptIdx() — whole-data.idx key (.rodata:0x004521c0).
    • decryptVfsEntry() — per-entry content key (.rodata:0x004524c0).
  • src/files/idx.zig: IDX.read now loads the whole index, auto-detects
    encryption (decrypt-in-place when the header isn't plaintext) and the entry
    format:
    • named (older): length-prefixed path + offset/size/block_size +
      deleted/compressed/encrypted bools + version/checksum. Reading the 3 bools
      fixes a latent 3-byte misalignment.
    • 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; IDX gains
      encrypted/hashed_paths.
  • examples/vfs.zig: per-entry decrypt when is_encrypted (plus a
    --decrypt override); hashed entries are named by their hash hex.
  • examples/idx.zig: prints encryption/hash status and per-entry details.
  • build.zig.zon: pin minimum_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).
  • idx example on a live encrypted install: 49716 entries; last entry
    offset + size equals rose.vfs size exactly.
  • idx example on the bundled plaintext fixture: 43270 entries with real
    paths; round-trip write test still passes.

🤖 Generated with Claude Code

michaelowens and others added 3 commits May 22, 2026 02:25
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant