NickNa.PEPacker 1.0.6

dotnet add package NickNa.PEPacker --version 1.0.6
                    
NuGet\Install-Package NickNa.PEPacker -Version 1.0.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="NickNa.PEPacker" Version="1.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="NickNa.PEPacker" Version="1.0.6" />
                    
Directory.Packages.props
<PackageReference Include="NickNa.PEPacker" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add NickNa.PEPacker --version 1.0.6
                    
#r "nuget: NickNa.PEPacker, 1.0.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package NickNa.PEPacker@1.0.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=NickNa.PEPacker&version=1.0.6
                    
Install as a Cake Addin
#tool nuget:?package=NickNa.PEPacker&version=1.0.6
                    
Install as a Cake Tool

PE Packer

NuGet

A .NET library for post-processing compiled assemblies: rewriting PE metadata and creating single-file executables.

Why does this exist?

If you use System.Reflection.Emit to generate .NET assemblies at runtime (via TypeBuilder, PersistedAssemblyBuilder, etc.), the types you emit end up referencing System.Private.CoreLib — the internal runtime implementation assembly. That's fine for running locally, but those assemblies won't load correctly on other machines or runtimes because System.Private.CoreLib is an implementation detail, not a stable contract.

The fix is to rewrite those references to point at the official SDK reference assemblies (System.Runtime, System.Collections, System.Threading, etc.) — the public surface that .NET guarantees across versions. That's what AssemblyReferenceRewriter does. It reads a compiled PE, rebuilds the entire metadata image with corrected assembly references, patches all IL tokens, and writes a new valid PE. No decompilation, no re-compilation — just metadata surgery.

Why not just use MetadataLoadContext types directly? Because MetadataLoadContext types are inspection-only. You can't pass them to TypeBuilder.DefineType() for interface implementation or base classes. The workaround is to compile against runtime types (which TypeBuilder accepts), then post-process the output to fix the references.

The library also includes single-file bundling — the ability to package a managed DLL into a self-contained executable using the .NET apphost, either through the official SDK bundler (Microsoft.NET.HostModel) or a built-in byte patcher that produces the same bundle format without taking a dependency on it.

Features

  • Assembly Reference Rewriting — Rewrites System.Private.CoreLib references to SDK reference assemblies (System.Runtime, System.Collections, etc.). Handles generics, nested types, method specs, properties, events, P/Invoke, custom attributes and IL token patching. Refuses input it cannot reproduce faithfully rather than emitting a lossy assembly — see Scope and limitations.
  • Single-File Bundling — Creates single-file .NET executables. Automatically selects the SDK bundler when available, falls back to the built-in bundler. Windows and Linux apphost templates are embedded, so built-in bundling does not require an installed SDK.
  • App Host Generation — Generates standalone executable wrappers around .NET DLLs with proper runtime configuration.

Requirements

  • .NET 10 — the package targets net10.0.
  • The built-in bundler needs no installed SDK for win-x64, win-x86, win-arm64, linux-x64, linux-arm, or linux-arm64; those apphost templates ship in the package. The SDK bundler requires an installed .NET SDK. Both output a framework-dependent executable, so the target machine still needs a compatible .NET runtime.

Installation

dotnet add package NickNa.PEPacker

Usage

Assembly Reference Rewriting

using PEPacker;

// sourceAssembly: a compiled DLL with System.Private.CoreLib references
// refAssemblyPath: path to SDK ref assemblies, e.g.:
//   C:\Program Files\dotnet\packs\Microsoft.NETCore.App.Ref\10.0.0\ref\net10.0
using var sourceStream = File.OpenRead("compiled.dll");
using var rewriter = new AssemblyReferenceRewriter(sourceStream, refAssemblyPath);
rewriter.Rewrite();

using var output = File.Create("rewritten.dll");
rewriter.Save(output);

Rewrite() throws PEPackerException if the source uses metadata the rewriter does not reproduce. The message names each offending construct and how many rows it has, so the failure points at the feature rather than a table number.

Single-File Bundling

using PEPacker;
using PEPacker.Bundling;

