IoStore containers (.utoc and .ucas) and pak files
Unreal Engine 5 packs cooked content into IoStore containers: a .ucas file holds the data in compressed blocks and a
.utoc file is its table of contents (which chunks exist, where, how they are compressed). The older .pak format
still ships beside them. MECCHA CHAMELEON (Steam app 4704690, Unreal Engine 5.6.1, executable PenguinHotel, project Chameleon)
has two IoStore containers and one pak in Chameleon/Content/Paks/. Sourced · Unreal Engine source
Phantom Blade Zero’s containers, which are encrypted, differ as described in its section.
| File | Size (bytes) | Holds | Confidence |
|---|---|---|---|
Chameleon-Windows.utoc / .ucas |
2,526,778 / 3,105,798,464 | the game’s cooked content | Proven · MECCHA |
global.utoc / .ucas |
770 / 3,135,872 | the engine’s script objects (global data) | Proven · MECCHA |
Chameleon-Windows.pak |
88,565,611 | non-IoStore files | Observed · MECCHA |
The .utoc header
Section titled “The .utoc header”The header is 144 bytes, little-endian. Proven · MECCHA
Little-endian. Values are those of Chameleon-Windows.utoc; global.utoc differs where noted.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 16 | char[16] = -==--==--==--==- | magic | Proven | |
| 0x10 | 1 | u8 = 8 | version | Proven | EIoStoreTocVersion 8 (ReplaceIoChunkHashWithIoHash) in both containers |
| 0x11 | 1 | u8 | reserved | Proven | zero |
| 0x12 | 2 | u16 | reserved | Proven | zero |
| 0x14 | 4 | u32 = 144 | header size | Proven | |
| 0x18 | 4 | u32 | entry count | Proven | chunks: 12,407 (global.utoc: 1) |
| 0x1C | 4 | u32 | compressed block count | Proven | 124,297 (global: 48) |
| 0x20 | 4 | u32 = 12 | compressed block entry size | Proven | |
| 0x24 | 4 | u32 | compression method count | Proven | 1 (global: 0) |
| 0x28 | 4 | u32 = 32 | compression method name length | Proven | |
| 0x2C | 4 | u32 = 65536 | compression block size | Proven | |
| 0x30 | 4 | u32 | directory index size | Proven | 439,500 (global: 0, no directory index) |
| 0x34 | 4 | u32 = 1 | partition count | Proven | |
| 0x38 | 8 | u64 | container id | Proven | 0x97ACDB3EDF1CD386 (global: 0xFFFFFFFFFFFFFFFF) |
| 0x40 | 16 | u8[16] | encryption key guid | Proven | all zero in both containers |
| 0x50 | 1 | u8 | container flags | Proven | 0x09 (global: 0x00), see below |
| 0x51 | 1 | u8 | reserved | Proven | zero |
| 0x52 | 2 | u16 | reserved | Proven | zero |
| 0x54 | 4 | u32 | perfect hash seed count | Proven | 6,204 (global: 1) |
| 0x58 | 8 | u64 = 0xFFFFFFFFFFFFFFFF | partition size | Proven | no size limit per partition |
| 0x60 | 4 | u32 | chunks without perfect hash count | Proven | 0 |
| 0x64 | 4 | u32 | reserved | Proven | zero |
| 0x68 | 40 | u64[5] | reserved | Proven | all zero |
The container flags are a bit set: 1 compressed, 2 encrypted, 4 signed, 8 indexed (has a directory index). Sourced · Unreal Engine source
The game’s container has 0x09 (compressed and indexed): it is neither encrypted nor signed. Proven · MECCHA
global.utoc has none. Proven · MECCHA
The arrays after the header
Section titled “The arrays after the header”In this order, with no gaps; their sizes add up to the file’s length exactly, so the layout holds for both containers. Proven · MECCHA
| Array | Element size | Count | Content | Confidence |
|---|---|---|---|---|
| chunk ids | 12 | entry count | FIoChunkId: an 8-byte id, a 2-byte index, a padding byte, and a chunk type byte at offset 11 |
Sourced · Unreal Engine source |
| offsets and lengths | 10 | entry count | each chunk’s offset and length in the uncompressed address space: two 5-byte big-endian integers | Proven · MECCHA |
| perfect hash seeds | 4 | seed count | seeds of the minimal perfect hash that maps chunk ids to entries | Sourced · Unreal Engine source |
| chunks without perfect hash | 4 | count (0) | overflow entries | Sourced · Unreal Engine source |
| compressed blocks | 12 | block count | a 5-byte offset into the .ucas, a 3-byte compressed size, a 3-byte uncompressed size, a 1-byte compression method index (little-endian) |
Proven · MECCHA |
| compression method names | 32 | method count | NUL-padded names: Oodle |
Proven · MECCHA |
| directory index | directory index size | one | the file tree: a mount point string, directory and file entries | Proven · MECCHA |
| chunk meta | 24 | entry count | per chunk a 20-byte hash (FIoHash), flags and padding |
Sourced · Unreal Engine source |
The directory index’s mount point is ../../../. Proven · MECCHA The 297,768 bytes after the directory index
are exactly 12,407 chunk meta entries of 24 bytes. Proven · MECCHA
Chunks and blocks
Section titled “Chunks and blocks”The type byte at offset 11 of the chunk ids takes only the values below in the two containers. Proven · MECCHA
Chunk types seen (names from the engine’s EIoChunkType5): Sourced · Unreal Engine source
| Type | Name | Chameleon-Windows |
global |
Confidence |
|---|---|---|---|---|
| 1 | ExportBundleData | 6,549 | Proven · MECCHA | |
| 2 | BulkData | 3,099 | Proven · MECCHA | |
| 3 | OptionalBulkData | 1 | Proven · MECCHA | |
| 5 | ScriptObjects | 1 | Proven · MECCHA | |
| 6 | ContainerHeader | 1 | Proven · MECCHA | |
| 8 | ShaderCodeLibrary | 4 | Proven · MECCHA | |
| 9 | ShaderCode | 2,753 | Proven · MECCHA |
The 6,549 export bundle chunks equal the 6,549 .uexp and the 3,099 bulk chunks the 3,099 .ubulk entries of the build’s
file manifest. Observed · MECCHA The chunks’ lengths add up to 7,533,837,449 bytes uncompressed (the largest
201,327,113); global’s one chunk is 3,135,860 bytes. Proven · MECCHA
The uncompressed address space is cut into 65,536-byte blocks. The game’s container has 124,297 blocks, 117,169 compressed with
Oodle (method index 1) and 7,128 stored (index 0); global’s 48 blocks are all stored. Every block starts at a multiple of 16 in
the .ucas. Proven · MECCHA 12,396 blocks have an uncompressed size other than 65,536. Proven · MECCHA
The pak file
Section titled “The pak file”Chameleon-Windows.pak is a version 11 pak: the footer’s magic 0x5A6F12E1 is 204 bytes from the end of the file, the index is
at offset 88,226,758 and is 74,330 bytes. Observed · MECCHA The shipped game lists 20,835 files in a build
manifest (Manifest_UFSFiles_Win64.txt: 6,549 uexp, 6,527 uasset, 3,438 res, 3,099 ubulk, 415 png, 313 svg, 175
uplugin, 105 ini, 90 locres, 25 ufont) and 24 more outside the file system (Manifest_NonUFSFiles_Win64.txt: the
.uproject, config files, plugin descriptors and the engine’s native libraries). Observed · MECCHA
The pak is 88 MB; the cooked assets are in the IoStore container. Which of the manifest’s files (the .res and .png files,
the config) the pak carries is not established.
Phantom Blade Zero
Section titled “Phantom Blade Zero”Phantom Blade Zero keeps its content in ProjectPBZ/Content/Paks/: the IoStore container ProjectPBZ-Windows, the
global container global, the pak ProjectPBZ-Windows.pak and nine movie paks pakchunkMovies*-Windows.pak.
Observed · PBZ
Its game container’s TOC has the same version and header as above, with these values. The header and the arrays after it add up to the 47,488,040 bytes of the file exactly. Proven · PBZ
| Header field | ProjectPBZ-Windows.utoc |
Confidence |
|---|---|---|
| version | 8 | Proven · PBZ |
| container flags | 0x0b: compressed, encrypted and indexed, not signed |
Proven · PBZ |
| entry count (chunks) | 328,220 | Proven · PBZ |
| compressed block count | 1,360,326 | Proven · PBZ |
| compression block size | 262,144 | Proven · PBZ |
| compression method | Oodle |
Proven · PBZ |
| directory index size | 15,409,392 | Proven · PBZ |
| partition count | 66 | Proven · PBZ |
| partition size | 2 GiB | Proven · PBZ |
| encryption key guid | all zero | Proven · PBZ |
The 66 partitions are the data files ProjectPBZ-Windows.ucas and ProjectPBZ-Windows_s1.ucas to _s65.ucas.
Observed · PBZ The zero key GUID selects the game’s one default key. Sourced · Unreal Engine source
global.utoc is not encrypted. Observed · PBZ
The pak files are version 12, with version 11’s footer layout (221 bytes), an encrypted index, the zero key GUID and
Oodle compression. Observed · PBZ Version 12 adds a UTF-8 path directory
(Utf8PakDirectory); CUE4Parse reads it as an Unreal Engine 5.7 pak. Sourced · CUE4Parse Each movie pak comes with
an empty IoStore pair: no files, mount point /. Observed · PBZ
Without the key, nothing past the headers and footers (the directory index, the packages, the pak indexes) is described here.