A Rust modding utility for Project Wingman (PW) — inspired by Project Sicario.
- Status
- V1 Scope — 1:1 parity with the Project Sicario merger
- Parity status
- Merging mods without metadata (
combine) - Translation mods (
.locres) - Documentation
- Building
- Usage
- Project Structure
- License
v1 (Sicario-merger parity) is essentially complete and byte-verified against the C# merger. Working today:
- PAK I/O (
info,list,unpack,pack,scan) .dtm/.dtppatch parsing, Fragment DSL, every Sicario patch type- Template rendering (Fluid-compatible:
vars/inputs+ the full Sicario filter set) - Verified DataTable engine (byte-exact walker + same-size splice + length-changing rowops)
- HexPatch engine (
filePatches) with the C# stream-walk semantics and the.uexplength auto-correct - Merge pipeline: component model (embeddedPresets → loosePresets → sicarioRequests), engine-major phases,
.uexp/.uassetsidecar handling buildwrites a real mergedSicarioMerge_P.pak;preset-packbuilds standalone preset packs with the preset embedded atContent/sicario/;--reportwrites the C#-identical merge reportcombinemerges conflicting override mods that carry no Sicario metadata (the common case for 2024+ mods) — three-way, field-level, with a full conflict report
In-game acceptance passed (SPEAR Unlock + Improved Chimera verified in Project Wingman 2.1.1A).
The v1 goal is 1:1 parity with the Sicario merger — no less.
A drop-in replacement for the Sicario merger (SicarioPatch.Loader) against Project Wingman 2.1.1A / UE 4.27 / Pak V11:
- Inputs: identical
.dtm/.dtpWingmanMod JSON, with_vars/_inputstemplating. - Patch semantics: every Sicario fragment type, every Sicario patch type, identical matching and value behavior.
- Merge semantics: identical multi-mod merging to Sicario's engine.
- Output: a Pak V3 mod pack (mount
../../../) for the game's~mods/directory — same as the merger. - Zero .NET dependency — single native binary.
Verified against the C# merger at byte level (oracle harness in ~/modding/project-wingman/sicario-oracle):
| Surface | Status |
|---|---|
| DataTable patches (propertyValue, modify, array, text, duplicate*, delete) | ✅ byte-exact (uassets identical; uexps modulo random FText keys) |
filePatches hex engine (all types, windows, filters, length fix-up) |
✅ byte-exact, including the destructive absent-value path |
objectRef (import-table writes) + customSkins PSM slot merging |
✅ byte-exact + in-game verified (AJS-37 skin slot live) |
| Multi-mod merge order + conflict semantics | ✅ oracle-verified |
Components, parameters/inputs, engine-version gate, GetLabel |
✅ |
Merge report (--report) |
✅ byte-identical |
preset-pack |
✅ byte-identical output pak |
| In-game acceptance | ✅ |
All known v1 parity gaps are closed. customSkins skin-slot merging works with real
PSM skin paks (ProjectWingman/Content/Assets/Skins/<aircraft-row>/…), live-verified in-game.
- Self-contained merges (default): the merged pak embeds detected skin files, so one pak
installs everything (
--no-embed-skinsrestores the strict C# output). - No-metadata merging (
combine): three-way, field-level datatable merging for plain override mods, with import carrying and conflict reports. - End-of-merge reports for both
buildandcombine: merge order, field-level conflicts (which mod overwrote which value), and warnings.
Project Sicario's merge metadata (.dtp presets, embedded requests) was never updated for
Project Wingman 2.0 / UE 4.27, so most 2024+ mods are plain file overrides — and when two
of them edit the same uasset, the game loads only one. modman combine merges them anyway:
modman combine <pak-or-dir...> --install-path <game> --output <dir>- DataTables are merged three-way (vanilla + each mod's delta, later mods win) at field level — different fields of the same row all survive; genuine same-field clashes resolve later-wins and are reported.
- Mods that add object references (textures, materials) keep working: import-table entries are carried with all FName and package-index references remapped.
- Non-datatable files (maps, models, textures) pass through single-winner, with a warning when two mods fight over one — those file types genuinely cannot be combined.
- Every run ends with a merge report: the order, every conflict (with mod names and values), and warnings.
https://emberglazee.github.io/project-modman/ — the same merge engine, compiled to WebAssembly and running entirely in your browser:
- Drop your conflicting
*_P.pakfiles, point at your game'spakchunk0-WindowsNoEditor.pak, hit merge, download the combined pak. - Nothing is uploaded — the game pak is read in place (a few MB of it, even from a ~16 GB file) via the browser's File API; the page makes zero network requests after loading.
- Shows the same merge report (order + conflicts) with a friendly UI.
- Windows — double-clicking
modman.exeshows a welcome screen (it pauses so you can read it) pointing at the browser option; the CLI itself takes arguments (modman --helpin a terminal). Windows may show a SmartScreen prompt for a new unsigned binary: More info → Run anyway. - macOS — the binaries are unsigned, so Gatekeeper blocks the first run:
right-click → Open once, or
xattr -d com.apple.quarantine modman. - Linux —
chmod +x modmanand run it from a terminal. - Or skip all of that: the web page, or the offline
modman-merge.html(one file, double-click it — no install, no server, no network).
The game's text lives in Unreal localization binaries at
ProjectWingman/Content/Localization/ProjectWingman/<lang>/ProjectWingman.locres.
modman reads and patches them with full structural fidelity — every namespace/key
hash, entry order and string reference count is preserved, so in-game lookups never
miss. (Verified: parse → write reproduces all nine shipped language files
byte-for-byte.)
# 1) Pull the language file straight out of the game's pak
# (finds the install automatically; --pak <file> to use another pak)
modman locres extract --lang en-US -o ProjectWingman.locres
# ...or go straight to the spreadsheet: add --csv
# ...or omit --lang to dump every language at once
# 2) Dump a locres to a translator-friendly CSV (key,source,Translation)
modman locres read ProjectWingman.locres -o translations.csv
# 3) Translate the third column — the CSV matches the UEExtractor format,
# so the existing translator tooling and workflows apply as-is
# 4) Build a ready-to-install translation mod in one step
modman locres pak ProjectWingman.locres translations.csv --lang en-US -o MyTranslation_P.pak
# (--dedup merges identical strings, matching other community tools — a
# rebuild of an existing mod comes out byte-identical to it, verified
# against the ":3" mod)
# Bonus: see exactly what a mod changes vs the game
modman locres diff Original.locres Mod.locres --csv changes.csv --limit 40Drop the result into ~mods — done.
In-depth guides live in docs/:
- Merging conflicting mods — the no-metadata merge, conflict semantics, what can and can't merge, reading the report.
- Translation mods — the full
.locresworkflow, the CSV format, tips, and binary format notes for tool authors. - FAQ — install paths, first-run warnings (SmartScreen, Gatekeeper), merge conflicts, browser-page privacy.
Requires Rust 2021 edition or later.
# Build all crates
cargo build --workspace
# Build the CLI only
cargo build -p modman
# Run tests
cargo testmodman --help
modman --version
# Merge mods/presets into a mod pak (like `ProjectSicario build`)
modman build <paths...> --install-path <game> --output <dir> --report merge-report.json
# Build standalone preset packs (preset embedded at Content/sicario/)
modman preset-pack <preset.dtp...> -n MyPack --install-path <game>
# Merge conflicting override mods — no metadata required
modman combine <pak-or-dir...> --install-path <game> --output <dir>crates/
├── modman-core/ Data models, patch engine, merge pipeline, components,
│ locres reader/writer
├── modman-uasset/ UE4 .uasset binary parser (standalone)
├── modman-pak/ PAK file operations (wraps repak)
├── modman-cli/ CLI binary
└── modman-wasm/ WebAssembly frontend (wasm-bindgen) powering the web UI
web/ GitHub Pages site: hosted page + offline single-file build
MIT