[utopian-io] ips-util - Python package for manipulating IPS patches

Background

The International Patching System (IPS) patch format is a historically popular distribution format for ROM hacks. It's a very simple format, and a number of tools exist for creating IPS patches from binary data. I wanted to be able to create IPS patches programatically in Python scripts, however, and no existing library provided that support. So I made one.

I got enough testing and polish done on the library that I chose to upload it to PyPI as a formal Python package. As such, a released version can be acquired by any Python user with pip install ips-util.

Links

Usage notes

(Pretty much straight from the README.)

To create a patch, using existing source and target binary files:

> ips_util create "Super Mario World.smc" "Super Mario World [1337357_h4x_3v4r].smc" -o 1337_p47ch.ips

To apply a patch to a binary file:

> ips_util apply 1337_p47ch.ips "Super Mario World.smc" -o w00t.smc

To dump the contents of a patch:

> ips_util trace 1337_p47ch.ips

The package can also be used within Python scripts to build IPS patches manually, as follows:

from ips_util import Patch

def this_is_my_patch():
  patch = Patch()
  
  patch.add_record(0x1234, 999.to_bytes(2, byteorder='little')) # Max out some stat
  patch.add_rle_record(0x5678, b'\xea', 0x10)                   # NOP out a bunch of code
  
  with open('gavroche.ips', 'w+b') as f:
    f.write(patch.encode())

Testing

The package includes unit tests (contained in the ips_util/tests/test_patch.py script) to verify design requirements and some common edge cases in IPS format:

Manually created patches

  • Verify that a basic patch produces the desired result (test_patch_core)
  • Verify that a change made past the end of the original data will pad the result data as needed (test_patch_padding)
  • Verify that the truncation syntax used by some IPS tools functions as intended (test_patch_truncation)
  • Verify that the library generates an error when the starting address of a patch record is exactly 0x454f46 ('EOF') (test_patch_eof_edge_case)

Patches created from source and target binary data (the Patch.create() API)

  • Verify that the patch created from binary data reproduces the desired target data when applied (test_create_equal_length)
  • Verify that creating a patch correctly handles padding when the target data is longer than the source data (test_create_padded_length)
    • ... even when the padded data contains only zeros. (test_create_padded_length_all_zero)
  • Verify that creating a patch correctly handles truncation when the target data is shorter than the source data (test_create_truncated_length)
  • Verify that when a difference occurs at the address 0x454f46, the generated patch shifts that address to avoid the EOF error noted above (test_create_eof_edge_case)
  • Verify that an error is generated when the binary data provided is longer than the maximum supported address (test_create_address_overflow)
  • Verify that when creating a patch, differences longer than the maximum supported size are handled gracefully (test_create_size_overflow)
    • ... even when the record is run-length encoded (test_create_rle_size_overflow)
H2
H3
H4
3 columns
2 columns
1 column
5 Comments