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
)
- ... even when the padded data contains only zeros. (
- 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
)
- ... even when the record is run-length encoded (