Skip to content

Instantly share code, notes, and snippets.

@extrawurst
Created March 8, 2026 20:05
Show Gist options
  • Select an option

  • Save extrawurst/883edf600d12a3849c6860a8ef6139de to your computer and use it in GitHub Desktop.

Select an option

Save extrawurst/883edf600d12a3849c6860a8ef6139de to your computer and use it in GitHub Desktop.
HDZ_FORMAT

HDZ Archive and Record Layout

hedz.hdz — full archive (26,529,985 bytes, 227 head records)
+=======================================================================+
| INDEX HEADER (5 bytes)                                                |
|   +0x00  u16be  record_count = 227                                   |
|   +0x02  [u8;3] padding (always 00 00 00)                            |
+=======================================================================+
| OFFSET TABLE (record_count x 4 = 908 bytes)                          |
|   u32le[0]   = 913  (first record starts right after the table)      |
|   u32le[1]   = 46395                                                 |
|   ...                                                                |
|   u32le[226] = 26525352                                              |
+=======================================================================+
| RECORD[0]   offset=913      size=45,482B   Bare_Alien (polygon-only) |
| RECORD[1]   offset=46,395   size=103,749B                            |
| RECORD[2]   offset=150,144  size=123,202B                            |
| ...                         smallest=4,633B  largest=177,098B        |
| RECORD[226] offset=26,525,352  size=4,633B                           |
+=======================================================================+

Head record — general layout

All offsets within a record are relative to the record start. The header contains u32le pointers into the record body. For polygon-only heads (like head 0), voxel/bmp pointers all equal the descriptor offset.

HEAD RECORD (variable size)
+-----------------------------------------------------------------------+
| HeadHeader (0xD1 = 209 bytes, packed C struct)                        |
|   +0x00  u16   header_magic                                          |
|   +0x02  u32   data_size_plus_6  (record_size + 12)                  |
|   +0x06  u32   extended_header_offset                                |
|   +0x0A  [u8;4] unused                                               |
|   +0x0E  u32   voxel_direct_pointer_v92  -.                          |
|   +0x12  u32   voxel_table_0_v93          |  pointers into           |
|   +0x16  u32   voxel_table_1_v94          |  record body             |
|   +0x1A  u32   voxel_table_2_v95          |                          |
|   +0x1E  u32   bmp_pointer_1              |                          |
|   +0x22  u32   bmp_pointer_2              |                          |
|   +0x26  u32   bmp_pointer_3              |                          |
|   +0x2A  u32   character_assembly_desc ---'                          |
|   +0x2E  u32   unknown_2e                                            |
|   +0x32  u32   wav_pointer_1                                         |
|   +0x36  u32   wav_pointer_2                                         |
|   +0x3A  u32   wav_pointer_3                                         |
|   +0x3E  u8    wav_count                                             |
|   +0x3F  ...   stats, physics, projectile fields ...                 |
|   +0x4A  u16   head_id                                               |
|   +0x4C  [u8;10] language_name_lengths[0..9]                         |
|   +0x56  ...   more gameplay fields ...                              |
|   +0xD0  u8    last field                                            |
+-----------------------------------------------------------------------+
| Language names  (sum of language_name_lengths[0..9] bytes)            |
|   10 consecutive byte strings, lengths from header +0x4C..+0x55      |
+-----------------------------------------------------------------------+
| Record body: sections at offsets given by header pointers             |
|   Sections appear in pointer order; unused pointers alias another.    |
|   Typical order for a voxel+polygon head:                            |
|     v92_direct -> v93_table -> v94_table -> v95_table                |
|     -> bmp_1 -> bmp_2 -> bmp_3 -> descriptor -> wav_data            |
+-----------------------------------------------------------------------+

Head 0 — polygon-only template head (Bare_Alien, 45,482 bytes)

Head 0 is the prototype body source. All voxel/bmp pointers alias the descriptor offset (no voxel data). The body-part meshes for the entire game are embedded inline at the end of this record.

rec+0x000 +---------------------------------------------+
          | HeadHeader (209 bytes)                       |
rec+0x0D1 +---------------------------------------------+
          | Language names (82 bytes)                    |
          |   "Bare_Alien" x8, "_" x2                   |
rec+0x123 +=============================================+ <-- descriptor
          | DESCRIPTOR PREFIX (20 bytes)                 |
          |   +0x00 u32  stream_word_0                   |
          |   +0x04 [u8;7] category_counts (all 0)       |
          |   +0x0B [u8;3] base_tint_rgb                 |
          |   +0x0E u8   voxel_obj_count = 0             |
          |   +0x0F u8   helper_count = 0                |
          |   +0x10 u8   poly_node_count_a = 14          |
          |   +0x11 u8   poly_node_count_b = 1           |
          |   +0x12 u16  active_blob_id                  |
rec+0x137 +---------------------------------------------+ <-- node stream
          | NODE 0: type 0xFF (compact inline)           |
          |   1 + 599 bytes: lookup/color/vertex/uv/face |
          |   65 vertices, 48 faces (the "head mesh")    |
rec+0x38F +---------------------------------------------+
          | NODES 1-14: bare type bytes (CachedClone)    |
          |   [10,8,13,4,2,7,14,9,11,12,3,5,6,1]        |
          |   14 bytes, one per body-part reference       |
