Skip to content

MK1 Format

thethiny edited this page Jun 21, 2026 · 1 revision

MK1 (Mortal Kombat 1) File 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.

Pipeline

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.

UAsset Header — 64 bytes (0x40)

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

Name Table

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)

Export Table Entry — 72 bytes (0x48)

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)

ImportObjectIndex Encoding

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

Import Table Entry — 8 bytes

Single u64, decoded as ImportObjectIndex. Count derived from (ExportsLocation - ImportTableLocation) / 8.

Export Data

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.

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.

Special ObjectProperty Handlers

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

IoStore Container Format (UTOC + UCAS)

UTOC Header — 144 bytes

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

UTOC Section Layout (after header)

Sections are sequential, order is critical:

  1. ChunkIdsEntryCount × 12 bytes
  2. OffsetAndLengthsEntryCount × 10 bytes (5B offset + 5B length, both big-endian)
  3. PerfectHashSeedsHashSeedsCount × 4 bytes (skipped if 0 or 0xFFFFFFFF)
  4. ChunkIndicesWithoutPerfectHashNoHashCount × 4 bytes
  5. CompressedBlocksCompressedBlockCount × 12 bytes
  6. CompressionMethodsCompressionMethodCount × CompressionMethodNameLength bytes (null-terminated ASCII, e.g. "Oodle")
  7. Signatures (if Signed flag) — 4B hash_size + hash_size×2 + CompressedBlockCount×20
  8. DirectoryIndexDirectoryIndexSize bytes (AES-ECB encrypted if Encrypted flag)

Compressed Block Entry — 12 bytes

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)

Directory Index (decrypted)

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.

UCAS Data Reading

  1. Look up entry index from directory index user_data
  2. Get offset + length from OffsetAndLengths (5B big-endian each)
  3. Compute first/last compression blocks: offset / CompressionBlockSize
  4. For each block: seek in UCAS → read aligned to 16B → AES-ECB decrypt → Oodle decompress (if method > 0)
  5. Trim to exact offset within first block + length

Encryption

  • 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

Cross-Game Comparison

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

Clone this wiki locally