mirror of
https://github.com/BrianPugh/game-and-watch-patch.git
synced 2025-12-16 07:16:26 +01:00
105 lines
3.5 KiB
Python
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))
|
|
|
|
|