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.

example/main.dart

// ignore_for_file: avoid_print
import 'dart:io';
import 'dart:math';
import 'dart:typed_data';

import 'package:binary_patch/binary_patch.dart';

/// Comprehensive example demonstrating all binary_patch features.
///
/// Run with:
///   dart run example/main.dart
Future<void> main() async {
  print('');
  print('╔══════════════════════════════════════════════════════════════╗');
  print('║          binary_patch — Comprehensive Example                ║');
  print('╚══════════════════════════════════════════════════════════════╝');
  print('');

  // Create temporary directory for example files
  final tmpDir = Directory.systemTemp.createTempSync('binary_patch_example_');
  final oldPath   = '${tmpDir.path}/app_v1.bin';
  final newPath   = '${tmpDir.path}/app_v2.bin';
  final patchPath = '${tmpDir.path}/patch_v1_to_v2.bin';
  final outPath   = '${tmpDir.path}/app_v2_restored.bin';

  try {
    // ── 1. Generate example binary files ──────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 1: Generating test binary files  ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final rng     = Random(42);
    final oldData = _generateFakeExecutable(rng, sizeKB: 512);
    final newData = _simulateMinorUpdate(oldData, rng);

    await File(oldPath).writeAsBytes(oldData);
    await File(newPath).writeAsBytes(newData);

    print('  ✓ Old file : ${(oldData.length / 1024).toStringAsFixed(1)} KiB → $oldPath');
    print('  ✓ New file : ${(newData.length / 1024).toStringAsFixed(1)} KiB → $newPath');
    print('');

    // ── 2. Pre-flight analysis ─────────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 2: Pre-flight Analysis (dry run) ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final analysis = await BinaryPatch.analyze(
      oldFile: oldPath,
      newFile: newPath,
      options: PatchOptions.balanced(),
    );

    print('  Estimated patch size : ${analysis.estimatedPatchMB.toStringAsFixed(3)} MiB');
    print('  Estimated savings    : ${analysis.estimatedSavingsPercent.toStringAsFixed(1)}%');
    print('');

    // ── 3. Create patch (balanced options) ────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 3: Creating the Patch            ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final createResult = await BinaryPatch.create(
      oldFile: oldPath,
      newFile: newPath,
      outputPatch: patchPath,
      options: PatchOptions.balanced(),
      onProgress: (p) {
        final filled = (p * 30).round().clamp(0, 30);
        final bar    = '█' * filled + '░' * (30 - filled);
        stdout.write('\r  Progress: [$bar] ${(p * 100).toInt()}%');
      },
    );

    print('\n');
    print(createResult.summary);
    print('');

    // ── 4. Inspect metadata ────────────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 4: Inspecting Patch Metadata     ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final meta = await BinaryPatch.readMeta(patchPath);
    print('  Format version : ${meta.patchVersion}');
    print('  Compression    : ${meta.compressionType}');
    print('  Created at     : ${meta.createdAt}');
    print('  Old size       : ${meta.oldFileSize} bytes');
    print('  New size       : ${meta.newFileSize} bytes');
    print('  Old SHA-256    : ${meta.oldFileChecksum.substring(0, 24)}…');
    print('  New SHA-256    : ${meta.newFileChecksum.substring(0, 24)}…');
    print('');

    // ── 5. Verify patch ────────────────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 5: Verifying Patch Integrity     ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final isValid = await BinaryPatch.verify(patchPath);
    print(isValid
        ? '  ✓ Patch header is valid'
        : '  ✗ Patch is INVALID!');
    print('');

    // ── 6. Apply patch ─────────────────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 6: Applying the Patch            ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final applyResult = await BinaryPatch.apply(
      oldFile: oldPath,
      patchFile: patchPath,
      outputFile: outPath,
      verifyChecksum: true,
      onProgress: (p) {
        final filled = (p * 30).round().clamp(0, 30);
        final bar    = '█' * filled + '░' * (30 - filled);
        stdout.write('\r  Progress: [$bar] ${(p * 100).toInt()}%');
      },
    );

    print('\n');
    print('  ✓ Patch applied in ${applyResult.durationMs} ms');
    print('  ✓ Output: ${applyResult.outputPath}');
    print('');

    // ── 7. Verify round-trip ───────────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 7: Round-trip Verification       ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final restored = await File(outPath).readAsBytes();
    final match    = _bytesEqual(restored, newData);

    if (match) {
      print('  ✓ PERFECT MATCH — Restored file is bit-for-bit identical to original!');
    } else {
      print('  ✗ MISMATCH — Something went wrong!');
      exit(1);
    }
    print('');

    // ── 8. In-memory API demo ──────────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 8: In-Memory API (Flutter ready) ');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    final patchBytes = await BinaryPatch.createBytes(
      oldData: oldData,
      newData: newData,
      options: PatchOptions.fast(),
    );
    print('  ✓ In-memory patch created: ${patchBytes.length} bytes');

    final restoredBytes = await BinaryPatch.applyBytes(
      oldData: oldData,
      patchData: patchBytes,
      verifyChecksum: true,
    );
    print('  ✓ In-memory patch applied : ${restoredBytes.length} bytes');
    print('  ✓ Match: ${_bytesEqual(restoredBytes, newData)}');
    print('');

    // ── 9. Compression comparison ──────────────────────────────────────────
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
    print(' STEP 9: Compression Algorithm Comparison');
    print('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');

    for (final type in [
      CompressionType.none,
      CompressionType.bzip2,
      CompressionType.zstd,
      CompressionType.lzma,
    ]) {
      final sw    = Stopwatch()..start();
      final bytes = await BinaryPatch.createBytes(
        oldData: oldData,
        newData: newData,
        options: PatchOptions(compression: type, compressionLevel: 6),
      );
      sw.stop();
      final savings = (1.0 - bytes.length / newData.length) * 100;
      print('  ${type.name.padRight(6)}: '
          '${(bytes.length / 1024).toStringAsFixed(1).padLeft(8)} KiB  '
          'savings=${savings.toStringAsFixed(1).padLeft(5)}%  '
          '${sw.elapsedMilliseconds}ms');
    }
    print('');

    // ── Summary ────────────────────────────────────────────────────────────
    print('╔══════════════════════════════════════════════════════════════╗');
    print('║  All steps completed successfully!  🎉                      ║');
    print('╚══════════════════════════════════════════════════════════════╝');
    print('');
  } finally {
    await tmpDir.delete(recursive: true);
  }
}

