Skip to content

MK11 Encryption and VFS

thethiny edited this page Jun 21, 2026 · 1 revision

MK11 Encryption & VFS

DFP Encryption

MK11 uses DFP encryption for certain game files (inventory databases, config, coalesced). DFP-encrypted files are identified by the 32-byte magic suffix:

mcnxyxcmvmcxyxcmskdldkjshagsdhfj

File Structure

[Encrypted Data] [DFP Info Block] [Info Size (u32)] [Magic (32B)]

Reading from the tail:

  1. Last 32 bytes = magic suffix
  2. 4 bytes before magic = DFP info block size
  3. Info block = file_size - info_size to file_size - 36
  4. Encrypted data = bytes 0 to data_size (from decrypted info)

DFP Info Block Decryption

The info block contains the encryption key and data size, but is itself encrypted with a custom stream cipher. The algorithm (reverse engineered from the game binary) uses:

  1. Stage 1: Accumulate a hash from bytes at offset 0x19E (count from u16 at 0x188)
  2. Stage 2: Compute a checksum (r12b) from specific byte positions across the info block using chained add+double+XOR operations
  3. Stage 3: Process 129 triplets from offset 4 with the same chain operation
  4. Stage 4: Six cipher passes decrypt different regions using a CRC-like stream cipher seeded with 0xE3AFEC21:
    • Pass 1: buf[0x19E..0x19E+count] (key schedule region)
    • XOR buf[0x187] with 0x9F
    • Pass 2: buf[0x18A..0x18E] (4 bytes)
    • Pass 3: buf[0x00..0x04] (4 bytes)
    • Pass 4: buf[0x192..0x19A] (8 bytes — contains data_size)
    • Pass 5: buf[0x19A..0x19E] (4 bytes)
    • Pass 6: buf[0x04..0x187] (387 bytes — contains mode and key)
  5. Final XOR: Mode-dependent XOR at buf[5] using the r12b checksum

Each cipher pass uses a rolling state: hash 4 bytes of state → XOR with current byte → store result → rotate state by result & 0x1F → spread byte → add → rotate 1. Counter resets state doubling every 17 iterations (check BEFORE increment).

Data Decryption

After info block decryption, mode (byte at offset 4) and key (byte at offset 5) are extracted. Mode 2 uses a rolling XOR cipher:

xor_val = key
for i in range(data_size):
    decrypted[i] = encrypted[i] ^ xor_val
    xor_val = (encrypted[i] ^ key) - (i & 0xFF)  # all & 0xFF

Verified File Types

File Type DFP Output Next Step
JSON files Plaintext JSON Direct use
Coalesced files AES-encrypted binary Feed to Coalesced AES decryptor
.xxx assets NRS archive (magic 0x9E2A83C1) Feed to game pipeline
.ini files AES-encrypted binary Feed to Coalesced AES decryptor

Implementation

mk_utils/formats/dfp.py — pure Python, no native code. Streaming file support via decrypt_dfp_file() which reads only the tail for header info, then decrypts data in 64KB chunks.

VFS Tree Structure

Package Groups (Stripped in Browser)

MK11 export paths include a "package group" prefix: /Package/, /Audio/, /FX/, /Mesh/, /Texture/, etc. These are bundle type identifiers, not real filesystem paths. The browser strips them.

Linker Path (Stripped in Browser)

Each .xxx file has an internal linker name (e.g., CHAR_SUB_SCRIPTASSETS). In UE3 games (IJ2, MKX), this becomes a directory in the browse tree. For MK11, linker paths are stripped — root Package exports attach directly under the game node.

Merged Tree

When multiple .xxx files are mounted, MK11's tree merges directories with matching names. This matches IOStore behavior — shared assets (FX, Audio, Materials) that appear in multiple packages are deduplicated.

Clash analysis (4 character files): 1984 cross-file clashes, all identical shared assets. Zero conflicting data. Safe to merge.

Detail Pane Metadata

For MK11 exports, the right pane shows:

  • Linker: The source .xxx file's internal linker name
  • Bundle: The package group prefix (first path component)

Background Mounting

Mounting uses QThread workers. Each file mounts in the background, emitting a signal per completion. The browse tree syncs incrementally — new nodes are added without rebuilding the existing tree, preserving scroll position, selection, and expansion state.

Clone this wiki locally