rompatcher

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 3, 2026 License: MIT Imports: 23 Imported by: 0

README

RomPatcher.go

A native, lightweight Go rewrite of RomPatcher.js by Marc Robledo, providing the patching engine and CLI without the web frontend. The library requires Go 1.21 or newer; release binaries require no Go runtime.

AI-assisted development

This Go rewrite was developed with substantial assistance from OpenAI Codex. Although it has been reviewed and tested, AI-assisted code can contain mistakes. Users should independently evaluate and test it for their use case.

Format support

Format Apply Create Validation
IPS / IPS32 / EBP Yes Yes
UPS Yes, bidirectional Yes CRC32
BPS Yes Yes CRC32
APS (N64) Yes Yes N64 cart ID + CRC
APS (GBA) Yes No (reference engine is also apply-only) CRC16
RUP / NINJA2 Yes, including undo Yes MD5
PPF 1–3 Yes, including undo data Yes Size, block check, and undo records when present
BSDIFF40 (.bdf / .bspatch) Yes No (reference engine is also apply-only)
Paper Mario Star Rod (.mod) Yes No (reference engine is also apply-only) CRC32
VCDIFF / xdelta Yes, including xdelta3 LZMA No (reference engine is also apply-only) Adler-32 windows

VCDIFF supports RFC 3284 default and custom code tables, source/target windows, configurable address caches, Adler-32 validation, and xdelta3's common LZMA secondary compression through the pure-Go github.com/ulikunitz/xz package. The uncommon DJW and FGK secondary compressors remain unsupported.

Library

patch, err := rompatcher.Parse(patchBytes)
output, err := patch.Apply(sourceBytes, rompatcher.ApplyOptions{Validate: true})

created, err := rompatcher.Create(original, modified, rompatcher.FormatBPS, nil)
patchBytes, err := created.MarshalBinary()

// Cancellable random-access file patching for every supported format.
size, err := rompatcher.ApplyReaderAt(ctx, source, sourceSize, patchFile,
    patchSize, output, options)

ApplyWithOptions also supports temporary copier-header removal/addition and Game Boy or Mega Drive/Genesis internal checksum repair. ApplyOptions accepts a context.Context and progress callback. File-backed patching keeps source and output data out of memory; VCDIFF retains only the current target window. Temporary header/checksum transformations use the compatibility memory path. Inspect, DryRun, and ApplyChain provide structured metadata, validation previews, and ordered patch chains.

CLI

go install github.com/olsonb97/RomPatcher.go/cmd/rompatcher@latest
rompatcher apply game.sfc translation.bps -v -o game-patched.sfc
rompatcher apply --dry-run --json game.sfc translation.bps
rompatcher apply games.zip patch.bps -s "region/game.sfc" -o game.sfc
rompatcher apply game.sfc base.bps addon.ips -v -o final.sfc
rompatcher archive games.zip
rompatcher create original.sfc modified.sfc -f bps -o patch.bps
rompatcher inspect --json patch.bps
rompatcher hash game.sfc
rompatcher version

Use - for stdin or stdout. ZIP archives use Go's built-in archive/zip; when multiple ROM or patch candidates exist, the CLI refuses to guess and requires --source-entry or --patch-entry. Outputs are written to a temporary file, synced, and atomically published only after patching succeeds; existing outputs are preserved. Common options have matching short forms: -o/--output, -v/--validate, -n/--dry-run, -j/--json, -p/--progress, and -m/--max-output. Options work before or after filenames. Supplying multiple patches to apply creates an ordered chain; chain remains as a readable alias.

Batch mode accepts a JSON manifest:

{
  "jobs": [{
    "source": "games.zip",
    "sourceEntry": "region/game.sfc",
    "patches": [{"path": "translation.bps"}, {"path": "fix.ips"}],
    "output": "game-patched.sfc",
    "validate": true,
    "removeHeader": false,
    "addHeader": false,
    "fixChecksum": true
  }]
}

The cmd/releasepack helper packages a manually built release binary with the project and dependency licenses, a SHA-256 checksum, and an SPDX 2.3 SBOM.

License

RomPatcher.go is MIT-licensed and retains the original RomPatcher.js copyright and license notice. Its pure-Go XZ dependency and the Go standard library are BSD-3-Clause; the exact required attributions are tracked in THIRD_PARTY_NOTICES.md and bundled in every release archive alongside LICENSE.

Verification

go test ./...
go vet ./...

