Skip to content

Compatibility

Byte 4 of every archive is a u32 format version. It is 1 today.

A reader accepts exactly its own version and rejects anything else with UnsupportedVersion. There is no forward compatibility and no partial reading:

Terminal window
$ zpack list future.zpak
zpack: cannot open archive 'future.zpak': UnsupportedVersion

That is deliberate. An archive format where an old reader silently skips fields it does not understand is a format where an old reader silently extracts the wrong thing. Refusing outright means the failure is loud, immediate, and actionable.

While the format version stays 1:

  • The header stays 12 bytes: ZPAK, u32 version, u32 entry count
  • An entry record stays path_len u16, path, offset u64, size u64, stored_size u64, hash u64, method u8, in that order
  • Integers stay little-endian
  • method values 0 and 1 keep meaning store and raw deflate
  • Hashing stays XxHash64 with seed 0
  • Entries stay sorted by path
  • Paths stay UTF-8, relative, /-separated

An archive written by any v1 zpack is readable by any other v1 zpack, in either direction, on any platform.

Anything that changes how existing bytes are interpreted:

Change Why it bumps
A new method value An old reader would reject it as UnsupportedMethod mid-index, having already read part of a file it cannot use
A different hash function Same field, different meaning
A new field in the entry record Shifts every following field
Big-endian, or a different integer width Same bytes, different values
Unsorted entries Breaks reproducibility, and the contiguous-parent assumption in unpack

Adding a compression method is the most likely reason it will ever happen.

Anything that changes what zpack writes without changing what a v1 reader can read:

  • A different deflate level, or a smarter store-versus-deflate heuristic
  • Packing a different set of files by default
  • New CLI commands, new library functions, new output formats
  • Changes to the manifest shape, which is a reporting format and not part of the archive

An archive is compatible if a v1 reader produces the original bytes from it. How those bytes got there is not part of the contract.

An asset id is XxHash64 of the path under id_seed = 0x5a7061636b496473. Neither the function nor the seed changes within a format version, so:

  • Handles generated by zpack ids stay valid across repacks
  • They stay valid across zpack versions
  • A generated assets.zig only needs regenerating when the set of paths changes, not when the archive is rebuilt

The seed is distinct from hash_seed so a path handle and a content digest can never be confused for one another.

The CLI version - 0.0.2 today - is independent of the format version. It comes from build.zig.zon at compile time, so the binary cannot disagree with the package that produced it, and the release workflow refuses to publish a tag whose name disagrees with that field.

While zpack is pre-1.0, the library API may change between releases. The archive format is versioned separately and does not move with it.

To check what a binary supports:

Terminal window
$ zpack --version
zpack 0.0.2

And what an archive claims:

Terminal window
$ zpack manifest game.zpak | head -2
.{
.format_version = 1,

zpack targets Zig 0.16.0 and uses the std.Io interfaces introduced there. It does not build on 0.15 or earlier. This is a build-time requirement only - archives are not affected by which Zig produced the binary.