PGZP minizip archives
A Forza Horizon 6 track keeps its world (every model, placement table and track texture) in four GeoChunk
archives, GeoChunk0.minizip to GeoChunk3.minizip, in the track’s folder under media\Tracks. The format is not a
ZIP: the entries have no names inside the archive, only an index, a position and a compression method. The names and the
resource types come from companion text and binary files beside the archives. Observed
This page describes the Tokyo track Brio, the only track measured, as Steam build 25588841 ships it. Every
statement marked Proven was checked on all four of its GeoChunks: every entry read by a reader that refuses any value
the layout below does not allow, and compared field by field with ForzaTools’ reading of the same files. Proven
What a chunk holds
Section titled “What a chunk holds”| Chunk | Size | Entries | Holds | Confidence |
|---|---|---|---|---|
GeoChunk0 |
39,724,802,048 bytes | 411,013 | every model, every .pgeo placement file and .pb texture (each with at least its lowest quality tier), terrain cluster models |
Observed |
GeoChunk1 |
3,573,829,632 bytes | 3,065 | no .modelbin; its other contents (the models, placements and .pb textures are in chunks 0 to 2) are not described separately |
Unknown |
GeoChunk2 |
49,414,393,856 bytes | 79,840 | higher quality tiers of textures, including the largest terrain cell maps (mainmap q5) and the top.dxt near-field layer files |
Observed |
GeoChunk3 |
4,762,284,032 bytes | 1,476 | only the highest-resolution builtmegatexture records (the .hq* files) |
Observed |
No path appears in two chunks. Observed Only GeoChunk0 holds .modelbin files. Observed
Header
Section titled “Header”Little-endian. The folder index follows at 0x20.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | char[4] = PGZP | magic | Proven | |
| 0x4 | 4 | u32 = 101 | version | Proven | FH5 shipped 100 and 101 per ForzaTools; the FH6 chunks are 101 |
| 0x8 | 4 | u32 = 0x20 | folder index offset | Proven | |
| 0xC | 4 | u32 | entry count | Proven | 411,013 / 3,065 / 79,840 / 1,476 for chunks 0 to 3 |
| 0x10 | 4 | u32 | folder count | Proven | 81,657 / 3,065 / 79,840 / 1,476 |
| 0x14 | 4 | u32 = 512 | entries per sub-chunk | Proven | |
| 0x18 | 4 | u32 | sub-chunk count | Proven | ceil(entry count / 512): 803 / 6 / 156 / 3 |
| 0x1C | 4 | u32 = 0 | reserved | Unknown | 0 in all four chunks |
After the header comes the folder index: one u32 per folder, the index of the folder’s first entry, then one
more u32, the entry count. A folder’s entries run from its value to the next, so the entries of a folder are
consecutive and the values never go down; no folder is empty. A zero u32 pads the list to a multiple of 8 bytes
(chunks 2 and 3). Proven Then come the sub-chunks, and right after the directory (the end of the list below) the
first entry’s data. Proven
Sub-chunks and entries
Section titled “Sub-chunks and entries”Each sub-chunk is a u64 absolute data start followed by the records of its entries: 512, the last sub-chunk the
rest. Every sub-chunk’s first entry has relative offset 0. Proven One more 12-byte record ends the list: its data
offset, relative to the last sub-chunk’s data start, is where the last entry’s data ends, and its other fields are 0.
Proven
Preceded in each sub-chunk by a u64 absolute data start. Little-endian.
| Offset | Size | Type | Field | Confidence | Notes |
|---|---|---|---|---|---|
| 0x0 | 4 | u32 | relative data offset | Proven | absolute offset = sub-chunk data start + this; a multiple of 4 for every entry |
| 0x4 | 4 | u32 | uncompressed size | Proven | |
| 0x8 | 2 | u16 | flags | Proven | version 101: method = flags & 0x0FFF, padding = flags >> 12 (0 to 3 in FH6). Version 100 (per ForzaTools): flags & 0x3FFF and flags >> 14 |
| 0xA | 2 | u16 | folder | Proven | the low 16 bits of the entry's folder number (its folder by the folder index): chunks 0 and 2 have more than 65,536 folders, and the field wraps |
An entry’s stored bytes run from its offset to the next entry’s offset (for the last entry, the end the closing record gives), less its padding: the padding is the 0 to 3 bytes that bring the next entry to a multiple of 4. A stored entry’s stored bytes are exactly its uncompressed size. Proven After the last entry’s data the file is zero bytes up to a multiple of 4,096. Proven
| Method | Compression | Where | Confidence |
|---|---|---|---|
| 0 | stored | GeoChunks | Proven |
| 8 | raw DEFLATE (RFC 1951) | GeoChunk0 only | Proven |
| 31 | raw LZ4 block; the payload starts with an LZ4 token byte (F0 30 = 15 + 48 literals) |
GeoChunks; not in ForzaTools’ FH5 table | Proven |
| 21 | XMemCompress LZX | platform zips per ForzaTechStudio; no entry of the 9,076 zips of the FH6 install uses it | Sourced · ForzaTechStudio |
| 22 | deflate wrapped in TFIT encryption | platform zips (29,250 entries in 1,085 of the install’s 9,076 zips), not GeoChunks | Observed |
| Chunk | Stored | DEFLATE | LZ4 | Confidence |
|---|---|---|---|---|
GeoChunk0 |
115,773 | 14,041 | 281,199 | Proven |
GeoChunk1 |
710 | 0 | 2,355 | Proven |
GeoChunk2 |
31,427 | 0 | 48,413 | Proven |
GeoChunk3 |
1,137 | 0 | 339 | Proven |
Every entry of the four chunks decodes to exactly its uncompressed size with every stored byte used: no DEFLATE
stream or LZ4 block ends early or leaves bytes over. Proven 1,800 entries (200 of each method of each chunk, spread
evenly) decode to the same bytes in ForzaTools (stored and DEFLATE entries) and K4os.Compression.LZ4 (LZ4 entries).
Observed ForzaTools never sets the closing record’s offset, so the size it gives the last entry of GeoChunk0 (a
compressed one) wraps and it cannot read that entry. Proven
Companion files
Section titled “Companion files”Both sit beside the chunks, one pair per chunk. Observed
ChunkMapN.dat is exactly 4 × entry count bytes: one little-endian u32 per entry, the resource type in the top
byte and a 24-bit id below it (type << 24 | id). Proven Over the 495,394 records of the four chunk maps no (type, id)
pair repeats, so a record’s whole u32 names one entry of the track. Proven Placement files name models and
visibility lists by this word (Hand-placed instances).
| Type | Resource | Type | Resource |
|---|---|---|---|
| 0 | .pgeo placement file |
14 | .dds |
| 1 | .phys |
15 | .hkx (Havok) |
| 2 | placeholder | 16 | .zip |
| 3 | .owb |
17 | .mtxmoddxt near-field splat map |
| 4 | .modelbin |
18 | .dxt |
| 5 | .pb texture |
19 | .mtp grass clutter map |
| 7 | .gr2 |
20 | pack |
| 8 | .tz trigger zone |
22 | .gipack |
| 9 | .soundscape |
24 | .hqdxt |
| 10 | .bank |
25 | streaming cell |
| 11 | .lightblock |
26 | .tdft |
| 12 | .pvsz per-cell visibility list |
27 | .fvt |
| 28 | .predeform |
All the type names are Observed from the extensions of the paths the contents file gives for each type. A model is
named in other files by 4 << 24 | id and a texture by PVSTextureIndex << 4 | (quality − 1). Observed
ChunkContentsMiniZipN.txt is ASCII with one line per entry in entry order, each ended by CR LF, in one of two
forms: <PREZIPPED>d:\...\zipcache\pc\<path>|<folder>, the entry’s path on the machine that built the archive, or a
bare generated name and its folder, __04965.pvsz|30579 or __tz00021.tz|14717. Proven GeoChunk0 has 14,041 bare
lines (13,702 .pvsz, 339 .tz); the other chunks have none. Proven The number after the last | is the entry’s
whole folder number, the one the folder index gives (not its 16-bit field). Proven
Other files in the track folder
Section titled “Other files in the track folder”Beside the chunks sit the files below. They are named here and described on the pages that use them. Observed
| File | Holds | Described in |
|---|---|---|
ChunkAssign*.dat, Duplication_*.dat, TransHints_*.dat, Compensation.dat |
streaming assignment data | Unknown |
bin.zip |
shaders, DXIL permutations, 1,676 material files (419 parent materials × summer, spring, autumn, winter), AssetManifest.xml |
Asset manifests, Material files, Shader containers |
AssetManifest.xml |
the loose manifest of textures, models and instances | Asset manifests |
Ribbon_00\ |
brio_00.pvs (hand-placed instances) and the instance template libraries |
Hand-placed instances, Template libraries |
trackroutes\*.nt |
plain XML track locators | Coordinates and cells |
triggerzones\tz_oceanparams\ |
the ocean preset zones | Water |
The shared engine textures live outside the track, in media\_library\TexturesPG\*.zip (.swatchbin), and more track
textures in media\Stripped\gs\swatchbins.zip (.pb: festival banners and flags, some distance-level textures).
Observed
They are present beside the chunks and were not read.
The ids of type 4 (models) and type 12 (.pvsz) are named by the hand-placed instance file; what the ids of the other
types count is not established.