The tests cover every format, malformed-input safety, resizing, cancellation, atomic output, ZIP ambiguity, chains, custom VCDIFF tables, reversible RUP, header handling, and exact known patch checksums. An optional real-world corpus harness provides broader compatibility coverage without redistributing third-party patches or game data.

Documentation

Overview

Package rompatcher parses, inspects, creates, and applies common ROM patch formats. It provides memory-backed helpers, cancellable random-access file patching, validation, ordered patch chains, ZIP input selection, and ROM header/checksum compatibility operations.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrUnknownFormat    = errors.New("rompatcher: unknown patch format")
	ErrInvalidPatch     = errors.New("rompatcher: invalid patch")
	ErrSourceMismatch   = errors.New("rompatcher: source checksum mismatch")
	ErrTargetMismatch   = errors.New("rompatcher: target checksum mismatch")
	ErrPatchMismatch    = errors.New("rompatcher: patch checksum mismatch")
	ErrOutputTooLarge   = errors.New("rompatcher: requested output exceeds the configured size limit")
	ErrUnsupported      = errors.New("rompatcher: operation is not supported by this format")
	ErrUnexpectedEnd    = errors.New("rompatcher: unexpected end of patch")
	ErrAmbiguousArchive = errors.New("rompatcher: archive contains multiple candidate entries")
)

Functions

func AdditionalChecksum

func AdditionalChecksum(data []byte, name string) string

func Adler32

func Adler32(data []byte) uint32

func Apply

func Apply(source, patchData []byte, options ApplyOptions) ([]byte, error)

func ApplyFile

func ApplyFile(sourcePath, patchPath, outputPath string, opts ApplyOptions) error

func ApplyFileContext

func ApplyFileContext(ctx context.Context, sourcePath, patchPath, outputPath string, opts ApplyOptions) error

func ApplyParsedWithOptions

func ApplyParsedWithOptions(source []byte, p Patch, opts ApplyOptions) ([]byte, error)

func ApplyReaderAt

func ApplyReaderAt(ctx context.Context, source io.ReaderAt, sourceSize int64, patch io.ReaderAt, patchSize int64, output io.WriterAt, opts ApplyOptions) (int64, error)

ApplyReaderAt applies a patch from random-access inputs to a random-access output. Source and output data remain file-backed unless temporary header or checksum transformations require the compatibility memory path. Formats that must read already-produced output also require output to implement ReaderAt; *os.File satisfies both interfaces. The patch itself is parsed in memory. The returned size is the exact output length.

func ApplyWithOptions

func ApplyWithOptions(source, patchData []byte, opts ApplyOptions) ([]byte, error)

func CRC16

func CRC16(data []byte) uint16

CRC16 returns CRC-16/CCITT-FALSE, used by APS (GBA).

func CRC32

func CRC32(data []byte) uint32

func CreateFile

func CreateFile(originalPath, modifiedPath, outputPath string, format Format, opts *CreateOptions) error

func DefaultPatchedPath

func DefaultPatchedPath(sourcePath string) string

func ExtractZIP

func ExtractZIP(path, entry string, kind InputKind, maxSize uint64, dst io.Writer) (string, error)

ExtractZIP writes one explicitly selected or unambiguous entry without buffering it in memory.

func FixROMChecksum

func FixROMChecksum(data []byte, name string) bool

func MD5

func MD5(data []byte) string

func ReadZIP

func ReadZIP(path, entry string, kind InputKind, maxSize uint64) ([]byte, string, error)

func ReadZIPBytes

func ReadZIPBytes(data []byte, entry string, kind InputKind, maxSize uint64) ([]byte, string, error)

func SHA1

func SHA1(data []byte) string

func WriteFileAtomic

func WriteFileAtomic(path string, data []byte) error

WriteFileAtomic writes data to a temporary file in the destination directory, flushes it, and atomically publishes it. Existing destinations are never clobbered.

Types

type APSGBAPatch

type APSGBAPatch struct {
	SourceSize, TargetSize uint32
	Records                []apsGBARecord
	// contains filtered or unexported fields
}

func (*APSGBAPatch) Apply

