ROM Patcher
A Dart library for applying ROM patch files in IPS, BPS, and UPS formats. This library provides functionality similar to the Floating IPS (FLIPS) program and the Rom Patcher JS library, allowing you to apply patches to ROM files in memory without requiring file system access.
Features
- Multiple Patch Formats: Support for IPS, BPS, and UPS patch formats
- Auto-Detection: Automatically detects patch format from file signature
- Memory-Based: Works with in-memory
Uint8List
objects, no file system required - Validation: Comprehensive checksum and format validation
- Performance: Optimized for large ROMs (up to hundreds of MB)
- Error Handling: Descriptive error messages with specific error codes
Supported Formats
IPS (International Patching System)
- Simple byte-level patching
- Run Length Encoding (RLE) support
- ROM size extension support
- File signature: "PATCH"
BPS (Binary Patch System)
- Advanced patching with multiple operation types
- CRC32 checksum validation
- Variable-length encoding for efficient storage
- File signature: "BPS1"
UPS (Unified Patch System)
- XOR-based patching
- CRC32 checksum validation
- Variable-length encoding
- File signature: "UPS1"
Installation
Add this to your package's pubspec.yaml
file:
dependencies:
rom_patcher: ^0.1.0
Then run:
dart pub get
Usage
Basic Usage
import 'package:rom_patcher/rom_patcher.dart';
// Load your ROM and patch data
final romData = await File('original.sfc').readAsBytes();
final patchData = await File('patch.ips').readAsBytes();
// Apply patch with auto-detection
final patchedRom = applyPatch(romData, patchData);
// Save the patched ROM
await File('patched.sfc').writeAsBytes(patchedRom);
Format-Specific Usage
import 'package:rom_patcher/rom_patcher.dart';
// Apply IPS patch
final ipsPatchedRom = applyIps(romData, ipsPatchData);
// Apply BPS patch
final bpsPatchedRom = applyBps(romData, bpsPatchData);
// Apply UPS patch
final upsPatchedRom = applyUps(romData, upsPatchData);
Manual Format Detection
import 'package:rom_patcher/rom_patcher.dart';
// Detect patch format
final format = PatchUtils.detectFormat(patchData);
print('Detected format: ${format.name}');
// Validate patch header
final isValid = PatchUtils.validateHeader(patchData, PatchFormat.ips);
if (isValid) {
final patchedRom = applyPatch(romData, patchData);
}
Error Handling
import 'package:rom_patcher/rom_patcher.dart';
try {
final patchedRom = applyPatch(romData, patchData);
// Success!
} on PatchException catch (e) {
switch (e.code) {
case PatchErrorCode.invalidHeader:
print('Invalid patch header: ${e.message}');
break;
case PatchErrorCode.checksumMismatch:
print('Checksum validation failed: ${e.message}');
break;
case PatchErrorCode.fileTooSmall:
print('Patch file is too small: ${e.message}');
break;
default:
print('Patch error: ${e.message}');
}
}
API Reference
Main Functions
applyPatch(Uint8List rom, Uint8List patch, {PatchFormat? format})
Applies a patch to a ROM with optional format specification.
- rom: The original ROM data
- patch: The patch data
- format: Optional patch format. If null, format will be auto-detected
- Returns: The patched ROM data
- Throws:
PatchException
if the patch is invalid or application fails
applyIps(Uint8List rom, Uint8List patch)
Applies an IPS patch to a ROM.
applyBps(Uint8List rom, Uint8List patch)
Applies a BPS patch to a ROM.
applyUps(Uint8List rom, Uint8List patch)
Applies a UPS patch to a ROM.
Utility Functions
PatchUtils.detectFormat(Uint8List patch)
Detects the format of a patch file from its signature.
PatchUtils.validateHeader(Uint8List patch, PatchFormat format)
Validates that a patch file has a valid header for the specified format.
PatchUtils.getMinimumSize(PatchFormat format)
Gets the minimum size required for a valid patch file of the specified format.
Exception Handling
PatchException
Exception thrown when patch operations fail.
Properties:
code
: The error code that identifies the type of errormessage
: A human-readable error messagedetails
: Optional additional context about the error
PatchErrorCode
Enumeration of error codes:
invalidHeader
: The patch file header is invalid or unrecognizedfileTooSmall
: The patch file is too small to contain valid datachecksumMismatch
: A checksum or hash validation failedunsupportedFormat
: The patch format is not supportedromTooSmall
: The ROM file is too small for the patchcorruptedPatch
: The patch file is corrupted or contains invalid dataunexpectedError
: An unexpected error occurred during patch applicationtruncatedPatch
: The patch file is truncated or incompleteinvalidOffset
: The patch contains invalid offset or size dataformatDetectionFailed
: The patch format could not be auto-detected
Examples
Flutter App Example
import 'package:flutter/material.dart';
import 'package:rom_patcher/rom_patcher.dart';
class RomPatcherApp extends StatefulWidget {
@override
_RomPatcherAppState createState() => _RomPatcherAppState();
}
class _RomPatcherAppState extends State<RomPatcherApp> {
Uint8List? romData;
Uint8List? patchData;
Uint8List? patchedRom;
String? errorMessage;
Future<void> _loadRom() async {
// Load ROM file
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['sfc', 'smc', 'nes', 'gb', 'gba'],
);
if (result != null) {
final file = File(result.files.single.path!);
romData = await file.readAsBytes();
}
}
Future<void> _loadPatch() async {
// Load patch file
final result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['ips', 'bps', 'ups'],
);
if (result != null) {
final file = File(result.files.single.path!);
patchData = await file.readAsBytes();
}
}
void _applyPatch() {
if (romData == null || patchData == null) return;
try {
setState(() {
errorMessage = null;
patchedRom = applyPatch(romData!, patchData!);
});
} on PatchException catch (e) {
setState(() {
errorMessage = 'Patch failed: ${e.message}';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ROM Patcher')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
ElevatedButton(
onPressed: _loadRom,
child: Text('Load ROM'),
),
ElevatedButton(
onPressed: _loadPatch,
child: Text('Load Patch'),
),
ElevatedButton(
onPressed: _applyPatch,
child: Text('Apply Patch'),
),
if (errorMessage != null)
Text(errorMessage!, style: TextStyle(color: Colors.red)),
if (patchedRom != null)
Text('Patch applied successfully!'),
],
),
),
);
}
}
Command Line Tool Example
import 'dart:io';
import 'package:rom_patcher/rom_patcher.dart';
void main(List<String> args) async {
if (args.length != 3) {
print('Usage: dart run patcher.dart <rom_file> <patch_file> <output_file>');
exit(1);
}
final romFile = File(args[0]);
final patchFile = File(args[1]);
final outputFile = File(args[2]);
try {
// Load files
final romData = await romFile.readAsBytes();
final patchData = await patchFile.readAsBytes();
// Detect format
final format = PatchUtils.detectFormat(patchData);
print('Detected patch format: ${format.name}');
// Apply patch
final patchedRom = applyPatch(romData, patchData);
// Save result
await outputFile.writeAsBytes(patchedRom);
print('Patch applied successfully!');
print('Output saved to: ${outputFile.path}');
} on PatchException catch (e) {
print('Error: ${e.message}');
exit(1);
} on FileSystemException catch (e) {
print('File error: ${e.message}');
exit(1);
}
}
Performance Considerations
- The library is optimized for large ROMs and avoids unnecessary memory copying
- IPS patches are the fastest to apply due to their simple format
- BPS and UPS patches include checksum validation which adds some overhead
- Memory usage is proportional to the target ROM size
Limitations
- BPS and UPS patches require properly formatted patch files with valid checksums
- The current implementation includes basic CRC32 validation for BPS/UPS formats
- Some advanced BPS/UPS features may not be fully implemented
Contributing
We welcome contributions to ROM Patcher! Please see our Contributing Guidelines for detailed information on how to get started.
Quick Start
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Run tests (
dart test
) - Format code (
dart format .
) - Commit your changes (
git commit -m 'feat: add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Development
For detailed development information, see our Development Guide.
Code of Conduct
This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.
Security
Please report security vulnerabilities via email to siliconfish42@protonmail.com rather than through public GitHub issues. See our Security Policy for more details.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Inspired by the Floating IPS (FLIPS) program
- Based on the Rom Patcher JS library
- Thanks to the ROM hacking community for the patch format specifications
Libraries
- rom_patcher
- A Dart library for applying ROM patch files in IPS, BPS, and UPS formats.