rec+0x39D +---------------------------------------------+
          | Descriptor stream continuation               |
          |   mount/transform tables, LUT-indexed vertex |
          |   data, and other assembly stream data       |
          |   (~37,701 bytes, partially mapped)          |
rec+0x96E2+=============================================+ <-- body mesh section
          | BODY MESH OFFSET TABLE (68 bytes)            |
          |   17 x u32le, one per body-part type 0..16   |
          |   type[0] = garbage (unused)                 |
          |   type[4] = 0x0044 -> first mesh (3 verts)   |
          |   type[10]= 0x0138 (3 verts)                 |
          |   ...                                        |
          |   Offsets relative to table start.            |
          |   Smallest valid offset = 0x44 = 68          |
rec+0x9726+---------------------------------------------+
          | BODY MESHES (types 1-16, 6,788 bytes total)  |
          |   16 submeshes packed back-to-back            |
          |   each: vertex_count(u32)                     |
          |         vertex_data(vc x 32B)                 |
          |         face_count(u32)                       |
          |         face_data(fc x 8B)                    |
          |         material_count(u32)                   |
          |         face_count_check(u32)                 |
          |         material_indices(fc x 4B)             |
          |         material_data(mc x 80B)               |
          |         vertex_count_check(u32)               |
          |         uv_data(vc x 8B)                      |
          |   fills exactly to record end                 |
rec+0xB1AA+=============================================+ <-- record end

Head 8 — mixed voxel+polygon head (Angry_demonstrator, 108,562 bytes)

A typical head: voxel data for the head/props, polygon body from the prototype cache, bmp textures, and wav sounds.

rec+0x00000 +-------------------------------------------+
            | HeadHeader (209 bytes)                     |
rec+0x000D1 +-------------------------------------------+
            | Language names (149 bytes)                 |
rec+0x00166 +-------------------------------------------+ <-- v92
            | V92 DIRECT BLOB (44,270 bytes)             |
            |   legacy voxel format: 16B header +        |
            |   256-entry palette + color stream +       |
            |   12B/segment headers + packed moves       |
rec+0x0AE54 +-------------------------------------------+ <-- v93
            | V93 TABLE (688 bytes)                      |
            |   keyed voxel container (blob49)           |
rec+0x0B104 +-------------------------------------------+ <-- v94
            | V94 TABLE (4,035 bytes)                    |
            |   keyed voxel container (blobs 49,50,51)   |
rec+0x0C0C7 +-------------------------------------------+ <-- v95
            | V95 TABLE (14,737 bytes)                   |
            |   keyed voxel container (blobs 49,50,51)   |
            |   each blob: 0x01F2 magic + 22B header +   |
            |   RGB565 palette + color stream + trailer   |
rec+0x0FA58 +-------------------------------------------+ <-- bmp_1
            | BMP TEXTURES (3 sections, ~5,730 bytes)    |
rec+0x110BA +===========================================+ <-- descriptor
            | DESCRIPTOR PREFIX (20 bytes)               |
            |   voxel_obj_count = 3                      |
            |   poly_node_count_a = 14, _b = 0           |
            +-------------------------------------------+
            | Mounted voxel blob ids: [49, 50, 51]       |
            +-------------------------------------------+
            | 14 polygon node type bytes (CachedClone):  |
            |   [10,8,13,4,2,7,14,9,11,12,3,5,6,1]      |
            |   all resolved from head 0's prototype lib |
            +-------------------------------------------+
            | Descriptor stream: mount/transform tables  |
            |   (~27K bytes, partially mapped)           |
rec+0x17C80 +-------------------------------------------+ <-- wav
            | WAV DATA (~11,154 bytes)                   |
rec+0x1A812 +===========================================+ <-- record end

Body-part submesh binary format

Each body-part mesh within the offset table section:

+0x00  u32    vertex_count (vc)
+0x04  [vc]   vertex_data: vc x 32 bytes each
              +0x00 f32 position_x
              +0x04 f32 position_y
              +0x08 f32 position_z
              +0x0C [20 bytes] normals / other vertex attributes
       u32    face_count (fc)
       [fc]   face_data: fc x 8 bytes each
              +0x00 u16 index_a
              +0x02 u16 index_b
              +0x04 u16 index_c
              +0x06 u16 padding/flags
       u32    material_count (mc)
       u32    face_count_check (must == fc)
       [fc]   material_indices: fc x 4 bytes (per-face material assignment)
       [mc]   material_data: mc x 80 bytes (0x50 per material)
       u32    vertex_count_check (must == vc)
       [vc]   uv_data: vc x 8 bytes each
              +0x00 f32 u
              +0x04 f32 v

Polygon node resolution flow

                     +-----------+
                     | hedz.hdz  |
                     +-----+-----+
                           |
              parse_record_offsets()
                           |
              +------------+------------+
              |                         |
     head 0 record               head N record
     (template body)              (any other head)
              |                         |
   find_body_mesh_section()    parse descriptor
              |                         |
   68-byte offset table        read type bytes
   + 16 submeshes (types 1-16)   [10,8,13,...,1]
              |                         |
              v                         v
    +--------------------+     for each type byte:
    | prototype library  |       cache hit?
    | HashMap<u8, Mesh>  |<------+ yes -> clone mesh
    +--------------------+       | no  -> missing_clone_types
              |
              v
    viewer renders all nodes:
      CompactInline  = unique per-head attachment
      CachedClone    = shared body part from library
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment