binary_patch 1.0.0 copy "binary_patch: ^1.0.0" to clipboard
binary_patch: ^1.0.0 copied to clipboard

binary_patch is a Dart/Flutter library for creating and applying efficient binary diffs using the bsdiff algorithm, reducing update sizes with support for zstd, bzip2, and lzma compression.

binary_patch πŸ”§ #

pub version Dart SDK License: BSD-3 CI

Update your app with 1 MiB instead of 100 MiB. A high-performance, Pure Dart binary diff/patch library based on the bsdiff algorithm β€” the same algorithm used by macOS, Chrome, and major app stores for delta updates.


The Problem #

Every time you ship a new version of your app β€” even a one-line bug fix β€” your users must download the entire binary again. For a 100 MiB desktop app, that wastes bandwidth, time, and hosting cost on every update.

The Solution #

import 'package:binary_patch/binary_patch.dart';

// ── Server side (CI/CD) ──────────────────────────────────────────────────────
final result = await BinaryPatch.create(
  oldFile:     'app_v1.exe',     // 100 MiB
  newFile:     'app_v2.exe',     // 100 MiB
  outputPatch: 'patch.bin',      // ~1 MiB  ← users download only this!
  options: PatchOptions.balanced(),
);
print('Savings: ${result.savingsPercent.toStringAsFixed(0)}%'); // "Savings: 99%"

// ── Client side (user's device) ──────────────────────────────────────────────
await BinaryPatch.apply(
  oldFile:    'app_v1.exe',   // already on device
  patchFile:  'patch.bin',    // downloaded
  outputFile: 'app_v2.exe',   // reconstructed β€” bit-perfect match!
);

Features #

Feature Description
⚑ bsdiff algorithm Best-in-class binary differencing, used by macOS & Chrome
πŸ—œοΈ 3 compression modes zstd (default), bzip2 (classic compat), lzma (max ratio)
πŸ” SHA-256 verification Checksums embedded for old file, new file, and patch
🧡 Non-blocking All CPU work runs in Dart Isolates β€” UI thread never blocked
πŸ“± Flutter-ready In-memory API (createBytes / applyBytes) for app storage
πŸ–₯️ CLI included dart run binary_patch create/apply/verify/analyze/info
🎯 Pure Dart No native dependencies, no FFI required
βœ… Typed errors ChecksumException, PatchFormatException, PatchException

Installation #

# pubspec.yaml
dependencies:
  binary_patch: ^1.0.0
dart pub get

Quick Start #

Create a patch (server/CI side) #

final result = await BinaryPatch.create(
  oldFile:     'dist/app_v1.exe',
  newFile:     'dist/app_v2.exe',
  outputPatch: 'dist/patch_v1_to_v2.bin',
  options: PatchOptions.balanced(),   // zstd level 19
  onProgress: (p) => print('${(p * 100).toInt()}%'),
);
print(result.summary);

Apply a patch (client device) #

await BinaryPatch.apply(
  oldFile:    'app_v1.exe',
  patchFile:  'patch_v1_to_v2.bin',
  outputFile: 'app_v2.exe',
  // verifyChecksum: true (default) β€” verifies SHA-256 before & after
);

Estimate savings before creating #

final analysis = await BinaryPatch.analyze(
  oldFile: 'app_v1.exe',
  newFile: 'app_v2.exe',
);
print('Patch will be ~${analysis.estimatedPatchMB.toStringAsFixed(2)} MiB '
      '(saves ${analysis.estimatedSavingsPercent.toStringAsFixed(1)}%)');

In-memory API (Flutter) #

// Read files however you like (app documents, asset bundles…)
final oldBytes = await File(oldPath).readAsBytes();
final newBytes = await File(newPath).readAsBytes();

// Create patch in memory β€” no disk I/O
final patchBytes = await BinaryPatch.createBytes(
  oldData: oldBytes,
  newData: newBytes,
);

// Apply patch in memory
final restoredBytes = await BinaryPatch.applyBytes(
  oldData: oldBytes,
  patchData: patchBytes,
);
assert(restoredBytes.length == newBytes.length);

CLI Usage #

# Generate patch (typically run on CI)
dart run binary_patch create app_v1.exe app_v2.exe patch.bin

# Apply patch on client
dart run binary_patch apply app_v1.exe patch.bin app_v2.exe

# Fast header validation (no decompression)
dart run binary_patch verify patch.bin

# Dry-run estimate without writing a file
dart run binary_patch analyze app_v1.exe app_v2.exe --compression=lzma

# Read embedded metadata
dart run binary_patch info patch.bin

Compression Guide #

