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.