pefix 0.3.0

dotnet tool install --global pefix --version 0.3.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local pefix --version 0.3.0
                    
This package contains a .NET tool you can call from the shell/command line.
#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.

CI NuGet License: MIT

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.

  • inspect exits 0 only for compatible by default; non-compatible inspection results exit 1 after printing the report. Other report commands use 0 for command execution success unless an explicit gate or refusal applies.
  • JSON gate reports directory integrity through gate.integrity, gate.issue_count, and gate.issue_codes.
  • closure --json reports entry_assemblies, unresolved_chains, cycle_chains, total_refs_walked, and framework_leaves; framework_leaves counts framework-provided leaves only, while host/loader-provided leaves are filtered but not counted there. closure --fail-on-unresolved is the explicit process gate for unresolved leaves.
  • inspect results expose repair_class and repair_hint; scan issues add next_steps, verify_command, and unverified_risks.
  • repair_class separates automatic mutation from diagnostics: auto_fix is the pefix fix status contract, guided_fix requires explicit user-supplied mutation intent, assisted_fix emits evidence for external repair, and diagnostic_only never mutates artifacts.
  • ReadyToRun, trimmable, and single-file bundle findings are diagnostic_only; --apply --force does not turn them into fixes.
  • Guided-fix JSON exposes repair_class, unverified_risks, and exact mutation targets.
  • snstrip.outcome reports the command result. Single-file values include dry_run, patched, unsigned, and dep_refused; directory values include dry_run, patched, refused, and unchanged.
  • Directory snstrip --json reports dependency files with rewrite targets at top level through deps_patched and deps; use top-level dry_run / outcome to distinguish planned rewrites from applied rewrites.
  • For CI gates, use --fail-on <status> for file status thresholds, --fail-on-conflict for version conflicts, and closure --fail-on-unresolved for 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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