Patchy 1.1.0

dotnet add package Patchy --version 1.1.0
                    
NuGet\Install-Package Patchy -Version 1.1.0
                    
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="Patchy" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Patchy" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Patchy" />
                    
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 Patchy --version 1.1.0
                    
#r "nuget: Patchy, 1.1.0"
                    
#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 Patchy@1.1.0
                    
#: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=Patchy&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Patchy&version=1.1.0
                    
Install as a Cake Tool

Patchy

NuGet Version License: MIT Stars Issues

A secure, ECDsa-based self-updater library for .NET applications with support for file-level binary patching.

Overview

Patchy provides a secure way to deliver updates to your users. It ensures that update information is authentic (signed by the developer) and that update files are integral (not corrupted or tampered with).

The library supports two update strategies:

  • Full package updates: Replace the entire application with a new version
  • File-level delta updates: Patch only modified files using bsdiff binary diffing

Features

  • ECDsa digital signatures for manifest verification
  • SHA256 hash verification for all files
  • Per-file binary patching with bsdiff
  • Automatic detection of added, modified, and removed files
  • Command-line tool for release preparation
  • Cross-platform .NET 8.0 support

Documentation

Components

Component Description
Patchy NuGet library for your application
Patchy.Tool CLI utility for creating and signing releases

Installation

dotnet add package Patchy

Quick Start

1. Generate Keys (One-Time Setup)

Patchy.Tool.exe generate-keys

This creates:

  • privateKey.pem - Keep this secret, use for signing
  • publicKey.pem - Embed in your application for verification

2. Create an Update Package

Compare two versions of your application and generate a delta update:

Patchy.Tool.exe create-update-package <old_dir> <new_dir> <output_dir> <private_key> [config.json]

Example:

Patchy.Tool.exe create-update-package v1.0 v1.1 release privateKey.pem config.json

3. Apply Updates in Your Application

const string PublicKey = @"-----BEGIN PUBLIC KEY-----
...your public key...
-----END PUBLIC KEY-----";

var updater = new PatchyUpdater(manifestUrl, PublicKey, () => Task.FromResult(true));

// Download and apply file-level update package
var manifest = await updater.ApplyUpdatePackageAsync("update.pkg", targetDirectory);
Console.WriteLine($"Updated to version {manifest.Version}");

Update Package Structure

The create-update-package command generates a signed ZIP archive:

update.pkg
├── meta.json           # Signed manifest with file actions
├── diffs/              # Binary patches for modified files
│   ├── file1.dll.patch
│   └── file2.exe.patch
└── add/                # New files (full content)
    └── newfile.dll

Manifest Format (meta.json)

{
  "VersionId": 1733421600,
  "Version": "1.1.0",
  "FromVersionId": 1733335200,
  "ReleaseName": "Update 1.1.0",
  "Changes": ["Bug fixes", "New feature"],
  "Files": [
    {
      "Path": "lib/core.dll",
      "Action": "modified",
      "PatchFile": "diffs/lib_core.dll.patch",
      "SourceHash": "abc123...",
      "TargetHash": "def456..."
    },
    {
      "Path": "plugins/new.dll",
      "Action": "added",
      "AddFile": "add/plugins_new.dll",
      "TargetHash": "789abc..."
    },
    {
      "Path": "old/legacy.dll",
      "Action": "removed"
    }
  ],
  "Signature": "base64signature..."
}

File Actions

Action Description
modified File exists in both versions with different content. A bsdiff patch is created.
added File only exists in the new version. Full file is included.
removed File only exists in the old version. Will be deleted on update.

Configuration File

Optional configuration for create-update-package:

{
  "NewVersionId": 1733421600,
  "Version": "1.1.0",
  "FromVersionId": 1733335200,
  "ReleaseName": "Update 1.1.0",
  "Changes": [
    "Fixed critical bug",
    "Added new feature"
  ],
  "RestartRequired": true,
  "Critical": false,
  "InstallerFile": "Dist/Setup.exe",
  "InstallerArguments": "/silent",
  "FullPackageFile": "Full.zip"
}

API Reference

PatchyUpdater

public class PatchyUpdater
{
    // Constructor
    public PatchyUpdater(string infoUrl, string publicKeyPem, Func<Task<bool>> confirmFullDownloadCallback);

    // Apply a file-level update package
    public Task<UpdatePackageManifest> ApplyUpdatePackageAsync(string packagePath, string targetDirectory);

    // Apply a single bsdiff patch
    public Task ApplyPatchAsync(string oldFilePath, string patchFilePath, string newFilePath);
}

UpdatePackageManifest

public class UpdatePackageManifest
{
    public long VersionId { get; set; }
    public string Version { get; set; }
    public long FromVersionId { get; set; }
    public string ReleaseName { get; set; }
    public List<string> Changes { get; set; }
    
    // New Flags
    public bool RestartRequired { get; set; }     // Should app restart after update?
    public bool Critical { get; set; }            // Is this a security/critical update?

    // Optional Fallbacks
    public string FallbackInstallerFile { get; set; }      // Path to embedded installer
    public string FallbackInstallerArguments { get; set; } // Arguments for installer
    public string FullPackageFile { get; set; }            // Name of standalone full zip
    
    public List<FileAction> Files { get; set; }
    public string Signature { get; set; }
}

public class FileAction
{
    public string Path { get; set; }        // Relative file path
    public string Action { get; set; }      // "modified", "added", "removed"
    public string PatchFile { get; set; }   // Path to patch file (modified)
    public string AddFile { get; set; }     // Path to new file (added)
    public string PackageFileHash { get; set; } // Hash of the file in the package
    public string SourceHash { get; set; }  // SHA256 of original file
    public string TargetHash { get; set; }  // SHA256 of target file
}

CLI Commands

generate-keys

Generate ECDsa key pair for signing.

Patchy.Tool.exe generate-keys

create-update-package

Create a file-level update package with per-file patches.

Patchy.Tool.exe create-update-package <old_dir> <new_dir> <output_dir> <private_key> [config.json]

create-patch

Create a single bsdiff patch between two files.

Patchy.Tool.exe create-patch <old_file> <new_file> <patch_output>

apply-patch

Apply a bsdiff patch to recreate the new file.

Patchy.Tool.exe apply-patch <old_file> <patch_file> <new_file_output>

hash

Calculate SHA256 hash of a file.

Patchy.Tool.exe hash <file_path>

Security

  • All manifests are signed with ECDsa (NIST P-256 curve)
  • File integrity is verified with SHA256 hashes
  • Source file hash is verified before applying patches
  • Target file hash is verified after applying patches
  • Signature verification fails if manifest is tampered

Dependencies

License

This project is licensed under the MIT License. See the LICENSE file for details.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 98 12/5/2025
1.0.0 101 12/5/2025
0.2.4 180 11/5/2025
0.2.3 176 11/5/2025
0.2.2 226 9/21/2025
0.2.1 202 9/21/2025
0.2.0 192 9/21/2025
0.1.0 253 9/15/2025