-
Notifications
You must be signed in to change notification settings - Fork 0
MK1 Format
MK1 uses UE4.27 with IoStore (Zen storage). Unlike UE3 NRS games which use .xxx compressed archives, MK1 stores assets in IoStore containers (.utoc + .ucas) or standalone .uasset files.
UTOC (table of contents) + UCAS (content archive)
→ AES-256-ECB decrypt
→ Oodle v9 decompress
→ .uasset (midway equivalent)
→ Exports (DataTable, textures, etc.)
The .uasset IS the midway — it contains name/import/export tables and serialized export data, equivalent to a decompressed UPK in UE3 games.
No magic number. The header is a fixed struct at the start of every .uasset.
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0x00 | 8 | FilePathFName | u64, name hash |
| 0x08 | 8 | EngineFilesCount | u64 |
| 0x10 | 4 | UFlags | u32, serialization flags |
| 0x14 | 4 | DataLocationInUCas | u32, offset into UCAS (for container-hosted assets) |
| 0x18 | 4 | NameTableOffset | u32, byte offset to name table |
| 0x1C | 4 | NameTableSize | u32, name table size in bytes |
| 0x20 | 4 | UnkData1Offset | u32, auxiliary data offset |
| 0x24 | 4 | UnkData1Size | u32, auxiliary data size |
| 0x28 | 4 | ImportTableLocation | u32, import table offset |
| 0x2C | 4 | ExportsLocation | u32, export table offset |
| 0x30 | 4 | UnkTable2Location | u32, unknown metadata table offset |
| 0x34 | 4 | UnkTableOffset | u32, unknown table offset |
| 0x38 | 8 | UnkTableSize | u64, unknown table total size |
Located at NameTableOffset, variable length (total = NameTableSize bytes).
Each entry:
- u16 length (byte-swapped: stored LE, read as BE)
- UTF-8 string of that length (no null terminator)
9 × u64 fields. Located at ExportsLocation, count derived from (UnkTable2Location - ExportsLocation) / 72.
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0x00 | 8 | ObjectLocation | u64, data offset (in UCAS for container assets) |
| 0x08 | 8 | ObjectSize | u64, serialized data size |
| 0x10 | 8 | ObjectName | u64, index into name table |
| 0x18 | 8 | OuterIndex | u64, ImportObjectIndex encoded |
| 0x20 | 8 | ClassIndex | u64, ImportObjectIndex encoded |
| 0x28 | 8 | SuperIndex | u64, ImportObjectIndex encoded |
| 0x30 | 8 | TemplateIndex | u64, ImportObjectIndex encoded |
| 0x38 | 8 | GlobalImportIndex | u64, ImportObjectIndex encoded |
| 0x40 | 8 | ObjectFlags | u64, low byte = export type (0x0B = DataTable) |
The u64 index fields pack type and value:
- Bits [63:62] = Type: 0=Export, 1=ScriptImport, 2=PackageImport, 3=Invalid
- Bits [61:0] = Value
- For PackageImport: upper 32 bits = package index, lower 32 bits = export hash index
Single u64, decoded as ImportObjectIndex. Count derived from (ExportsLocation - ImportTableLocation) / 8.
Export data is stored sequentially after the last table (after UnkTable). For standalone .uasset files, exports are read from the current file position — NOT from ObjectLocation (which stores the UCAS container offset).
Exports with ObjectFlags & 0xFF == 0x0B are DataTable exports containing UE4 tagged properties.
Export data uses UE4's self-describing tagged property format:
FName PropertyName → FName PropertyType → type-specific data
Supported types: BoolProperty, ByteProperty, IntProperty (u8/16/32/64, signed/unsigned), FloatProperty, StrProperty, NameProperty, TextProperty, EnumProperty, StructProperty, ArrayProperty, MapProperty, ObjectProperty, SoftObjectProperty, FieldPathProperty.
Unlike UE3, container types (Array, Map) serialize their element types inline — no whitelists needed.
| element_name | Behavior |
|---|---|
| RowStruct | Read super FName + class ref + children count, then per-child struct elements |
| mLootStruct | Read super FName + one property |
| ScriptStruct | Read source FName + reference + 0-3 properties |
| mPreReqStruct | Read inner struct element |
Magic: -==--==--==--==- (16 bytes, ASCII literal)
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0x00 | 16 | Magic | -==--==--==--==- |
| 0x10 | 4 | Version | u32, = 3 for UE4.27 |
| 0x14 | 4 | HeaderSize | u32, = 144 |
| 0x18 | 4 | EntryCount | u32, number of chunks |
| 0x1C | 4 | CompressedBlockCount | u32 |
| 0x20 | 4 | CompressedBlockEntrySize | u32, = 12 |
| 0x24 | 4 | CompressionMethodCount | u32 |
| 0x28 | 4 | CompressionMethodNameLength | u32, = 32 |
| 0x2C | 4 | CompressionBlockSize | u32, = 65536 |
| 0x30 | 4 | DirectoryIndexSize | u32 |
| 0x34 | 4 | PartitionCount | u32 |
| 0x38 | 8 | ContainerId | u64 |
| 0x40 | 16 | EncryptionKeyGuid | GUID (all zeros for MK1) |
| 0x50 | 4 | ContainerFlags | u32, bitmask |
| 0x54 | 4 | HashSeedsCount | u32 (0 or 0xFFFFFFFF = none) |
| 0x58 | 8 | PartitionSize | u64 (0xFFFFFFFFFFFFFFFF = single) |
| 0x60 | 4 | NoHashCount | u32 |
Container Flags:
| Bit | Flag |
|---|---|
| 0 | Compressed |
| 1 | Encrypted |
| 2 | Signed |
| 3 | Indexed |
Sections are sequential, order is critical:
-
ChunkIds —
EntryCount × 12bytes -
OffsetAndLengths —
EntryCount × 10bytes (5B offset + 5B length, both big-endian) -
PerfectHashSeeds —
HashSeedsCount × 4bytes (skipped if 0 or 0xFFFFFFFF) -
ChunkIndicesWithoutPerfectHash —
NoHashCount × 4bytes -
CompressedBlocks —
CompressedBlockCount × 12bytes -
CompressionMethods —
CompressionMethodCount × CompressionMethodNameLengthbytes (null-terminated ASCII, e.g. "Oodle") -
Signatures (if Signed flag) —
4B hash_size + hash_size×2 + CompressedBlockCount×20 -
DirectoryIndex —
DirectoryIndexSizebytes (AES-ECB encrypted if Encrypted flag)
Packed as u64 + u32:
-
u64[0:40]→ block offset in UCAS (40 bits) -
u64[40:64]→ compressed size (24 bits) -
u32[0:24]→ uncompressed size (24 bits) -
u32[24:32]→ compression method index (8 bits, 0 = uncompressed)
AES-256-ECB decrypted with game key. Structure:
| Field | Type | Notes |
|---|---|---|
| mount_len | i32 | Mount point string length |
| mount | string | UTF-8, typically ../../../
|
| dir_count | u32 | Number of directory entries |
| dirs[] | struct×dir_count | 16B each: name_idx(u32) + first_child(u32) + next_sibling(u32) + first_file(u32) |
| file_count | u32 | Number of file entries |
| files[] | struct×file_count | 12B each: name_idx(u32) + next_file(u32) + user_data(u32) |
| str_count | u32 | Number of strings |
| strings[] | variable | FString: i32 len + data (UTF-8 if len>0, UTF-16LE if len<0) |
Sentinel value 0xFFFFFFFF = end of linked list. user_data = entry index into ChunkIds/OffsetAndLengths.
- Look up entry index from directory index
user_data - Get offset + length from OffsetAndLengths (5B big-endian each)
- Compute first/last compression blocks:
offset / CompressionBlockSize - For each block: seek in UCAS → read aligned to 16B → AES-ECB decrypt → Oodle decompress (if method > 0)
- Trim to exact offset within first block + length
- Algorithm: AES-256-ECB, no padding
-
Key:
0x6FAABA4F4EF8A6AC188A517ACEF38F1422484E3B1F3F4CF3DACB27A6CBCCD076 - Applied to: UCAS data blocks (aligned to 16B) and UTOC directory index
-
Compression: Oodle v9 (Kraken/Leviathan/Mermaid), requires
oo2core_9_win64.dll
| Feature | MKX | IJ2 | MK11 | MK1 |
|---|---|---|---|---|
| Engine | UE3 | UE3 | UE3 | UE4.27 |
| Container | .xxx (ZLIB/LZO) | .xxx (Oodle v4) | .xxx (Oodle v5) | IoStore (UTOC/UCAS) |
| Midway | Decompressed UPK | Decompressed UPK | Decompressed UPK | .uasset |
| Magic | 0x9E2A83C1 | 0x9E2A83C1 | 0x9E2A83C1 | None (no magic) |
| Encryption | None | None | None (DFP separate) | AES-256-ECB |
| Properties | UE3 tagged (whitelist) | UE3 tagged (whitelist) | UE3 tagged (whitelist) | UE4 tagged (self-describing) |
| Localization | Coalesced (AES) | Coalesced (AES) | Coalesced (AES) | .locres (JSON) |
| Bulk data | TFC | TFC | PSF | UCAS |
NRS Asset Manager
Architecture
Game Formats
Game Documentation
- Injustice 2 (DCF2)
- Mortal Kombat X (MK10)
- Mortal Kombat 11 (MK11)
- Mortal Kombat 1 (MK12)
Export Handlers
Reference