func (p *APSGBAPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (APSGBAPatch) Description

func (APSGBAPatch) Description() string

func (*APSGBAPatch) Format

func (*APSGBAPatch) Format() Format

func (*APSGBAPatch) MarshalBinary

func (p *APSGBAPatch) MarshalBinary() ([]byte, error)

func (*APSGBAPatch) ValidateSource

func (p *APSGBAPatch) ValidateSource(source []byte) bool

func (*APSGBAPatch) ValidationInfo

func (p *APSGBAPatch) ValidationInfo() *ValidationInfo

type APSN64Patch

type APSN64Patch struct {
	HeaderType, Encoding byte
	PatchDescription     string
	OriginalFormat       byte
	CartID               string
	CartCRC              [8]byte
	Pad                  [5]byte
	TargetSize           uint32
	Records              []apsRecord
}

func (*APSN64Patch) Apply

func (p *APSN64Patch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (*APSN64Patch) Description

func (p *APSN64Patch) Description() string

func (*APSN64Patch) Format

func (*APSN64Patch) Format() Format

func (*APSN64Patch) MarshalBinary

func (p *APSN64Patch) MarshalBinary() ([]byte, error)

func (*APSN64Patch) ValidateSource

func (p *APSN64Patch) ValidateSource(source []byte) bool

func (*APSN64Patch) ValidationInfo

func (p *APSN64Patch) ValidationInfo() *ValidationInfo

type ApplyOptions

type ApplyOptions struct {
	Validate, RemoveHeader, AddHeader, FixChecksum bool
	SourceName                                     string
	// Context cancels long-running patch operations. Nil means context.Background.
	Context context.Context
	// Progress receives coarse, monotonic progress updates. Callbacks must return
	// quickly; they run synchronously with patching.
	Progress func(Progress)
	// MaxOutputSize caps allocations from untrusted patches. Zero chooses a
	// source-relative default (64 MiB plus twice the source size).
	MaxOutputSize uint64
}

type ArchiveEntry

type ArchiveEntry struct {
	Name            string `json:"name"`
	Size            uint64 `json:"size"`
	CompressedSize  uint64 `json:"compressedSize"`
	PatchCandidate  bool   `json:"patchCandidate"`
	SourceCandidate bool   `json:"sourceCandidate"`
}

func ListZIP

func ListZIP(path string) ([]ArchiveEntry, error)

func ListZIPBytes

func ListZIPBytes(data []byte) ([]ArchiveEntry, error)

type ArtifactInfo

type ArtifactInfo struct {
	Size   *uint64           `json:"size,omitempty"`
	Hashes map[string]string `json:"hashes,omitempty"`
}

type BDFPatch

type BDFPatch struct {
	TargetSize uint64
	Records    []bdfRecord
	// contains filtered or unexported fields
}

func (*BDFPatch) Apply

func (p *BDFPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (BDFPatch) Description

func (BDFPatch) Description() string

func (*BDFPatch) Format

func (*BDFPatch) Format() Format

func (*BDFPatch) MarshalBinary

func (*BDFPatch) MarshalBinary() ([]byte, error)

func (BDFPatch) ValidateSource

func (BDFPatch) ValidateSource([]byte) bool

func (BDFPatch) ValidationInfo

func (BDFPatch) ValidationInfo() *ValidationInfo

type BPSPatch

type BPSPatch struct {
	SourceSize, TargetSize         uint64
	Metadata                       string
	Actions                        []bpsAction
	SourceCRC, TargetCRC, PatchCRC uint32
}

func (*BPSPatch) Apply

func (p *BPSPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (*BPSPatch) Description

func (p *BPSPatch) Description() string

func (*BPSPatch) Format

func (*BPSPatch) Format() Format

func (*BPSPatch) MarshalBinary

func (p *BPSPatch) MarshalBinary() ([]byte, error)

func (*BPSPatch) ValidateSource

func (p *BPSPatch) ValidateSource(source []byte) bool

func (*BPSPatch) ValidationInfo

func (p *BPSPatch) ValidationInfo() *ValidationInfo

type ChainResult

type ChainResult struct {
	Output []byte      `json:"-"`
	Steps  []ChainStep `json:"steps"`
}

func ApplyChain

func ApplyChain(source []byte, patches [][]byte, opts ApplyOptions) (ChainResult, error)

ApplyChain applies patches in order. Each patch receives the exact output of the previous patch, so validation catches an incorrectly ordered chain.

type ChainStep

type ChainStep struct {
	Index      int          `json:"index"`
	Inspection Inspection   `json:"inspection"`
	Output     ArtifactInfo `json:"output"`
}

type CreateOptions

type CreateOptions struct {
	Metadata    map[string]string
	Description string
	SourceName  string
	// BPSDelta forces delta matching. By default it is used for files up to 4 MiB.
	BPSDelta bool
}

type DryRunResult

type DryRunResult struct {
	Inspection  Inspection   `json:"inspection"`
	SourceValid *bool        `json:"sourceValid,omitempty"`
	Output      ArtifactInfo `json:"output"`
}

func DryRun

func DryRun(source, patchData []byte, opts ApplyOptions) (DryRunResult, error)

type Format

type Format string
const (
	FormatIPS    Format = "ips"
	FormatIPS32  Format = "ips32"
	FormatEBP    Format = "ebp"
	FormatUPS    Format = "ups"
	FormatAPSN64 Format = "aps"
	FormatAPSGBA Format = "aps-gba"
	FormatBPS    Format = "bps"
	FormatRUP    Format = "rup"
	FormatPPF    Format = "ppf"
	FormatBDF    Format = "bdf"
	FormatPMSR   Format = "mod"
	FormatVCDIFF Format = "vcdiff"
)

type HashInfo

type HashInfo struct {
	Size  int64  `json:"size"`
	CRC32 string `json:"crc32"`
	MD5   string `json:"md5"`
	SHA1  string `json:"sha1"`
}

func HashBytes

func HashBytes(data []byte) HashInfo

func HashReader

func HashReader(ctx context.Context, r io.Reader) (HashInfo, error)

HashReader calculates all supported hashes in one read pass.

type HeaderInfo

type HeaderInfo struct {
	Name string
	Size int
}

func AddHeader

func AddHeader(data []byte, name string) ([]byte, *HeaderInfo)

func CanAddHeader

func CanAddHeader(data []byte, name string) *HeaderInfo

func DetectHeader

func DetectHeader(data []byte, name string) *HeaderInfo

func RemoveHeader

func RemoveHeader(data []byte, name string) (header, rom []byte, info *HeaderInfo)

type IPS32Patch

type IPS32Patch struct {
	Records     []ipsRecord
	Truncate    uint32
	HasTruncate bool
	// contains filtered or unexported fields
}

IPS32Patch is the 32-bit-offset extension of IPS. It uses an IPS32 header, EEOF terminator, and an optional four-byte truncate size.

func (*IPS32Patch) Apply

func (p *IPS32Patch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (IPS32Patch) Description

func (IPS32Patch) Description() string

func (*IPS32Patch) Format

func (*IPS32Patch) Format() Format

func (*IPS32Patch) MarshalBinary

func (p *IPS32Patch) MarshalBinary() ([]byte, error)

func (IPS32Patch) ValidateSource

func (IPS32Patch) ValidateSource([]byte) bool

func (IPS32Patch) ValidationInfo

func (IPS32Patch) ValidationInfo() *ValidationInfo

type IPSPatch

type IPSPatch struct {
	Records     []ipsRecord
	Truncate    int
	HasTruncate bool
	Metadata    map[string]string
	// contains filtered or unexported fields
}

func (*IPSPatch) Apply

func (p *IPSPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (*IPSPatch) Description

func (p *IPSPatch) Description() string

func (*IPSPatch) Format

func (p *IPSPatch) Format() Format

func (*IPSPatch) MarshalBinary

func (p *IPSPatch) MarshalBinary() ([]byte, error)

func (IPSPatch) ValidateSource

func (IPSPatch) ValidateSource([]byte) bool

func (IPSPatch) ValidationInfo

func (IPSPatch) ValidationInfo() *ValidationInfo

type InputKind

type InputKind string
const (
	InputSource InputKind = "source"
	InputPatch  InputKind = "patch"
	InputAny    InputKind = "file"
)

type Inspection

type Inspection struct {
	Format      Format          `json:"format"`
	Description string          `json:"description,omitempty"`
	Source      ArtifactInfo    `json:"source,omitempty"`
	Target      ArtifactInfo    `json:"target,omitempty"`
	Validation  *ValidationInfo `json:"validation,omitempty"`
	Reversible  bool            `json:"reversible"`
	CanCreate   bool            `json:"canCreate"`
	RecordCount int             `json:"recordCount,omitempty"`
	// RecordCountKnown distinguishes a valid empty patch from a lazily decoded
	// format whose record count is unavailable without applying it.
	RecordCountKnown bool              `json:"recordCountKnown"`
	Metadata         map[string]string `json:"metadata,omitempty"`
	Limitations      []string          `json:"limitations,omitempty"`
}

func Inspect

func Inspect(data []byte) (Inspection, error)

func InspectParsed

func InspectParsed(p Patch) Inspection

type PMSRPatch

type PMSRPatch struct {
	TargetSize int
	Records    []pmsrRecord
	// contains filtered or unexported fields
}

func (*PMSRPatch) Apply

func (p *PMSRPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (PMSRPatch) Description

func (PMSRPatch) Description() string

func (*PMSRPatch) Format

func (*PMSRPatch) Format() Format

func (*PMSRPatch) MarshalBinary

func (*PMSRPatch) MarshalBinary() ([]byte, error)

func (*PMSRPatch) ValidateSource

func (*PMSRPatch) ValidateSource(source []byte) bool

func (*PMSRPatch) ValidationInfo

func (*PMSRPatch) ValidationInfo() *ValidationInfo

type PPFPatch

type PPFPatch struct {
	Version          int
	PatchDescription string
	ImageType        byte
	BlockCheck       []byte
	Undo             bool
	InputSize        uint32
	Records          []ppfRecord
	FileID           string
}

func (*PPFPatch) Apply

func (p *PPFPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (*PPFPatch) Description

func (p *PPFPatch) Description() string

func (*PPFPatch) Format

func (*PPFPatch) Format() Format

func (*PPFPatch) MarshalBinary

func (p *PPFPatch) MarshalBinary() ([]byte, error)

func (*PPFPatch) ValidateSource

func (p *PPFPatch) ValidateSource(source []byte) bool

func (*PPFPatch) ValidationInfo

func (p *PPFPatch) ValidationInfo() *ValidationInfo

type Patch

type Patch interface {
	Format() Format
	Apply(source []byte, options ApplyOptions) ([]byte, error)
	ValidateSource(source []byte) bool
	ValidationInfo() *ValidationInfo
	Description() string
	MarshalBinary() ([]byte, error)
}

func Create

func Create(original, modified []byte, format Format, opts *CreateOptions) (Patch, error)

func Parse

func Parse(data []byte) (Patch, error)

type Progress

type Progress struct {
	Phase     string `json:"phase"`
	Format    Format `json:"format,omitempty"`
	Completed int64  `json:"completed"`
	Total     int64  `json:"total,omitempty"`
}

type RUPPatch

type RUPPatch struct {
	TextEncoding                                                         byte
	Author, Version, Title, Genre, Language, Date, Web, PatchDescription string
	Files                                                                []rupFile
}

func (*RUPPatch) Apply

func (p *RUPPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (*RUPPatch) Description

func (p *RUPPatch) Description() string

func (*RUPPatch) Format

func (*RUPPatch) Format() Format

func (*RUPPatch) MarshalBinary

func (p *RUPPatch) MarshalBinary() ([]byte, error)

func (*RUPPatch) ValidateSource

func (p *RUPPatch) ValidateSource(source []byte) bool

func (*RUPPatch) ValidationInfo

func (p *RUPPatch) ValidationInfo() *ValidationInfo

type UPSPatch

type UPSPatch struct {
	SourceSize, TargetSize uint64
	SourceCRC, TargetCRC   uint32
	Records                []upsRecord
	// contains filtered or unexported fields
}

func (*UPSPatch) Apply

func (p *UPSPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (UPSPatch) Description

func (UPSPatch) Description() string

func (*UPSPatch) Format

func (*UPSPatch) Format() Format

func (*UPSPatch) MarshalBinary

func (p *UPSPatch) MarshalBinary() ([]byte, error)

func (*UPSPatch) ValidateSource

func (p *UPSPatch) ValidateSource(source []byte) bool

func (*UPSPatch) ValidationInfo

func (p *UPSPatch) ValidationInfo() *ValidationInfo

type VCDIFFPatch

type VCDIFFPatch struct {
	// contains filtered or unexported fields
}

func (*VCDIFFPatch) Apply

func (p *VCDIFFPatch) Apply(source []byte, options ApplyOptions) ([]byte, error)

func (VCDIFFPatch) Description

func (VCDIFFPatch) Description() string

func (*VCDIFFPatch) Format

func (*VCDIFFPatch) Format() Format

func (*VCDIFFPatch) MarshalBinary

func (*VCDIFFPatch) MarshalBinary() ([]byte, error)

func (VCDIFFPatch) ValidateSource

func (VCDIFFPatch) ValidateSource([]byte) bool

func (VCDIFFPatch) ValidationInfo

func (VCDIFFPatch) ValidationInfo() *ValidationInfo

type ValidationInfo

type ValidationInfo struct {
	Type   string   `json:"type"`
	Values []string `json:"values"`
}

Directories

Path Synopsis
cmd
releasepack command
Command releasepack creates deterministic release archives, SHA-256 checksums, and an SPDX 2.3 SBOM for one binary.
Command releasepack creates deterministic release archives, SHA-256 checksums, and an SPDX 2.3 SBOM for one binary.
rompatcher command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL