Files
game-and-watch-patch/patches/patch.py
2021-08-19 12:18:00 -07:00

105 lines
3.5 KiB
Python

COMMAND_DESCRIPTIONS = {
"replace": "simple byte substitution",
"bl": "replace bl",
}
VALID_COMMANDS = set(list(COMMAND_DESCRIPTIONS.keys()))
class Patch:
def __init__(self, command, offset, data, size=None, message=None):
if command not in VALID_COMMANDS:
raise ValueError(f"Invalid command \"{command}\"")
self.command = command
self.offset = offset
self.data = data
self.size = size
self.message = message
@property
def command_callable(self):
return getattr(self, self.command)
def __call__(self, firmware):
return self.command_callable(firmware)
def replace(self, firmware):
"""
Parameters
----------
firmware : Firmware
"""
if not isinstance(self.offset, int):
raise ValueError(f"offset must be an int; got {type(self.offset)}")
if self.offset >= len(firmware):
raise IndexError(f"Patch offset {self.offset} exceeds firmware length {len(self)}")
if self.offset >= firmware.STOCK_ROM_END:
raise IndexError(f"Patch offset {self.offset} exceeds stock firmware region {firmware.STOCK_ROM_END}")
n_bytes_patched = 0
if isinstance(self.data, bytes):
# Write the bytes at that address as is.
firmware[self.offset:self.offset + len(self.data)] = self.data
n_bytes_patched = len(self.data)
elif isinstance(self.data, str):
if self.size:
raise ValueError("Don't specify size when providing a symbol name.")
self.data = firmware.address(self.data)
firmware[self.offset:self.offset+4] = self.data.to_bytes(4, 'little')
elif isinstance(self.data, int):
# must be 1, 2, or 4 bytes
if self.size is None:
raise ValueError("Must specify \"size\" when providing int data")
if self.size not in (1, 2, 4):
raise ValueError(f"Size must be one of {1, 2, 4}; got {self.size}")
firmware[self.offset:self.offset+self.size] = self.data.to_bytes(self.size, 'little')
else:
raise ValueError(f"Don't know how to parse data type \"{self.data}\"")
return n_bytes_patched
def bl(self, firmware):
if not isinstance(self.data, str):
raise ValueError(f"Data must be str, got {type(self.data)}")
dst_address = firmware.address(self.data)
pc = firmware.FLASH_BASE + self.offset + 4
jump = dst_address - pc
if jump <= 0:
raise NotImplementedError("negative jump")
offset_stage_1 = jump >> 12
if offset_stage_1 >> 11:
raise ValueError(f"bl jump 0x{jump:08X} too large!")
stage_1_byte_0 = 0b11110000 | ((offset_stage_1 >> 8) & 0x7)
stage_1_byte_1 = offset_stage_1 & 0xFF
offset_stage_2 = (jump - (offset_stage_1 << 12)) >> 1
if offset_stage_2 >> 11:
raise ValueError(f"bl jump 0x{jump:08X} too large!")
stage_2_byte_0 = 0b11111000 | ((offset_stage_2 >> 8) & 0x7)
stage_2_byte_1 = offset_stage_2 & 0xFF
# Store the instructions in little endian order
firmware[self.offset + 0] = stage_1_byte_1
firmware[self.offset + 1] = stage_1_byte_0
firmware[self.offset + 2] = stage_2_byte_1
firmware[self.offset + 3] = stage_2_byte_0
return 4
class Patches(list):
def append(self, *args, **kwargs):
super().append(Patch(*args, **kwargs))