MK11 Encryption and VFS - thethiny/NRS-Asset-Manager GitHub Wiki
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:
- Last 32 bytes = magic suffix
- 4 bytes before magic = DFP info block size
- Info block =
file_size - info_sizetofile_size - 36 - 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:
- Stage 1: Accumulate a hash from bytes at offset 0x19E (count from u16 at 0x188)
- Stage 2: Compute a checksum (r12b) from specific byte positions across the info block using chained add+double+XOR operations
- Stage 3: Process 129 triplets from offset 4 with the same chain operation
- 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]with0x9F - 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)
- Pass 1:
- 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
.xxxfile'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.