Module: Ips::Patcher

Defined in:
lib/ips/patcher.rb,
lib/ips/patcher/version.rb

Overview

IPS (International Patching System) patcher module for applying binary patches to ROM files.

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =

Current version of the ips-patcher gem

"0.1.1"

Class Method Summary collapse

Class Method Details

.apply(rom_path, patch_path, output: nil) ⇒ void

Note:

The original ROM file is not modified; a new patched file is created

This method returns an undefined value.

Applies an IPS patch file to a ROM file.

This method reads both the ROM and patch files, validates the patch format, and applies all patch records to create a new patched ROM file. If no output path is specified, the output file is named with ".patched" inserted before the file extension.

The IPS format supports two types of records:

  • Standard records: Replace bytes at a specific offset
  • RLE records: Fill bytes with a repeated value (when size is 0)

Examples:

Apply a patch with automatic output naming

Ips::Patcher.apply("game.nes", "translation.ips")
# Output: game.patched.nes

Apply a patch with custom output path

Ips::Patcher.apply("game.nes", "translation.ips", output: "patched_game.nes")
# Output: patched_game.nes

Parameters:

  • rom_path (String) —

    path to the ROM file to be patched

  • patch_path (String) —

    path to the IPS patch file

  • output (String, nil) (defaults to: nil) —

    optional path for the output file. If nil, the output file is automatically named with ".patched" inserted before the file extension

Raises:

  • (Error) —

    if the patch file does not have a valid "PATCH" header



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/ips/patcher.rb', line 51

def self.apply(rom_path, patch_path, output: nil)
  rom = File.binread(rom_path)
  patch = File.binread(patch_path)

  reader = Ips::BinaryReader.new(patch)
  writer = Ips::BinaryWriter.new(rom)

  header = reader.read_string(5)
  if header != 'PATCH'
    raise Error, "Invalid patch file"
  end

  loop do
    eof = reader.read_string(3)
    break if eof == 'EOF'

    reader.go_back(3)

    offset = reader.read_int24
    size   = reader.read_int16

    if size == 0
      rle_size  = reader.read_int16
      rle_value = reader.read_bytes(1)
      writer.set_bytes(offset, rle_value * rle_size)
      next
    end

    bytes = reader.read_bytes(size)
    writer.set_bytes(offset, bytes)        
  end

  output_path = output

  unless output_path
    rom_dir     = File.dirname(rom_path)
    filename    = File.basename(rom_path, File.extname(rom_path))
    extension   = File.extname(rom_path)
    output_path = File.join(rom_dir, "#{filename}.patched#{extension}")
  end

  writer.save_to_file(output_path)
end