pefix 0.3.0
dotnet tool install --global pefix --version 0.3.0
dotnet new tool-manifest
dotnet tool install --local pefix --version 0.3.0
#tool dotnet:?package=pefix&version=0.3.0
nuke :add-package pefix --version 0.3.0
pefix
Static portability and load-failure diagnostics for .NET assemblies, with one safe automatic header fix.
pefix is a single-binary CLI that inspects .NET assemblies for portability and load-failure causes: PE header layout, target framework, ReadyToRun, trimming, single-file bundles, platform restrictions, mixed-mode native code, reference assemblies, BepInEx plugin metadata, and more. Directory scans also surface missing managed references, duplicate providers, version conflicts, and missing hard BepInEx plugin dependencies. pefix closure traces transitive AssemblyRef chains. The automatic rewrite contract is the byte-level PE header fix for pure-IL PE32+ assemblies; other write commands require explicit user intent. File inspection uses stable reason_code values; directory issues use stable issue codes plus remediation hints.
The stable integration surface is the CLI, JSON output, reason codes, issue codes, and exit codes.
Install
As a .NET global tool:
dotnet tool install -g pefix
Run once with .NET 10 SDK, using the same arguments as pefix:
dotnet tool exec pefix -- inspect MyMod.dll
Or build from source:
git clone https://github.com/FeathBow/pefix
cd pefix
dotnet publish src/PeFix/PeFix.csproj -c Release -r osx-arm64 \
--self-contained -p:PublishAot=true -o ./out
The resulting ./out/pefix is a self-contained native binary with no .NET runtime dependency. Replace osx-arm64 with linux-x64 or win-x64 as needed.
Quick start
Inspect a single assembly:
pefix inspect MyMod.dll
Output:
pefix MyMod.dll
Status: FIXABLE
Summary: This assembly uses a platform-specific header, but the managed code is portable and can be fixed.
Action: Run: pefix fix MyMod.dll --apply
Details:
PE Format: PE32+ (AMD64)
IL Only: Yes
...
Status: fixable
Preview a fix (dry-run by default):
pefix fix MyMod.dll
Apply the fix:
pefix fix MyMod.dll --apply
Output:
pefix MyMod.dll fix
Status: PATCHED
Summary: PE header patched to AnyCPU.
Action: Backup written to MyMod.dll.bak.
Details:
PE Format: PE32+ (AMD64)
Status Before: not compatible
Status After: compatible
Backup: MyMod.dll.bak
Verify: re-inspection passed
Scan a directory:
pefix scan ./mods
Output:
pefix mods
Summary: Scanned 3 candidate files. 2 require attention.
Action: Run pefix fix <path> --apply for entries marked fixable.
Counts: compatible: 1 fixable: 1 cautioned: 0 unsafe: 1 corrupt: 0 issues: 0
Group: portability
- Compatible.dll [compatible] reason=portable action=none
- X64OnlyManaged.dll [fixable] reason=non_portable action=fix
why: This assembly uses a platform-specific header, but the managed code is portable and can be fixed.
Group: ref_assembly
- Reference.dll [unsafe] reason=ref_assembly action=blocked
why: Reference assembly, not a runtime assembly.
Trace transitive dependency closure:
pefix closure ./mods
Output:
pefix mods closure
Status: UNRESOLVED
Summary: 3 entry assemblies, 12 transitive references, 1 unresolved leaf, 0 cycles.
Action: Add the missing dependencies to the scanned directory or restore their packages.
Unresolved chains:
PluginA.dll
→ ModLib.dll v1.0.0.0 [resolved]
→ CoreUtils.dll v1.0.0.0 [resolved]
→ GameplayNet.dll v1.0.0.0 [MISSING]
Unity, BepInEx, Harmony, and other host- or loader-provided assemblies are filtered as provided leaves, so references such as UnityEngine.CoreModule do not appear as unresolved chains.
Check a BepInEx plugin directory:
pefix scan ./BepInEx/plugins
When scan sees [BepInPlugin] and [BepInDependency] metadata, it reports plugin GUIDs and hard dependencies. Missing hard dependencies appear as directory issues:
BepInEx deps (1):
- test.miss requires BepInEx plugin need.hard
Install the missing BepInEx plugin dependency into the scanned plugins directory.
BepInEx support is static. pefix does not run the game, simulate the chainloader, download packages, or install DLLs.
Add --json to any command for machine-readable output. JSON responses include schema_version; file results carry stable reason_code values, and directory issues carry stable issue codes. By default, scan --json exits 0 after writing a report even when directory integrity fails; use explicit fail gates for CI.
<details> <summary>Machine output details</summary>
Embedded file results that can also be produced on their own keep their own schema_version, such as scan.results[], snstrip.results[], fix.before, fix.after, and refusal before. Directory scan issue codes include missing_ref, dup_provider, asm_conflict, bep_missing, and bep_casing.
inspectexits0only forcompatibleby default; non-compatible inspection results exit1after printing the report. Other report commands use0for command execution success unless an explicit gate or refusal applies.- JSON
gatereports directory integrity throughgate.integrity,gate.issue_count, andgate.issue_codes. closure --jsonreportsentry_assemblies,unresolved_chains,cycle_chains,total_refs_walked, andframework_leaves;framework_leavescounts framework-provided leaves only, while host/loader-provided leaves are filtered but not counted there.closure --fail-on-unresolvedis the explicit process gate for unresolved leaves.inspectresults exposerepair_classandrepair_hint; scan issues addnext_steps,verify_command, andunverified_risks.repair_classseparates automatic mutation from diagnostics:auto_fixis thepefix fixstatus contract,guided_fixrequires explicit user-supplied mutation intent,assisted_fixemits evidence for external repair, anddiagnostic_onlynever mutates artifacts.- ReadyToRun, trimmable, and single-file bundle findings are
diagnostic_only;--apply --forcedoes not turn them into fixes. - Guided-fix JSON exposes
repair_class,unverified_risks, and exact mutationtargets. snstrip.outcomereports the command result. Single-file values includedry_run,patched,unsigned, anddep_refused; directory values includedry_run,patched,refused, andunchanged.- Directory
snstrip --jsonreports dependency files with rewrite targets at top level throughdeps_patchedanddeps; use top-leveldry_run/outcometo distinguish planned rewrites from applied rewrites. - For CI gates, use
--fail-on <status>for file status thresholds,--fail-on-conflictfor version conflicts, andclosure --fail-on-unresolvedfor unresolved chains.
</details>
Status legend
Every inspection produces one of five statuses:
| Status | Meaning |
|---|---|
| compatible | Already portable, no action needed. |
| fixable | Header can be rewritten by pefix fix. |
| cautioned | Requires reading repair_class: non_portable may be a guided header rewrite with --apply --force; ReadyToRun, trimming, and bundle findings are diagnostic-only. |
| unsafe | Refused. Rewriting would not produce a working assembly. |
| corrupt | Not a valid PE file or malformed beyond inspection. |
Each result also carries a stable reason_code printed in text and JSON output. Run pefix --help for the full option list and exit codes.
What it fixes
pefix fix rewrites non-portable pure-IL PE32+ managed headers and CorFlags so they match the layout the .NET loader expects across platforms.
The rewrite is byte-level. It does not touch metadata, embedded resources, or strong-name tokens, and it does not perform ildasm/ilasm round-tripping. After the rewrite, pefix re-inspects the file and validates the assembly manifest before reporting success.
Other mutation commands, such as snstrip, redir, and publicize, are Guided-fix paths only when they report mutation targets. No-op outcomes such as snstrip.outcome=unsigned are diagnostic-only. With fully specified CLI flags, mutating guided commands are Explicit Guided-fix paths, not part of the fixable status contract.
What it refuses
pefix fix will not perform the automatic header rewrite in any of the following cases:
| Refusal | Why |
|---|---|
| mixed-mode (C++/CLI) | Contains native code; needs ijwhost.dll or VC++ runtime. |
| reference assembly | Not a runtime artifact; cannot be executed. |
| satellite assembly | Localized resource container, not a code module. |
| multi-module assembly | Not supported on .NET Core or .NET 5+. |
| corrupt / non-PE | Not a parseable PE file. |
| strong-named | Cautioned. --apply --force accepts that the strong-name will break. |
| native dependencies | Cautioned. P/Invoke targets must be available on the host. |
Direction is one-way: pefix rewrites PE32+ managed headers to PE32 I386. Already compatible assemblies are left untouched.
Safety
Before an in-place rewrite with backups enabled, pefix copies the original to MyMod.dll.bak. Single-file writes are staged and verified before commit. Guided mutation batch writes use best-effort rollback and are not a true atomic transaction; pefix fix <dir> still processes files one at a time. If a .bak already exists, apply commands refuse rather than overwrite.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0 | 92 | 5/23/2026 |
| 0.2.0 | 122 | 5/11/2026 |
| 0.1.0 | 105 | 4/22/2026 |
| 0.0.1-stub | 99 | 4/22/2026 |