Skip to content

manifest

zpack manifest <archive.zpak>

Writes the index as ZON to stdout - everything the archive describes, without any of its contents:

Terminal window
$ zpack manifest game.zpak > manifest.zon
manifest.zon
.{
.format_version = 1,
.archive_bytes = 12828,
.entry_count = 5,
.total_bytes = 13309,
.stored_bytes = 12565,
.entries = .{
.{
.path = "audio/blip.wav",
.id = 0x40a8e93753577eb5,
.size = 2444,
.stored_size = 2370,
.method = .deflate,
.hash = 0xfe514dafacec030e,
},
.{
.path = "data/noise.bin",
.id = 0xbc5d025c01217e9b,
.size = 4096,
.stored_size = 4096,
.method = .store,
.hash = 0x8ca43708f0ada27c,
},
},
}
Field Is
format_version The archive format version. 1 today
archive_bytes Size of the .zpak file itself, index included
entry_count Number of entries
total_bytes Sum of every entry’s original size
stored_bytes Sum of every entry’s stored size
entries[].path Relative, /-separated
entries[].id The asset handle for that path
entries[].size Original file size
entries[].stored_size Bytes occupied in the archive
entries[].method .store or .deflate
entries[].hash XxHash64 of the original bytes

Sizes are exact byte counts, not the rounded units list prints. Entries appear in sorted path order, so two manifests of the same tree diff cleanly.

ZON is Zig’s own literal syntax, so the standard library parses it with no dependency and no hand-written parser:

const Manifest = struct {
format_version: u32,
archive_bytes: u64,
entry_count: u32,
total_bytes: u64,
stored_bytes: u64,
entries: []const struct {
path: []const u8,
id: u64,
size: u64,
stored_size: u64,
method: enum { store, deflate },
hash: u64,
},
};
const manifest = try std.zon.parse.fromSliceAlloc(Manifest, gpa, source, null, .{});

That makes CI assertions cheap: fail a build when an archive crosses a size budget, when an expected asset disappears, or when a texture’s hash changes unexpectedly. See manifests for worked examples.

The manifest is derived from the archive when the command runs, not written alongside it at pack time. There is no second file to forget to regenerate, and no way for the two to disagree.

Only the index is read, so manifest is as cheap as list.

Archive.writeManifest takes any std.Io.Writer, so a build.zig step can produce a manifest without invoking the binary:

var archive = try zpack.Archive.open(gpa, io, .cwd(), "game.zpak");
defer archive.deinit();
try archive.writeManifest(&writer.interface);