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. May split the record to multiple.
Returns: List of: self (no compression) or compressed IpsRecord
-
ipsy.cleanup_records(ips_records, path_dst)[source]¶ Removes useless records and combines records when possible.
Parameters: - ips_records – List of
IpsRecord - path_dst – Path to file that these patches are intended to be used on.
Returns: List of
IpsRecord, simplified where possible.- ips_records – List of
-
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
Returns: List of
IpsRecordthat were written to the file.
-
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 patches are intended 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
IpsRecordto 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.read(fhpatch, EOFcontinue=False)[source]¶ Read in an IPS file to a list of
IpsRecordParameters: - 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.rle_compress(records)[source]¶ Attempt to RLE compress a collection of IPS records.
Parameters: records – List of IpsRecordto compressReturns: RLE compressed list of IpsRecord
-
ipsy.write(fhpatch, records)[source]¶ Writes out a list of
IpsRecordto a fileParameters: - fhpatch – File handler of the new patch file
- records – 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 |