Ipsy’s documentation

Ipsy is a tool for applying IPS (International/Internal Patch System) files. They are typically used for distributing emulator ROM changes as distributing the patched ROM would violate copyright law. IPS has a rather strait forward format expalined below.


Using ipsy

You can run Ipsy without installing by passing it into python with the ‘-m’ flag from the root directory of the package.

python3 -m ipsy [Ipsy Arguments]

Install as you would any other python program. You shouldn’t use sudo to install Ipsy unless you have reviewed the source code.

pip3 install --user .

Ipsy has three major options: Patch, Diff, and Merge.

If you have to files ‘game.rom’ and ‘patch_for_game.ips’ you can patch the game.

ipsy patch ./game.rom ./patch_for_game.ips -o new_rom

This will generate a file named ‘new_rom’ or, if no output name is given, ‘game_patched.rom’. The ‘o’ flag always supersedes any name that might be assigned by Ipsy regardless of the operation.

If you want to generate a patch use...

ipsy diff ./game.rom ./edited_game.ips

This will generate a file named ‘patch.ips’

When diffing you can also enable Run Length Encoding (RLE)

ipsy diff ./game.rom ./edited_game.ips -rle

RLE finds groups of edits where the same value is written is succession and replaces them with the value and the number of times that value should be written.

Lastly, you can merge multiple IPS files using the merge option

ipsy merge patchOne patchTwo patchThree patchFour

Results in a single file named after the first patch.

Ipsy’s help output:

usage: ipsy [-h] {patch,diff,merge} ... [output]

Apply an IPS patch, Diff two files to generate a patch, or Merge multiple IPS
files.

positional arguments:
  {patch,diff,merge}  Options for Ipsy...
    patch             Apply a patch to an unpatched file.
    diff              Generate an IPS file by diffing the unpatched and
                      patched versions.
    merge             Combine several IPS files into one.
  output              Name for the new, patched, file.

optional arguments:
  -h, --help          show this help message and exit

Ipsy API

class ipsy.IpsRecord[source]

Data container for one record of an IPS file.

Parameters:
  • offset – offset in first 3 bytes of the record, stored as int
  • size – size in the next 2 bytes, stored as int
  • rle_size – size in the next 2 bytes if previous was 0, stored as int
  • data – bytes object of data with length ‘size’ or ‘rle_size’
compress()[source]

Attempts to RLE compress the record into a single, smaller, record. Makes No attempt to spilt the record up into multiple.

Returns:self or compressed IpsRecord
inflate()[source]

Inflate the record if it is currently RLE compressed.

Returns:self or inflated IpsRecord
last_byte()[source]

Calculate the last byte written to when this record is applied to a file.

Returns:offset of the last byte written to.
exception ipsy.IpsyError[source]

Logged by ips_read() when IPS corruption is found.

ipsy.cleanup_records(ips_records, path_dst)[source]

Removes useless records and cobines records when possible. This function creates and deletes two temp files in the calling directory.

Parameters:
  • ips_records – List of IpsRecord
  • path_dst – Path to file that these pathes are inteded to be used on.
Returns:

List of IpsRecord, simplified where possible.

ipsy.diff(fhsrc, fhdst, fhpatch=None, rle=False)[source]

Diff two files, attempt RLE compression, and write the IPS patch to a file. Assumes both files are the same size.

Parameters:
  • fhsrc – File handler of orignal file
  • fhdst – File handler of the patched file
  • fhpatch – File handler for IPS file
  • rle – True if RLE compression should be used
ipsy.eof_check(fhpatch)[source]

Reviews an IPS patch to insure it has only one EOF marker.

Parameters:fhpatch – File handler of IPS patch
Returns:True if exactly one marker, else False
ipsy.ips_read(fhpatch, EOFcontinue=False)[source]

Read in an IPS file to a list of IpsRecord

Parameters:
  • fhpatch – File handler for IPS patch
  • EOFcontinue – Continue processing until the real EOF is found (last 3 bytes of file)
Returns:

List of IpsRecord

ipsy.ips_write(fhpatch, records)[source]

Writes out a list of IpsRecord to a file

Parameters:
  • fhpatch – File handler of the new patch file
  • records – List of IpsRecord
ipsy.merge(fhpatch, *fhpatches, path_dst=None)[source]

Turns several IPS patches into one larger patch. The order that the patches are applied in is preserved. If the destination file is provided then further simplifications can be made.

Parameters:
  • fhpatch – File Handler for resulting IPS file
  • fhpatches – list of File Handlers for IPS files to merge
  • path_dst – Path to file that these pathes are inteded to be used on.
ipsy.patch(fhdest, fhpatch, EOFcontinue=False)[source]

Apply an IPS patch to a file. Destructive processes.

Parameters:
  • fhdest – File handler to-be-patched
  • fhpatch – File handler of the patch
  • EOFcontinue – Continue processing until the real EOF is found (last 3 bytes of file)
Returns:

Number of records applied by the patch

ipsy.patch_from_records(fhdest, records)[source]

Apply an list of IpsRecord a file. Destructive processes.

Parameters:
  • fhdest – File handler to-be-patched
  • fhpatch – File handler of the patch
Returns:

Number of records applied by the patch

ipsy.rle_compress(records)[source]

Attempt to RLE compress a collection of IPS records.

Parameters:records – List of IpsRecord to compress
Returns:RLE compressed list of IpsRecord

IPS File Format

IPS files consist of a header, a list of records, and a footer. All IPS files are big-endian. Due to fixed length of the offset value, IPS files can’t be used to patch a target larger that 2^24-1 bytes (16 MB). The minimum viable IPS file (1 record changing 1 byte of data) is 14 bytes in size. There are a handfull of concerns with the format. A literal ‘EOF’ marker is used that is intended to be found instead of an offset value, however if the offset value is RAM address 0x454f46 then we have a problem. Ipsy, and most differs, resolves this by checking the offset and, if needed, decriments it while increamenting the size of the record and pre-appending the previous byte’s value to data.

Section Size in Bytes Description
Header 5 String literal ‘PATCH’ (Hex: 50 41 54 43 48)
Record(s) See Below Repeatable section that contains describes a change to be made
Footer 3 String literal ‘EOF’ (Hex: 45 4f 46)

Each record has the format ...

Section Size in Bytes Description
Offset 3 Zero index offset that the change should be applied at
Size 2 Size of the ‘data’ section in bytes (See below if 0)
Data size Data to be written

There is one exception to the format of a record. Records with size 0 are Run Length Encoded (RLE). This means that the record represents a one byte value that is to be written many times starting at the offset. The format for an RLE Record is below.

Section Size in Bytes Description
Offset 3 Zero index offset that the change should be applied at
Size 2 0 (See above if not)
RLE_Size 2 Number of times to repeat the below one-byte value
Data 1 Value to be repeated