Skip to content

Reproducible archives

Packing the same directory twice produces byte-identical archives:

Terminal window
$ zpack pack assets/ a.zpak
$ zpack pack assets/ b.zpak
$ cmp a.zpak b.zpak # no output: identical

That holds across machines, across platforms, and across runs. It is not a best-effort property - it falls out of four deliberate choices.

Directory walk order is undefined and varies by filesystem, by inode allocation, and by how the tree was created. pack sorts paths byte-wise before writing the index, so walk order never reaches the output.

Sorting also gives unpack its contiguous-parent optimisation: files sharing a directory are adjacent, so the same parent is never created twice.

Paths are stored /-separated regardless of host separator, so an archive packed on Windows is byte-identical to one packed on Linux from the same tree.

Nothing time-varying is recorded. There are no mtimes, no permissions, no “created by” field, and no archive-level timestamp. The format has nowhere to put one - see the v1 layout.

Deflate at level 6 with a fixed window produces the same bytes for the same input, every time. The store-versus-deflate decision is a pure comparison of two sizes, so it too is a function of the input alone.

Change Effect
A different zpack build with a different deflate implementation Compressed bytes could differ; the contents still round-trip identically
Adding, removing, or renaming a file Changes the index size, which moves every offset
Editing a file Changes its size, hash, and possibly its method
A different .zpackignore Changes the set of entries
Packing a different directory whose contents happen to match Identical, in fact - only contents and paths matter

The first row is the one to be aware of: reproducibility is guaranteed for a given zpack binary. Two different zpack versions could in principle produce different compressed bytes for the same input while both round-trip perfectly. Pin the version if bit-identical output across time matters to you.

Terminal window
zpack pack assets/ a.zpak
zpack pack assets/ b.zpak
cmp a.zpak b.zpak && echo reproducible
Terminal window
zpack pack assets/ game.zpak
sha256sum game.zpak

Commit the expected digest and compare. A change means the assets changed - which is exactly what you want a build to tell you.

More informative than a digest, because it says what changed:

Terminal window
zpack manifest game.zpak > /tmp/manifest.zon
diff manifest.zon /tmp/manifest.zon

A changed hash with an unchanged size means contents changed. A new block means an asset was added. See manifests.

Packing into the directory you are packing

Section titled “Packing into the directory you are packing”

zpack pack . out.zpak is repeatable, which is less obvious than it sounds. Two mechanisms make it work:

  1. A file whose relative path equals the output path is skipped during the scan, so the previous out.zpak is not folded into the new one.
  2. The temporary file pack builds into is created after the scan, so it cannot appear in its own archive.

Without the first, each run would embed the last run’s archive and grow forever. Without the second, the output would contain a partial copy of itself.

pack builds into a temporary file beside the destination and moves it into place only once complete. A run that fails partway leaves any existing archive untouched rather than replacing it with a truncated one.

Combined with reproducibility, that means a failed build never leaves a plausible but wrong archive behind - you either get the correct bytes or the previous ones.

Caching. A build system can skip work when the inputs are unchanged, because unchanged inputs provably produce an unchanged archive. See build integration.

Patching. A binary diff between two releases is proportional to what actually changed, not to how the filesystem happened to enumerate directories that day.

Review. An unintended asset change shows up as a manifest diff rather than as an opaque binary blob nobody can review.

Verification. Anyone can rebuild the archive from the source tree and check that it matches what shipped - provided they use the same zpack version.