English | 简体中文
A fork of RePKG by ReZe20.
Modified .pkg extractor and .tex converter for Wallpaper Engine wallpapers.
Original author: NotScuffed (2019-2025)
Fork maintainer: ReZe20 (2025)
Wallpaper engine PKG unpacker/TEX converter, written in C#.
PKG and TEX formats reverse engineered by me.
Feel free to report errors.
- Extract PKG files
- Convert PKG into wallpaper engine project
- Convert a wallpaper project directory back into a PKG
- Convert TEX to image
- Dump PKG/TEX info
- Batch mode: convert PC packages into mobile
.mpkgpackages, and back
Runs on Windows, Linux and macOS on .NET 10 (JIT), and ships NativeAOT single-file
binaries for win-x64, win-arm64, linux-x64, linux-arm64 and osx-arm64 — each one
built, smoke-tested and sampled for on that architecture's own CI runner, so no artifact
here is a cross-compiled guess. Two gaps are set by the toolchain and by policy, not by
oversight: Intel Macs get no AOT binary (NativeAOT cannot cross-compile and CI has no
Intel macOS runner), and there is no 32-bit build — the memory gate caps in-flight work at
4 GB, which a 32-bit address space cannot honour, so a 32-bit run would throw
OutOfMemoryException inside the decoder rather than back off. macOS memory sampling takes
the total and the page size from sysctl(CTL_HW, …) and the page counts from Mach's
host_statistics64; CI checks it on an Apple-silicon runner by asserting the batch
startup gate line reports a non-zero avail=…MB (sysctl+mach), because a failed read
degrades silently into a ~2 s stall per texture rather than an error.
--help(or-h,-?) - lists the commands;extract --help,info --helpandbatch --helplist the options of one command--version- prints the version and the source commit it was built from- extract - extracts specified PKG/TEX file, or files from folder
-o, --output <DIR> Output directory (default: ./output)
-i, --ignoreexts <EXTS> Don't extract files with these extensions (comma-delimited)
-e, --onlyexts <EXTS> Only extract files with these extensions (comma-delimited)
-t, --tex Convert all TEX files into images from the directory given as input
-s, --singledir Put all extracted files in one directory instead of their entry path
-r, --recursive Search all subfolders of the specified directory
-c, --copyproject Copy project.json and the preview image (as declared in project.json)
from beside the PKG into output
-n, --usename Use the title in project.json as the subfolder name instead of the id
--no-tex-convert Don't convert TEX files into images while extracting PKG
-p, --only-tex-images Skip raw .tex output, keep only converted images (the .tex-json sidecar
is still written)
-I, --output-ignoreexts Don't write files with these extensions. Output-level filter: entries are
still parsed and TEX still converted, only the write is skipped; a
converted TEX image is judged by the format it converted into (.png/.jpg),
a raw entry by its own extension
-E, --output-onlyexts Only write files with these extensions (judged the same way as -I)
--filter-effect-images <PERCENT>
Skip entries whose converted image is mostly transparent or black
(typical effect sprites); threshold percent 1-100, 0 = off, e.g. 85 = skip
when the transparent OR black ratio is >= 85%
--onlypaths <PREFIXES> Only extract entries under these directory prefixes (comma-delimited,
subfolders included, both \ and / accepted; e.g. materials, materials/masks)
--ignorepaths <PREFIXES> Don't extract entries under these directory prefixes (same syntax)
--paths-depth <N> Limit --onlypaths/--ignorepaths to N path segments after the prefix
(1 = direct children only, subfolders excluded; 0 = unlimited, default)
--overwrite Overwrite all existing files
--lazy Read entry bytes one at a time instead of loading the whole package
--max-entry-size <KB> Skip entries larger than this, in KB
--min-entry-size <KB> Skip entries smaller than this, in KB
- info - Dumps PKG/TEX info
-s, --sort Sort entries a-z
-b, --sortby <KEY> Sort entries by name, extension or size (default: name; an unrecognized
value falls back to name)
-t, --tex Dump info about all TEX files from the directory given as input
-p, --projectinfo <KEYS> Keys to dump from project.json (comma-delimited, * for all)
-e, --printentries Print entries in packages
--title-filter <TEXT> Only list packages whose project.json title contains this text
(case-insensitive)
Exits 1 when the input path is missing/invalid or at least one file failed to parse (the failing
files are named on stderr / reported inline, e.g. Failed to read package: … — a corrupt package
no longer aborts the run with a raw exception); otherwise 0.
- batch - runs many wallpapers in one process, driven by a JSON manifest;
modeselects unpacking them to files (extract), rewriting them as mobile packages (mpkg), converting mobile packages back to PC ones (pkg), or packing a project directory into a PC package (pack)
repkg batch --manifest manifest.json [--threads 8]
--threads overrides the manifest threads; when it is 0 the manifest value is used, and when that
is 0 as well the worker count falls back to the physical core count (hyper-threading adds no
throughput here and doubles memory) — narrowed to the cgroup CPU quota when the kernel gives one.
Failures never stop a batch: each one is reported as a JSON
event on stdout and the next wallpaper is started. A finished batch always exits 0; only a missing
or invalid manifest exits 1.
Progress is reported as one JSON object per line on stdout (wallpaper start/done, entry, error,
batch done); the batch continues on errors and always exits 0 unless the manifest is invalid.
stdout carries nothing but those events — the single diagnostic line a run prints
(* gate: avail=503MB (cgroup v2:/user.slice/…) threads=1 (cgroup v2 cpu.max:…)) goes to stderr,
which is how you tell whether the memory gate is seeing the container's limit or the host's.
- pack - packs a wallpaper project directory (the loose files the editor works on) back into a PKG
-o, --output <DIR> Directory for the produced .pkg (default: ./output)
-n, --name <NAME> File name stem of the produced .pkg (default: project folder name)
--magic <MAGIC> Package magic to write (default PKGV0018)
--overwrite Overwrite an existing target PKG instead of writing name_1.pkg
--keep-source-images Also pack the source images alongside their .tex (real WE packages
carry none)
--no-tex-encode Do not wrap source images into a passthrough .tex; ship them as their
own entries
--dxt <FORMAT> Block-compress wrapped textures into DXT1, DXT3 or DXT5 instead of the
default passthrough PNG/JPEG blob (accepted: dxt1/dxt3/dxt5 or 1/3/5).
The result is a TEXB0002 .tex with a full mip chain (halved to 4x4),
every mip LZ4-compressed when that actually shrinks it. Multi-frame GIFs
and images smaller than 4x4 cannot be block-encoded and fall back to the
passthrough shape (or the raw entry), reported per file. An invalid
--dxt value exits 1 without packing anything
--no-loose-metadata Do not copy project.json / the preview image next to the produced .pkg
--excludepaths <PREFIXES>
Also skip these relative path prefixes (comma-delimited)
<input> is either a project directory (it holds project.json, scene.json, index.html or
assets.json at its root) or a directory whose subfolders are projects — in the latter case every
subfolder is packed on its own. Several projects sharing one output directory is supported; see
mode: "pack" below for what happens to their project.json.
Manifest keys are matched case-insensitively (onlyPaths and onlypaths are the same key).
| Key | Type | Default | Meaning | CLI equivalent |
|---|---|---|---|---|
mode |
string | extract |
extract = unpack to files; mpkg = rewrite the package as a mobile .mpkg; pkg = convert a mobile package back to a PC .pkg; pack = pack a project directory into a PC .pkg (see below); inspect = read-only health check, writes nothing (see below) |
- |
threads |
int | 0 | worker threads, 0 = physical core count (narrowed by the cgroup CPU quota) | --threads (wins over this) |
wallpapers |
array | - | jobs, one item per wallpaper; required | - |
wallpapers[].id |
string | - | echoed back in every event of this wallpaper | - |
wallpapers[].input |
string | - | a .pkg/.mpkg file, or a directory searched recursively for them; mode: pack wants a project directory instead (or a parent of several); mode: inspect never writes, so it is the one mode that does not need output |
<input> |
wallpapers[].output |
string | - | output directory for this wallpaper | --output |
wallpapers[].outputName |
string | source package name (pack: project folder name) | mode: mpkg / mode: pkg / mode: pack - file name (without extension) of the produced .mpkg/.pkg; invalid characters are replaced with _, and a wallpaper that yields several packages gets _<source-name> appended so they don't overwrite each other |
-n, --name (pack) |
wallpapers[].options |
object | every key falls back to global options |
mode: mpkg only (mode: inspect reads the same keys to describe what a tier would do) - per-entry overrides for the nine packing keys (preset / mpkgMagic / keepAudio / noLz4 / mpkgReduction / mpkgEtc2 / mpkgNoShaderCompat / mpkgNoDematerialize / mpkgShrinkDx). Only keys actually present override, so an entry can change just the reduction and inherit the rest; an entry preset fills only the two cells that entry did not write, so precedence is explicit key > entry preset > global (preset included) > default. A combination that is only illegal after merging (e.g. mpkgEtc2 on an entry whose resolved mpkgReduction is 1) fails the manifest at load time - nothing gets packed. Lets a caller send one batch instead of one batch per preset |
- |
options.overwrite |
bool | false | overwrite existing files | --overwrite |
options.onlypaths |
string[] | none | directory prefixes to keep | --onlypaths |
options.ignorepaths |
string[] | none | directory prefixes to drop | --ignorepaths |
options.pathsDepth |
int | 0 | depth limit for the two above | --paths-depth |
options.onlyexts |
string[] | none | extract-level extension filter | --onlyexts |
options.ignoreexts |
string[] | none | extract-level extension filter | --ignoreexts |
options.outputOnlyExts |
string[] | none | write-level extension filter | --output-onlyexts |
options.outputIgnoreExts |
string[] | none | write-level extension filter | --output-ignoreexts |
options.singleDir |
bool | false | true flattens the entry paths into one output directory (same meaning as -s/--singledir). The legacy key keepSubfolderStructure is still read — its name says the opposite of what it does — but it now prints a deprecation warning on stderr; when both keys are present singleDir wins |
-s, --singledir |
options.noTexConvert |
bool | false | don't convert TEX to images | --no-tex-convert |
options.onlyTexImages |
bool | false | skip raw .tex writes |
-p, --only-tex-images |
options.filterEffectImages |
int | 0 | read as an integer percent | --filter-effect-images |
options.mpkgMagic |
string | PKGM0019 |
mode: mpkg only - magic written into the output package |
- |
options.keepAudio |
bool | false | mode: mpkg only - keep sounds/*.mp3 instead of dropping them |
- |
options.noLz4 |
bool | false | mode: mpkg only - don't try to LZ4-compress materialized pixels |
- |
options.preset |
string | - | mode: mpkg only - one of 1x / 2x / 4x (case-insensitive; the × glyph is accepted too), expanding to WE's own three-tier pair: 1x = mpkgReduction: 1 + no ETC2, 2x = 2 + ETC2, 4x = 4 + ETC2. It only fills in keys you did not write - an explicit mpkgReduction / mpkgEtc2 in the same block wins, which is how "2x but keep RGBA8" stays expressible. An unknown name is a manifest error (exit 1), never a silent fallback to 1x |
- |
options.mpkgReduction |
int | 1 | mode: mpkg only - texture downscale divisor (any value >= 1; the presets above use 1/2/4). Only entries that get materialized are resized; the size is also recorded as "texturereduction" in the scene file |
- |
options.mpkgEtc2 |
bool | false | mode: mpkg only - emit reduced pixels as ETC2 RGBA8 (format=5, 1 byte/pixel) instead of RGBA8. Requires mpkgReduction > 1; not yet verified on a phone, hence off by default |
- |
options.mpkgNoShaderCompat |
bool | false | mode: mpkg only - true turns off the GLSL → GLSL ES rewrite that appends .0 to integer literals sitting in a float context. Mobile GLSL has no implicit int→float, so a shader that fails to compile makes its material fall back to the base texture and the layer shows up as a plain white rectangle. Every rewrite is reported per package as 着色器改写 N条/M处 |
- |
options.mpkgNoDematerialize |
bool | false | mode: mpkg only - true copies every .tex verbatim instead of materializing it to RGBA8/ETC2, i.e. "same container, same pixels". It is the parent of mpkgReduction / mpkgEtc2 / mpkgShrinkDx: when it is set, those three are normalised to 1 / false / false during manifest parsing - they neither error nor do anything, and both the summary line and mode: inspect report the normalised values, so a manifest can no longer express "4x + fmt5 + copy" (four switches on, nothing happening). noLz4 has no object either (LZ4 only applies to materialized mips), but its value is deliberately left alone: it changes no output bytes and appears in no reading, so rewriting it would only make the manifest contradict the log. Shader rewrites still happen (that is not pixel work). Callers that construct MobilePackageOptions directly bypass this derivation - the converter keeps an error-level warning plus the omitted texturereduction key as the backstop there. The reverse path spells the same idea without the prefix (noDematerialize, mode: pkg only) - the two keys are read from one options block, so writing the wrong one is silently ignored by the mode you asked for |
- |
options.mpkgShrinkDx |
bool | false | mode: mpkg only - also decode-and-resize DXT1/3/5 payloads instead of copying them byte for byte. Off by default because that path was previously reached only together with mpkgEtc2 (whose bytes are the phone-verified ones), and DXT is cheaper than RGBA8: on a DXT1 wallpaper mpkgReduction: 2 with ETC2 off makes the package roughly twice as large, not smaller. Inert when mpkgReduction is 1 - no error, since a global key plus a per-entry 1× is a normal manifest shape. Counted per package as DXT重缩 N |
- |
options.pkgMagic |
string | PKGV0018 |
mode: pkg and mode: pack - magic written into the output PC package |
--magic (pack) |
options.noDematerialize |
bool | false | mode: pkg only - true copies materialized RGBA8 textures verbatim instead of re-encoding them back to a PNG passthrough blob (useful when debugging the reverse path) |
- |
options.keepReductionKey |
bool | false | mode: pkg only - true keeps the "texturereduction" key in scene.json instead of removing it |
- |
options.packKeepSourceImages |
bool | false | mode: pack only - also pack source images next to their .tex |
--keep-source-images |
options.packNoEncode |
bool | false | mode: pack only - don't wrap source images into a passthrough .tex |
--no-tex-encode |
options.packDxt |
string | none | mode: pack only - block-compress wrapped textures: dxt1/dxt3/dxt5 (or 1/3/5), same semantics and fallback rules as --dxt; an invalid value makes the manifest invalid (exit 1) |
--dxt |
options.packNoLooseMetadata |
bool | false | mode: pack only - don't copy project.json / the preview next to the produced .pkg |
--no-loose-metadata |
options.packExcludePaths |
string[] | none | mode: pack only - extra relative path prefixes to skip |
--excludepaths |
{ "mode": "pack", "wallpapers": [ { "id": "1", "input": "D:/WE/projects/myprojects/my wp", "output": "D:/share", "outputName": "my wp" } ] }
The inverse of extract: the input is a directory of loose files, not a package, so there is no
source entry table to copy — the plan is built from a directory walk. The container is written the
same way the other two pack modes write it (one pass, offsets patched afterwards, nothing is ever
buffered as a whole package), entries are named with forward slashes relative to the project root,
and the produced file satisfies the same invariant the reader checks: table size + Σ entry lengths
= file size.
wallpapers[].input may be the project directory itself (it holds project.json, scene.json,
index.html or assets.json) or a parent of several — then every project subfolder is packed
separately. It is deliberately not recursed deeper: effect packages legitimately contain
preview/project.json, and treating that as a new wallpaper would invent wallpapers out of
sub-assets.
What goes in, and what does not, is decided from a census of 279 real local packages (8152 .tex
entries): the complete extension set inside a real WE package is json / tex / frag / vert / mdl / mp3 / ttf / otf / wav / ogg / flac / ttc / gif. There is no png, jpg, tga, obj, mtl,
dxs, tex-json, pkg, or mpkg entry, and no project.json / preview entry either. So:
- a source image with a same-named
.texnext to it is dropped and the.texis packed verbatim — that.texis what the editor compiled, so it is closer to the original than anything we could re-encode. - a source image with no
.texis wrapped into a passthrough.tex(TEXB0004,imageFormat = FIF_PNG/FIF_JPEG, one mip, payload = the image file's own bytes, never re-encoded). This is not an invented shape: 1348 of the surveyed textures are exactly that, shipped by WE itself. Header flags are taken from the<name>.tex-jsonimport-settings sidecar when one is there, becauseclampuvsdecides whether the GPU samples across the edge. .obj/.mtlare dropped when a same-named.mdlexists; without one they are packed as-is and reported (there is no.mdlwriter)..tex-json,*.dxsandshaders/blobsSM*/(the editor's compiled-shader cache), other.pkg/.mpkgfiles found inside the tree,project.jsonand the preview image are never packed.project.jsonand the preview image are written next to the.pkginstead, which is the layout Workshop subscription folders use and what makes the output folder loadable. Two projects packed into the same output directory both want aproject.jsonthere, so the second one is not overwritten — it is reported as an error event. Pass--no-loose-metadatato skip the copies.- An existing target of the same name is never overwritten:
scene.pkg→scene_1.pkg→ … (pointing the output at a subscription folder would otherwise destroy a Steam-downloaded original and make WE re-download it on the next integrity check).--overwriteopts in.
Two things to know about the produced package:
- The default
.texwe compile is a passthrough blob, which is bigger than the editor's. A passthrough PNG costs what the PNG costs, while the editor's DXT5 is 1 byte/pixel (4K: 8 MB against 33 MB uploaded). That shape is not invented — 1348 of the surveyed textures are exactly it — but if size matters,--dxt dxt5switches every wrapped texture to real block compression: aTEXB0002with a full mip chain, each mip LZ4'd when that pays. It is opt-in because the passthrough shape is what WE itself ships for imported PNGs, and because block compression is lossy in a way a PNG blob is not. Images it cannot encode (multi-frame GIFs, anything under 4x4) fall back to the passthrough shape or the raw entry, and the fallback is reported per file. Every wrapped texture is counted in the封纹理 Nreport line;--no-tex-encodeturns wrapping off entirely if you would rather see the raw image entry than a texture you did not ask for. - A same-named
.texthat the editor has not refreshed after an image edit is shipped as-is. The packer compares file names, not image content, so it cannot notice thatfoo.pngchanged afterfoo.texwas compiled. WE itself renders from the.tex, so this matches the editor.
{ "mode": "mpkg", "wallpapers": [ { "id": "1", "input": "C:/.../431960/123/scene.pkg", "output": "C:/out" } ] }
Each .pkg/.mpkg input becomes <output>/<same-name>.mpkg, or <output>/<outputName>.mpkg when the
job carries that key (callers typically pass the wallpaper title or the workshop id). The entry table is
rebuilt, the magic becomes mpkgMagic, and project.json / the preview image are embedded from the files
sitting next to the input package (a package that already carries them keeps its own copies).
Per entry, exactly one of these happens:
- a texture whose payload is a passthrough encoded image (TEX container
imageFormat != FIF_UNKNOWN, i.e. the pixels are stored as a PNG/JPEG blob) is decoded to raw RGBA8 - straight alpha, one mip,TEXB0004withimageFormat = FIF_UNKNOWN, and LZ4 when that actually shrinks the payload. Mobile reads such an entry aswidth*height*4raw bytes, so a passthrough blob renders as garbage. - everything else is copied byte for byte, including DXT1/3/5 textures with their full mip chains,
R8/RG88 masks, video textures (an mp4 embedded in a
.tex), models and JSON. Textures the reader cannot parse are copied as well, so an unknown format never blocks a conversion. The one way a.texstops being copied is a reduction: withmpkgReduction > 1a DXT payload is decoded and resized too oncempkgEtc2is on (that pairing is the phone-verified shape) or oncempkgShrinkDxasks for it on its own. On DXT that decode is what makes a reduction possible at all - and DXT costs fewer bytes than RGBA8, sompkgShrinkDxwithoutmpkgEtc2can make the package bigger than it started. SetmpkgNoDematerializeto copy every.texregardless - that is the "same pixels, new container" package. .frag/.vertsources are copied byte for byte except that an integer literal standing in a float context gets a.0suffix (the rewrite is insert-only: the output equals the input with".0"spliced in afterNliterals, and nothing else changes). Mobile GLSL has no implicit int→float, so an unpatched shader fails to compile and its material silently falls back to the base texture - on screen that is a white rectangle. SetmpkgNoShaderCompatto keep the sources untouched.
mode: mpkg runs one package at a time and caps wallpaper concurrency at 2: packing writes a single
output file, so entries cannot be spread over workers, and one materialized 8K texture alone can need
~230 MB of pixel buffer.
With mpkgReduction = 1 (the default) the conversion is deliberately faithful rather than small: it never
rescales a texture, so an output package is typically about twice the size of a Wallpaper Engine export made
with a reduced quality setting (which halves every texture and re-encodes them).
Byte-identity ends at the resize step. Any texture a reduction actually rescales (preset 2x/4x —
the default mpkgReduction = 1 rescales nothing) goes through ImageSharp's Lanczos3, and that
assembly carries a separate implementation per CPU feature set (Avx2, Sse41, Vector4 …); the taps are
float sums taken in a different order, so a few pixel components land on the wrong side of a rounding
tie. The resample call does not care which decoder fed it, so the exposure is not limited to DXT
sources; the decoders themselves are not implicated — every DXT→PNG decode extract produces is
byte-identical across build flavors. Measured on a real package: 5 of 126 entries differ, every
differing byte exactly ±1, container and entry table untouched. JIT as-is, NativeAOT, and JIT with
DOTNET_EnableHWIntrinsic=0 are three mutually different outputs for the same input and none is
canonical, so compare artifacts byte for byte only within one build flavor; across flavors or
machines, compare entry tables / sizes / formats.
Not expressible in a manifest: --tex, --recursive, --usename, --copyproject,
--min-entry-size, --max-entry-size (no manifest key exists for them), and --lazy - the batch
executor already reads entries on demand, so the flag is forced off (BatchManifest.cs).
{ "mode": "pkg", "wallpapers": [ { "id": "1", "input": "C:/out/scene.mpkg", "output": "C:/pc" } ] }
The reverse of mode: mpkg: the entry table is rebuilt with the magic set to pkgMagic
(PKGV0018 by default), and project.json / the preview image already inside the package are
kept as-is. Like mpkg it runs one package at a time with wallpaper concurrency capped at 2, and
its events use the same protocol.
Per entry:
- a texture that is our own materialized output (container
TEXB0004,imageFormat = FIF_UNKNOWN, header format RGBA8, a single frame) is re-encoded losslessly back into a PNG passthrough blob and written asimageFormat = FIF_PNG. The pixel data round-trips byte for byte; the encoded blob is not identical to the original PNG bytes, because those were discarded by the forward conversion and cannot be recovered. SetnoDematerializeto copy such entries verbatim instead. - a
"texturereduction"key inscene.jsonis removed, restoring the PC scene file (setkeepReductionKeyto leave it alone). - everything else is copied byte for byte: DXT1/3/5 textures, R8/RG88 masks, video textures, models, JSON, and textures the reader cannot parse (reported as an error event, never a blocker).
Two things the reverse path deliberately does not attempt:
- Shader
.0rewrite is not undone. The forward rewrite is insert-only and a PC GLSL source may legitimately contain the same1.0literals, so an appended.0cannot be told apart from a native one. Left-in.0suffixes are valid GLSL for the PC driver, so undoing them would be risk without benefit. - Dropped audio cannot come back, and ETC2 (or downscaled) pixels cannot be restored to their
original resolution — those inputs are gone by the time the
.mpkgexists. Converting ampkgReduction > 1package back yields a valid PC package with the smaller textures it carries.
A Wallpaper Engine phone export (as opposed to one produced by this tool) usually stores DXT or already-encoded textures; for those the conversion is the container swap alone and is lossless.
Manifest format (0 = physical core count for threads; options match extract):
{
"threads": 0,
"wallpapers": [
{ "id": "0", "input": "C:/path/to/wallpaper_dir", "output": "C:/path/to/out/wallpaper_0" }
],
"options": { "overwrite": true, "onlypaths": ["materials"], "filterEffectImages": 85 }
}
{ "mode": "inspect", "wallpapers": [ { "id": "1", "input": "C:/.../431960/123", "options": { "preset": "4x" } } ],
"options": { "preset": "2x" } }
The reason this mode exists is one silent behaviour of mpkg: a .tex it copies says nothing about
why. A wallpaper whose textures are all DXT5 comes out the same size at 1x and at 4x, and the only
reading is 缩小 0 - which does not distinguish "nothing here can shrink" from "the switch that would
have shrunk it is off". The probe replays the converter's own predicate (MobileTextureMaterializer.WouldReduce,
same class that decides what to materialize) over structure-only TEX reads, so a caller can say
"68.5% of this package's bytes are DXT5; without ETC2 or shrink-Dx no tier will move them" before the batch.
It reads no pixels and writes no file, so wallpapers[].output may be left out. The tier keys it understands
are exactly the mode: mpkg ones - preset / mpkgReduction / mpkgEtc2 / mpkgShrinkDx /
mpkgNoDematerialize, per entry or global, same precedence - because a probe that disagreed with the
converter about what a tier means would be worse than no probe. The same derivation applies here: on a row where
mpkgNoDematerialize is true, the event reports the normalised reduction/etc2/shrinkDx (1/false/false) -
a probe claiming "4x will shrink 7 textures" while textures are being copied verbatim would be the lie.
One event per package, plus the usual wallpaper start/done and error lines:
{"id":"1","type":"inspect","file":".../scene.pkg","entries":12,"bytes":1355687,"tex":1,"texBytes":1343778,
"passthrough":0,"dxt":1,"dxtBytes":1343778,"raw":0,"mask":0,"video":0,"noimages":0,"unreadable":0,
"audio":0,"audioBytes":0,"scene":true,"reduction":4,"etc2":false,"shrinkDx":false,"dematerialize":true,
"wouldReduce":0,"largestTex":1343778,"failed":false}
passthrough + dxt + raw + mask + video + noimages + unreadable == tex always holds. wouldReduce is the
number of .tex entries that tier really resizes - 0 with tex > 0 is the "缩不动" signal, and with
dematerialize: false it is forced to 0 because that is what the package will show.
Simply extract PKG and convert TEX entries into images to output folder created in current directory
repkg extract E:\Games\steamapps\workshop\content\123\scene.pkg
Find PKG files in subfolders of specified directory and make wallpaper engine projects out of them in output directory
repkg extract -c E:\Games\steamapps\workshop\content\123
Find PKG files in subfolders of specified directory and only convert TEX entries to png then put them in ./output omitting their paths from PKG:
repkg extract -e tex -s -o ./output E:\Games\steamapps\workshop\content\123
Convert all TEX files to images from specific folder
repkg extract -t -s E:\path\to\dir\with\tex\files