Monorepo for PackTools - tooling for Minecraft Modpacks!
README.md

Packopt #

Packopt turns a resource pack or datapack folder into a small, deterministic zip. Packwand links it in and runs it on every folder marked with packopt.json when it exports or builds.

It is a clean-room MIT replacement for Packeater, the PackSquash fork Packwand used to call as an AGPL sidecar.

Marking a folder #

Put a packopt.json at the top of the folder. {} is enough. packeater.json still works, as a deprecated alias with the same keys; when a folder has both, packopt.json wins.

Key Default Meaning
enabled true false leaves the folder's files loose.
maximumWidthAndHeight 8192 A larger PNG fails the build (1–65535).
include [] Globs for files of unknown type that are expected; they ship without the "unknown type" warning. Nothing is ever excluded.
compression.effort default in CI, fast locally fast, default or max; see "Effort" below.
compression.zipIterations per effort Zopfli iterations for zip entries (0 = libdeflate only); overrides the effort level's 0 / 5 / 15.
compression.imageIterations per effort Zopfli iterations for PNG data (0 removes Zopfli); overrides the effort level's Zopfli trial.
compression.nbtIterations the zip setting Zopfli iterations for NBT files' own gzip streams (0 = libdeflate only).
compression.recompressCompressedFiles false Also try deflating PNG, Ogg, NBT and zip entries.
compression.deduplicateFiles false Identical files share one copy in the zip.
lossy.png false Give PNGs a lossy trial.
lossy.pngPalette auto auto (16 then 256 colors), four_bit, eight_bit, or lossless (no palette trial).
lossy.pngDithering 0 Floyd–Steinberg strength, 0–1.
lossy.downsizeSingleColorImages false A one-color texture becomes 1×1, unless it has a .png.mcmeta.
lossy.minSaving 0.15 A lossy result must be at least this much smaller than the lossless one.
lossy.minSsim 0.98 Lowest SSIM (premultiplied luma and alpha) a lossy result may score.
lossy.minPsnr 40 Lowest PSNR in dB (premultiplied RGBA); catches color error SSIM forgives on busy textures.
lossy.protect [] More globs that never get a lossy trial.
lossy.audio false Give Ogg Vorbis files a re-encode trial.
lossy.audioQuality 3 Vorbis quality, -2 to 10 (oggenc -q; 3 is about 112 kbit/s stereo).
lossy.audioChannels null null or 2 keep channels; 1 downmixes every stereo file; "mono-positional" downmixes exactly the stereo files positional sounds play.
lossy.audioSampleRate null Downsample sounds faster than this (8000 to 192000 Hz); never upsampled.
overrides [] [{"match": "<glob>", "lossy": {…}}] or [{"category": "music", "lossy": {…}}]: the first match refines lossy for that path or sound category.

Globs are relative to the marked folder; * and ? stop at /, ** crosses it.

stripLocalHeaderNames is accepted, but it only warns for now.

Example: lossy only for GUI textures:

{
  "overrides": [{ "match": "assets/*/textures/gui/**", "lossy": { "png": true } }]
}

.packoptignore #

Uses gitignore syntax.

Effort #

compression.effort picks a fixed set of lossless trials per PNG, and the smallest verified result ships:

Level PNG trials Zip entries Measured on a real 1,616-texture pack
fast oxipng preset 2 libdeflate 4.4× less CPU than default, 2.4% more bytes
default oxipng preset 5 + Zopfli ×5 the reference
max preset 5, preset 6, Zopfli ×15 + Zopfli ×15 0.46% fewer bytes for ~5× the CPU

A marker that names no level gets default in CI (when CI is set, for release builds) and fast locally, where it is about 5 times quicker for a little over 1% more bytes. A run can force a level over every marker: --effort on the command line, PACKWAND_PACKOPT_EFFORT in Packwand. max is there for packs that want every last byte; on a real pack it saved 0.1% over default for nearly 3 times the time.

The cache #

Packopt utilizes cache files to avoid re-compressing files that have not changed.

Command line #

packopt plan    [ROOT] [--json]
packopt preview [ROOT] [--json]
packopt build   [ROOT] -o DIR [--json]
packopt archive FOLDER -o FILE.zip [--json]
packopt cache-gc | cache-clear [--cache-dir DIR]

--json prints the same schema-1 document Packwand reads.

Building #

nix develop .#packopt
cargo test
moon run packopt:check

Packwand links libpackopt.a through include/packopt.h.

License #

Packopt is licensed under MIT.