Option Algorithm Speed Ratio Use when
PatchOptions.fast() zstd L3 β˜…β˜…β˜…β˜…β˜… β˜…β˜…β˜…β˜† CI speed matters
PatchOptions.balanced() zstd L19 β˜…β˜…β˜…β˜…β˜† β˜…β˜…β˜…β˜…β˜† Default β€” recommended
PatchOptions.maximum() lzma L9 β˜…β˜…β˜†β˜†β˜† β˜…β˜…β˜…β˜…β˜… Minimise download size
PatchOptions.uncompressed() none β˜…β˜…β˜…β˜…β˜… β˜…β˜†β˜†β˜†β˜† Debug / inspection

Custom:

PatchOptions(
  compression: CompressionType.bzip2,
  compressionLevel: 6,
  label: '1.0.0 β†’ 2.0.0',
)

Error Handling #

try {
  await BinaryPatch.apply(/*…*/);
} on ChecksumException catch (e) {
  // Wrong source file (different version than the patch expects)
  print('Expected: ${e.expected}');
  print('Actual:   ${e.actual}');
} on PatchFormatException catch (e) {
  // Corrupt or incompatible patch file
  print('Bad patch at byte ${e.byteOffset}: ${e.message}');
} on PatchException catch (e) {
  // Any other binary_patch error
  print(e.message);
}

Patch File Format #

Offset   Size  Field
──────   ────  ───────────────────────────────────────────
0        6     Magic: ASCII "BPATCH"
6        1     Format version: 0x01
7        1     Compression type (0=zstd, 1=bzip2, 2=lzma, 3=none)
8        4     Metadata JSON length (uint32 LE)
12       N     Metadata JSON (UTF-8, contains SHA-256, sizes, timestamp…)
12+N     8     Control section length (int64 LE)
20+N     8     Diff section length (int64 LE)
28+N     8     Extra section length (int64 LE)
36+N     *     Control data  β€” compressed ControlBlock triples (24 B each)
*        *     Diff data     β€” compressed byte-difference stream
*        *     Extra data    β€” compressed verbatim-insert stream

Algorithm Overview #

The bsdiff algorithm (Percival, 2003) achieves its remarkable compression ratio because recompiling even a trivial change causes many address relocations throughout the binary. A naΓ―ve differ treats each relocated address as unique new data β€” bsdiff instead subtracts old bytes from new bytes, turning +4 address offsets into a stream of 0x04 values that compress to nothing.

Three phases:

1. Suffix Array  β†’  O(n logΒ² n) build from old file
                    O(log n) best-match search per position in new file

2. Match & Delta β†’  diff[i] = (new[pos+i] βˆ’ old[match+i]) mod 256
                    Bytes with no match β†’ extra stream

3. Compress      β†’  zstd / bzip2 / lzma applied independently to
                    control stream, diff stream, extra stream

Apply (bspatch) is O(m):

for each ControlBlock(copyLen, extraLen, seekOffset):
  newData[newPos..+copyLen] = oldData[oldPos..+copyLen] + diff[..+copyLen]
  newData[newPos+copyLen..+extraLen] = extra[extraPos..+extraLen]
  oldPos += seekOffset

Performance #

File size ~2% change zstd L19 patch Create time Apply time
16 KiB 320 B ~2 KiB <50 ms <5 ms
1 MiB ~20 KiB ~10–30 KiB ~200 ms ~20 ms
10 MiB ~200 KiB ~50–200 KiB ~2 s ~150 ms
100 MiB ~2 MiB ~0.5–2 MiB ~20–60 s ~1.5 s

Apply is always much faster than create β€” safe for low-power devices.


Comparison with Alternatives #

Feature binary_patch diff_match_patch bsdiff (C)
Target Binary files Text only Binary
Language Pure Dart Dart C
Flutter βœ… βœ… ❌
Isolates βœ… ❌ ❌
Multi-algo compression βœ… ❌ bzip2 only
Built-in CLI βœ… ❌ βœ…
SHA-256 checksums βœ… ❌ ❌
Savings (typical) ~95% text only ~95%

πŸ“œ License #

MIT β€” see LICENSE


Contributing #

Issues and pull requests are welcome at github.com/Brah-Timo/binary_patch.

Please run dart test and dart analyze before submitting.

0
likes
130
points
54
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

binary_patch is a Dart/Flutter library for creating and applying efficient binary diffs using the bsdiff algorithm, reducing update sizes with support for zstd, bzip2, and lzma compression.

Repository (GitHub)
View/report issues

Topics

#binary-diff #patching #delta-compression #update-optimization #file-comparison

License

MIT (license)

Dependencies

archive, crypto, meta, path, typed_data

More

Packages that depend on binary_patch