var result = AppHostGenerator.CreateSingleFileExecutable(
    managedDllPath: "myapp.dll",
    outputExePath: "myapp.exe",
    assemblyName: "myapp"
);

Console.WriteLine($"Bundled with {result.TechniqueDescription}");

You can also force a specific bundler:

// Force the built-in bundler (does not load Microsoft.NET.HostModel)
var bundler = BundlerFactory.GetBundler(BundlerMode.BuiltIn);
var result = bundler.CreateSingleFileExecutable("myapp.dll", "myapp.exe", "myapp");

BundlerMode.Sdk requires Microsoft.NET.HostModel.dll and throws if it isn't present; BundlerMode.Auto (the default) tries the SDK bundler and falls back to the built-in one. AppHostGenerator.GetPreferredTechnique() reports which would be chosen without bundling anything.

Native AOT applications should compile out the SDK bundler, which relies on dynamic assembly loading and cannot run there. Add this application-level item to the AOT project's .csproj:

<ItemGroup>
  <RuntimeHostConfigurationOption Include="PEPacker.EnableSdkBundler"
                                  Value="false"
                                  Trim="true" />
</ItemGroup>

The switch defaults to enabled, so managed applications keep SDK detection and BundlerMode.Auto behavior unchanged. Setting it to false makes an explicit BundlerMode.Sdk request fail with a diagnostic instead of silently changing modes.

For explicit target selection, multiple assemblies, or a private apphost template, use BundleRequest:

var result = AppHostGenerator.CreateSingleFileExecutable(
    new BundleRequest
    {
        EntryAssemblyPath = "myapp.dll",
        OutputPath = "myapp",
        AssemblyName = "myapp",
        AdditionalAssemblies = ["MyRuntime.dll"],
        RuntimeIdentifier = "linux-arm64",
        FrameworkVersion = new Version(10, 0)
    },
    BundlerMode.BuiltIn);

Scope and limitations

Rewriting

The rewriter targets assemblies emitted through PersistedAssemblyBuilder. It is not a general-purpose PE round-tripper, and it is deliberately fail-closed: every metadata table it reproduces is on an explicit allow-list, and a source using anything outside that list is rejected instead of being silently stripped. Rewriting refuses input that carries:

  • embedded or linked managed resources, exported/forwarded types, or multi-file assembly manifests
  • declarative security attributes
  • edit-and-continue deltas, or uncompressed metadata (whose indirection tables break row-order assumptions)
  • native Win32 resources

The rewritten image also drops the strong-name signature — output is never re-signed, so the flag is cleared rather than left claiming a signature that isn't there. Re-sign afterwards if you need one.

Bundling

Both bundlers embed the main assembly, any AdditionalAssemblies, and a generated runtimeconfig.json. A .deps.json is not required for these bundled application assemblies. The result remains framework-dependent rather than self-contained: the target machine needs a compatible .NET runtime. The built-in bundler deliberately refuses macOS because it does not yet perform the required Mach-O adjustment and ad-hoc signing.

Development

dotnet build PE-Packer.slnx --configuration Release
dotnet test PE-Packer.slnx --configuration Release

The rewriter is checked two independent ways. A metadata differ compares the assembly table-by-table and row-by-row before and after, asserting nothing changed beyond the retargeting the rewrite exists to perform; ILVerify then verifies the output is well-formed by an implementation that shares no code with the rewriter. Both matter — an image can round-trip byte-identical IL and still be invalid, and it can load fine while having quietly lost a table.

License

See LICENSE for details.

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.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories (1)

Showing the top 1 popular GitHub repositories that depend on NickNa.PEPacker:

Repository Stars
nickna/SharpTS
Compile TypeScript to .NET IL; Use .NET classes in Typescript
Version Downloads Last Updated
1.0.6 0 7/30/2026
1.0.5 0 7/30/2026
1.0.4 0 7/30/2026
1.0.3 0 7/29/2026
1.0.2 1,095 6/20/2026
1.0.1 2,588 4/5/2026
0.2.0 104 4/5/2026