// ─────────────────────────────────────────────────────────────────────────────
// Test data generators
// ─────────────────────────────────────────────────────────────────────────────

/// Generates a fake binary "executable" with realistic byte distribution.
Uint8List _generateFakeExecutable(Random rng, {required int sizeKB}) {
  final size   = sizeKB * 1024;
  final data   = Uint8List(size);
  // Header
  data[0] = 0x4D; data[1] = 0x5A; // MZ
  data[2] = 0x50; data[3] = 0x45; // PE
  // Fill with pseudo-code patterns (lots of zero bytes + structured data)
  for (var i = 4; i < size; i++) {
    // Simulate compiled code — mostly zeros and small values
    final r = rng.nextInt(100);
    data[i] = r < 40 ? 0 : (r < 70 ? rng.nextInt(16) : rng.nextInt(256));
  }
  return data;
}

/// Simulates a minor code update: changes ~2% of bytes.
Uint8List _simulateMinorUpdate(Uint8List original, Random rng) {
  final data    = Uint8List.fromList(original);
  final changes = (original.length * 0.02).toInt();
  // Simulate function body change — consecutive block
  final start = rng.nextInt(original.length ~/ 2);
  for (var i = 0; i < changes; i++) {
    data[start + i] = rng.nextInt(256);
  }
  // Simulate address relocation — scattered +4 offsets
  for (var i = 0; i < 50; i++) {
    final pos = rng.nextInt(original.length - 4);
    final val = (data[pos] + 4) & 0xFF;
    data[pos] = val;
  }
  return data;
}

bool _bytesEqual(Uint8List a, Uint8List b) {
  if (a.length != b.length) return false;
  for (var i = 0; i < a.length; i++) {
    if (a[i] != b[i]) return false;
  }
  return true;
}
0
likes
130
points
56
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