Skip to content

Instantly share code, notes, and snippets.

@yuriks
Created November 14, 2022 00:48
Show Gist options
  • Save yuriks/d292211416b624e55ccdaae2938a08ec to your computer and use it in GitHub Desktop.
Save yuriks/d292211416b624e55ccdaae2938a08ec to your computer and use it in GitHub Desktop.
ips2asm.py
#!/usr/bin/env python3
#
# Copyright (c) 2022 Yuri Kunde Schlesner (yuriks)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# Changelog:
# v1.1:
# Fix patches with RLE commands
# Remove forgotten debug prints
import argparse
import itertools
import struct
def make_argument_parser():
parser = argparse.ArgumentParser(
description="Convert IPS patches to asar-style ASM files containing db directives.")
parser.add_argument('--mapping', '-m', choices=['lorom'], default='lorom',
help="ROM mapping to use to calculate org directives. Default: lorom")
parser.add_argument('--width', '-w', type=int, default=16,
help="Number of bytes per line. Default: 16")
parser.add_argument('ips_file', type=argparse.FileType('rb'),
help="Path to input IPS file")
parser.add_argument('asm_file', type=argparse.FileType('w'),
help="Path to save output ASM to")
return parser
def unpack_u24(s):
a, bc = struct.unpack('>BH', s)
return (a << 16) | bc
def read_ips_patches(f):
magic = f.read(5)
if magic != b'PATCH':
raise ValueError('IPS magic invalid, excpected "PATCH" but got {}'.format(repr(magic)))
offset_raw = f.read(3)
while offset_raw != b'EOF':
offset = unpack_u24(offset_raw)
size_raw = f.read(2)
size, = struct.unpack('>H', size_raw)
if size == 0:
rle_raw = f.read(3)
rle_len, rle_byte = struct.unpack('>HB', rle_raw)
data = itertools.repeat(rle_byte, rle_len)
else:
data = f.read(size)
if len(data) != size:
raise ValueError('unexpected EOF while reading patch data')
yield (offset, size, data)
offset_raw = f.read(3)
def lorom_offset_to_addr(offset):
bank = offset // 0x8000
bank_offset = offset % 0x8000
return (bank << 16) + bank_offset + 0x8000
def groupn(it, n):
it = iter(it)
while True:
g = list(itertools.islice(it, n))
if not g:
return
yield g
def write_asm(f, patches, line_width):
f.write('lorom\n')
for offset, size, data in patches:
addr = lorom_offset_to_addr(offset)
f.write('\norg ${:06X}\n'.format(addr))
for line in groupn(data, line_width):
f.write(' db ')
f.write(','.join('${:02X}'.format(val) for val in line))
f.write('\n')
def main():
args = make_argument_parser().parse_args()
with args.ips_file as inf:
with args.asm_file as outf:
write_asm(outf, read_ips_patches(inf), args.width)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment