analyze · audit · strip languages · trim paks · recompress loose files · backup & restore · cooked-asset inspection · mip-strip projection · Δ-Codec residual compression
Note
What this is. An integrated analyze → trim → smaller game workflow for UE4/UE5 installs, with a backup-or-refuse policy from day one.
What it isn't. A pak recompressor, a transcoder, or a magic wand for AAA UE5 IoStore titles. Honest scope below — see What shrinkray will NOT do.
|
Pamali (UE4 demo, 591 MB install) Top wins are 4 K hair masks cooked at full res that nobody zooms in on. Read-only projection today; byte-exact write lands in v0.6. |
|
Main window — Aero chrome, preview-mode default-on, the three Phase 2 entry panels (pick folder · Inspect a pak · Texture mip strip)
More panels (analyze report, bloat audit, asset inspector, mip-strip projection, in-app file dialog) land in
docs/screenshots/as we capture them.
- Why
- The pipeline
- Realistic targets
- What shrinkray will NOT do
- Safety model
- Roadmap
- Quick start
- What this build does
- Stack
- Project layout
- Validation against real games
- License
UE games ship with predictable bloat — but not the bloat most tools promise to fix. A research pass (see research/SUMMARY.md) found modern UE5 already ships near-optimal codecs (Oodle Kraken, Bink Audio, BCn textures) that a third-party tool cannot meaningfully beat. The real size wins come from dropping content, not transcoding it:
- Unused languages — a fully-voiced AAA ships 4–8 dub languages; you only play one. Often 30–60 % of the audio bucket alone, zero quality loss.
- Orphan pak entries — dev/debug paks, duplicated shader caches, leftover platform variants.
- Loose marketplace / modded assets — uncooked
.png/.wav/.flacre-encodable with no parser needed. - Asset-flip / RPG-Maker-on-UE bloat — sometimes 80 % of the install is content that's never referenced.
- Oversized mip 0 textures — 4 K hair masks, 4 K decals, anything where the source artist forgot to set a max LOD.
Existing tooling (UnrealPak, umodel, FModel, UAssetGUI, repak) reads UE assets but doesn't apply optimizations. shrinkray is the integrated workflow on top.
flowchart LR
A[Drop a folder] --> B[Analyze<br/>census · L10N · paks · top 50]
A --> C[Bloat audit<br/>13 detectors · 0-100 score]
B --> D[Asset Inspector<br/>cooked .uasset drill-down]
D --> E[Mip-strip projection<br/>per-format byte formula]
B --> F{Preview mode?}
F -->|yes| G[Diff report only]
F -->|no| H[Differential backup<br/>SHA-256 manifest]
H --> I[Apply ops<br/>L10N strip · pak trim · recompress]
I --> J[Smaller folder]
G -.->|preview only| J
H -.->|restore| K[shrinkray restore]
K --> A
style A fill:#0ea5e9,stroke:#0369a1,color:#fff
style J fill:#16a34a,stroke:#15803d,color:#fff
style H fill:#f59e0b,stroke:#b45309,color:#fff
style F fill:#7c3aed,stroke:#5b21b6,color:#fff
style K fill:#dc2626,stroke:#991b1b,color:#fff
Every destructive op is preceded by a differential backup entry. Preview mode is default-on for first-time users — every "Apply" button is hard-disabled until you opt out.
| Game type | Typical savings | Where they come from |
|---|---|---|
| Asset-flip indie / RPG Maker UE ports | 50–80 % | Loose-file recompression + orphan trimming |
| Heavily-modded titles | 30–60 % | Uncompressed audio re-encode + language stripping |
| Mid-tier indie | 20–40 % | Language stripping + dev/debug pak removal |
| UE4 game w/ readable paks (Pamali class) | up to 77 % of texture bytes | Mip-strip projection (v0.6 will apply it) |
| Already-optimized AAA UE5 (IoStore) | 5–15 % | Language stripping (the bulk) + orphan trimming |
These are the upper-realistic numbers. The lower bound on a modern UE5 single-language AAA install is often <5 %. shrinkray surfaces the predicted delta in the diff report before any write.
Separate from the trim/backup workflow, shrinkray ships Δ-Codec — a texture
compression scheme that puts both an AI-fast prediction and a byte-exact
residual in one bitstream. Restore re-runs the predictor and adds the
residual back: byte-exact in RGBA at quant_step == 1, verified by a
per-texture SHA-256 receipt (the anti-cheat-safe property AI upscalers can't
offer).
What the validation actually says (paper-grade, not marketing):
- 126 / 126 byte-exact restores across 3 independent bench runs — the lossless invariant holds.
- Bilinear is a shockingly strong baseline for 1K PBR game content: median residual sidecar is 24.5 % of a full backup. PBR is engineered smooth + tileable, so bilinear is near-optimal by construction.
- ESRGAN wins only the niche (11 % of textures — industrial/fine-detail normals). A perfect per-texture oracle beats forced-bilinear by just 5.3 %, so bilinear ships as the default predictor; ESRGAN is opt-in.
The bitstream is predictor-agnostic (PredictorId enum) — future
game-content-trained predictors slot in without a decoder recompile.
→ Spec: docs/delta-codec-spec.md ·
Diagrams: docs/architecture-diagrams.md ·
Standalone crate: delta-mip (Apache-2.0)
Important
Honesty about scope, because half the existing UE-tool ecosystem overpromises.
Click to expand — the deliberate "no" list
- No pak recompression. Oodle Kraken (the UE5 default) beats Zstd on both ratio and decode speed for game data. Free Oodle encoders don't legally exist. Transcoding to Zstd would actually inflate or stutter.
- No cooked
.uasset/.uexp/.ubulksurgery (write-side). Correctly round-tripping cooked UE assets needs a serializer that tracks ~50 per-subsystem custom versions across every engine release. Only CUE4Parse (read) and UAssetAPI (read/write) do that today. Read-side ships in v0.5; write-side lands v0.6 via a bundled UAssetAPI sidecar. - No Vorbis re-encode by default. Vorbis → Opus is realistically ~20–25 % per file with a lossy → lossy quality cost — opt-in only in v2.
- No Bink Audio anything. No free encoder, no Rust binding.
- No BC3 → BC7 transcode. Same 8 bpp, 0 % size win.
- No IoStore
.utoc/.ucasrewriting in v1. Most UE5 AAA install bytes live in IoStore; v1 reports them but leaves them alone. v0.7 wires inretoc.
If the size you wanted came from those features, v1 is not for you yet.
Warning
shrinkray refuses to write without a backup.
On first run it offers:
- Differential backup (default) — records only the bytes about to be overwritten. Typically 5–15 % of the folder.
- Full copy — safe but expensive on a 100 GB AAA folder.
- Abort.
shrinkray restore <folder> # replays the manifest in reverse with hash verificationSigned paks (.sig sibling present) and undecryptable AES paks are skipped, never partially modified. The restore button is wired into the diff report from day one.
The original 6-phase roadmap was rewritten after the research pass — see research/SUMMARY.md for the full reasoning.
# Clone WITH submodules (CUE4Parse lives under sidecar/external/CUE4Parse)
git clone --recurse-submodules https://github.com/Thanukamax/shrinkray
cd shrinkray
# If you already cloned without --recurse-submodules:
git submodule update --init
bun install
cd src-tauri && cargo fetch && cd ..
# Build the .NET sidecar (requires dotnet 8 SDK)
bash scripts/build-sidecar.sh
python3 scripts/generate_icons.py # placeholder icons; one-time
bun run tauri dev # window at localhost:1420
bun run tauri build # production binarySystem tool deps (Linux)
- Required:
dotnet-sdk-8.0,opus-tools(foropusenc),libz-ng.so.2- Fedora / Nobara: ships by default
- Debian / Ubuntu:
apt install libz-ng opus-tools
- Optional:
oxipng(viacargo install oxipngor your distro) - Recommended for sidecar native helpers:
cmake+ a C/C++ toolchain. The native blob is optional — Linux builds work without it.
bun run tauri build --bundles appimage will produce target/release/bundle/appimage/shrinkray_<version>_amd64.AppImage (~109 MB, self-contained, runs on any glibc-based x86_64 distro from ~2022 onward).
Required system packages (Fedora 41+/Nobara 43, names approximate on other distros):
sudo dnf install \
webkit2gtk4.1-devel gtk3-devel libsoup3-devel \
openssl-devel librsvg2-devel \
fuse fuse-libs file desktop-file-utils \
patchelf gobject-introspection-develThen build with NO_STRIP=true to skip linuxdeploy's bundled strip binary, which is too old to handle modern ELF .relr.dyn relocation sections (present in every Fedora 41+/Nobara 43 system library):
NO_STRIP=true bun run tauri build --bundles appimageWithout NO_STRIP=true, the bundle step prints hundreds of unknown type [0x13] section .relr.dyn errors and exits non-zero — the AppDir is still fully populated, but linuxdeploy never invokes its appimage plugin. The flag is harmless: shipped libs come pre-stripped from /lib64, and the final AppImage is squashfs-compressed regardless.
Known limitations:
- The .NET sidecar (
src-tauri/binaries/sidecar/) and AI models (src-tauri/binaries/ai-models/) are not yet bundled into the AppImage — features that depend on them (asset inspector, AI restore) will need the sidecar installed alongside or a follow-upresourcesentry insrc-tauri/tauri.conf.json. - AppImage runtime needs FUSE2 (
fuse-libson Fedora). On hosts without FUSE, run with--appimage-extract-and-run.
|
Folder census + L10N detection + pak inventory (signed / encrypted / readable / IoStore stub) + top-50 fattest files. CEF locale 13 detectors, 0–100 bloat score, Markdown + JSON output. Works on encrypted installs where content surgery is impossible:
Drill into a single cooked |
Walk every UTexture-derived export in a readable pak, project savings from capping mip 0 dimension. Per-format BC/ASTC byte formula. Pamali UE4 demo: 185 textures → 570 MB save at 1024 px cap (77 %). Read-only; write-side lands v0.6 via UAssetAPI. Drop dub languages from loose files and from inside paks (preserves version / mount / path-hash-seed via PNG via Every destructive op is preceded by a Default-on for first-time users; hard-disables every apply button. Toggle is localStorage-backed. Custom title bar (Tauri OS decorations off), shrinkray audit <path> [--json] [--out FILE]Run from a terminal, share the markdown, paste it into a bug report. |
- Tauri v2 + Bun + Vite + React 19 + TypeScript (mirrors the vn2apk + wgpu-shader-explorer pattern)
- Rust core:
walkdir,tauri-plugin-dialog,repak0.2.3,sha2,dirs. Plusimage_dds,intel_tex_2,symphonia,opus,image-compare,rayon. - Phase 2 sidecar: .NET 8 self-contained CLI vendoring CUE4Parse master (Apache 2.0) as a Git submodule at
sidecar/external/CUE4Parse/. JSON-over-stdin IPC. ~75 MB published binary. Three additive patches to CUE4Parse handle UE4.13-era cook layouts — seeCHANGELOG.mdv0.5.0. - Frontend:
7.cssfor native Win7 Aero chrome (.window,.title-bar, fieldset / button styling). - Shell-out binaries:
oxipng. Optional:mozjpeg,cwebp. - Not used (yet): FFmpeg,
unreal_assetcrate, Bink anything. UAssetAPI bundle pending v0.6 write-side.
Click to expand the tree
src/ # React frontend
App.tsx # top-level shell + sections
TitleBar.tsx # custom Aero title bar (v0.5)
OpenDialog.tsx # in-app Win7 Open dialog (v0.5)
AssetInspector.tsx # cooked .uasset drill-down (v0.5)
MipStripPanel.tsx # texture mip-strip projection (v0.5)
assets/wallpaper.jpg # procedurally-generated Aero bg (v0.5)
crates/
shrinkray-core/src/ # destructive-write subsystems
analyze.rs # folder census + L10N + CEF filter
pak.rs # repak wrapper + pak classification
backup.rs # differential backup + restore
strip.rs # L10N stripping + pak trimming
recompress.rs # PNG / WAV / FLAC recompression
shrinkray-audit/src/ # read-only bloat audit
detectors/ # one .rs per detector, 13 total
shrinkray-sidecar/src/ # Rust IPC client + types
shrinkray-cli/src/ # CLI binary
sidecar/
ShrinkraySidecar/ # .NET 8 sidecar (CUE4Parse host)
AssetInspector.cs # per-package inspection + mip table
AssetLister.cs # pak entry enumeration
StripMipsPlanner.cs # walk + project mip-strip savings (v0.5)
Program.cs # JSON-over-stdin dispatcher
external/CUE4Parse/ # Git submodule — master + 3 patches
src-tauri/src/
lib.rs # tauri::commands wiring
research/ # research notes A-E + SUMMARY (read first)
scripts/ # generate_icons.py, build-sidecar.sh, lyra-smoke.sh
docs/screenshots/ # README screenshots
.github/workflows/ # CI (cargo test + clippy + bun build)
Unit tests build synthetic paks via repak's writer and round-trip them through the trim + backup + restore pipeline — 223 tests, all in CI. That gates correctness of the plumbing, not that shrinkray-modified games still launch.
The one corpus test that would prove launch-safety — Lyra Starter Game cooked PC build, optimize, boot, watch for crash — needs an Epic account to download Lyra source and a full Unreal Engine install to cook a binary, neither of which fits free GitHub runners. Until a self-hosted runner with a pre-cooked corpus exists, Lyra validation is a manual pre-release step: see scripts/lyra-smoke.sh.
| Game | Engine | Use as | State |
|---|---|---|---|
| Pamali demo | UE4 (4.13-era) | readable-pak target — primary mip-strip subject | 185 textures, 570 MB save at 1024 px |
| Stellar Blade | UE5 | IoStore corner case (60 GB AAA) | Detected: 64 paks all IoStore, retoc Phase 2 needed |
| AUGUST NIGHT | UE5 | IoStore at small scale (1 pak) | Reproduces the IoStore branch |
| Wuthering Waves | UE5 | fully-encrypted target | Audit-only; content surgery blocked |
| Lyra Starter Game | UE5 | launch-validation target | Manual pre-release; not in CI |
Source-available, no redistribution — see LICENSE.
You may clone this repo, read the source, and build shrinkray for your own use. You may not redistribute the source or binaries, mirror the repo, or publish forks / modified versions without prior written permission.
Earlier commits (v0.3.x and prior) were MIT-licensed and remain so for anyone who obtained them under that license; this restriction applies to commits from the relicense onward. The copyright holder reserves the right to relicense future versions under permissive terms at any time.
Built with Tauri v2, Rust, React 19, .NET 8, and an unreasonable amount of patience for cooked UE4 textures.