mirror of
https://github.com/BrianPugh/game-and-watch-patch.git
synced 2025-12-16 07:16:26 +01:00
191 lines
5.5 KiB
Python
191 lines
5.5 KiB
Python
"""
|
|
For usage, run:
|
|
python3 patch.py --help
|
|
"""
|
|
|
|
import sys
|
|
|
|
if sys.version_info[0] < 3 or sys.version_info[1] < 6:
|
|
raise Exception("Must be using at least Python 3.6")
|
|
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
|
|
import colorama
|
|
from colorama import Fore, Style
|
|
|
|
from patches import Device
|
|
from patches.exception import InvalidPatchError
|
|
|
|
colorama.init()
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Game and Watch Firmware Patcher.")
|
|
|
|
#########################
|
|
# Global configurations #
|
|
#########################
|
|
parser.add_argument(
|
|
"--device",
|
|
type=str,
|
|
choices=[
|
|
"mario",
|
|
"zelda",
|
|
],
|
|
default="mario",
|
|
help="Game and Watch device model",
|
|
)
|
|
parser.add_argument(
|
|
"--patch",
|
|
type=Path,
|
|
default="build/gw_patch.bin",
|
|
help="Compiled custom code to insert at the end of the internal firmware",
|
|
)
|
|
parser.add_argument(
|
|
"--elf",
|
|
type=Path,
|
|
default="build/gw_patch.elf",
|
|
help="ELF file corresponding to the bin provided by --patch",
|
|
)
|
|
parser.add_argument(
|
|
"--int-output",
|
|
type=Path,
|
|
default="build/internal_flash_patched.bin",
|
|
help="Patched internal firmware.",
|
|
)
|
|
parser.add_argument(
|
|
"--ext-output",
|
|
type=Path,
|
|
default="build/external_flash_patched.bin",
|
|
help="Patched external firmware.",
|
|
)
|
|
|
|
parser.add_argument(
|
|
"--extended",
|
|
action="store_true",
|
|
default=False,
|
|
help="256KB internal flash image instead of 128KB.",
|
|
)
|
|
parser.add_argument(
|
|
"--encrypt",
|
|
action="store_true",
|
|
help="Enable OTFDEC for the main extflash binary.",
|
|
)
|
|
parser.add_argument(
|
|
"--triple-boot",
|
|
action="store_true",
|
|
help="Enable RIGHT+GAME to launch 0x08020000.",
|
|
)
|
|
parser.add_argument(
|
|
"--sd-bootloader",
|
|
action="store_true",
|
|
help="Change target dual boot to 0x08032000.",
|
|
)
|
|
parser.add_argument(
|
|
"--compression-ratio",
|
|
type=float,
|
|
default=1.4,
|
|
help="Data targeted for SRAM3 will only be put into "
|
|
"SRAM3 if it's compression ratio is above this value. "
|
|
"Otherwise, will fallback to internal flash, then external "
|
|
"flash.",
|
|
)
|
|
|
|
debugging = parser.add_argument_group("Debugging")
|
|
debugging.add_argument(
|
|
"--show",
|
|
action="store_true",
|
|
help="Show a picture representation of the external patched binary.",
|
|
)
|
|
debugging.add_argument(
|
|
"--debug", action="store_true", help="Install useful debugging fault handlers."
|
|
)
|
|
|
|
args, _ = parser.parse_known_args()
|
|
args.int_firmware = Path(f"internal_flash_backup_{args.device}.bin")
|
|
args.ext_firmware = Path(f"flash_backup_{args.device}.bin")
|
|
|
|
device = Device.registry[args.device](
|
|
args.int_firmware, args.elf, args.ext_firmware
|
|
)
|
|
args = device.argparse(parser)
|
|
device.crypt() # Decrypt the external firmware
|
|
|
|
# Save the decrypted external firmware for debugging/development purposes.
|
|
Path("build/decrypt.bin").write_bytes(device.external)
|
|
|
|
# Dump ITCM and DTCM RAM data
|
|
if (
|
|
device.internal.RWDATA_OFFSET is not None
|
|
and device.internal.RWDATA_ITCM_IDX is not None
|
|
):
|
|
Path("build/itcm_rwdata.bin").write_bytes(
|
|
device.internal.rwdata.datas[device.internal.RWDATA_ITCM_IDX]
|
|
)
|
|
if (
|
|
device.internal.RWDATA_OFFSET is not None
|
|
and device.internal.RWDATA_DTCM_IDX is not None
|
|
):
|
|
Path("build/dtcm_rwdata.bin").write_bytes(
|
|
device.internal.rwdata.datas[device.internal.RWDATA_DTCM_IDX]
|
|
)
|
|
|
|
# Copy over novel code
|
|
patch = args.patch.read_bytes()
|
|
if len(device.internal) != len(patch):
|
|
raise InvalidPatchError(
|
|
f"Expected patch length {len(device.internal)}, got {len(patch)}"
|
|
)
|
|
|
|
# novel_code_start = device.internal.address("__do_global_dtors_aux") & 0x00FF_FFF8
|
|
novel_code_start = device.internal.STOCK_ROM_END
|
|
device.internal[novel_code_start:] = patch[novel_code_start:]
|
|
del patch
|
|
|
|
if args.sd_bootloader:
|
|
device.internal.extend(b"\x00" * 0x12000)
|
|
elif args.extended:
|
|
device.internal.extend(b"\x00" * 0x20000)
|
|
|
|
print(Fore.BLUE)
|
|
print("#########################")
|
|
print("# BEGINING BINARY PATCH #")
|
|
print("#########################" + Style.RESET_ALL)
|
|
|
|
(
|
|
internal_remaining_free,
|
|
compressed_memory_remaining_free,
|
|
) = device() # Apply patches
|
|
|
|
if args.show:
|
|
# Debug visualization
|
|
device.show()
|
|
|
|
# Re-encrypt the external firmware
|
|
Path("build/decrypt_flash_patched.bin").write_bytes(device.external)
|
|
if args.encrypt:
|
|
device.external.crypt(device.internal.key, device.internal.nonce)
|
|
|
|
# Save patched firmware
|
|
args.int_output.write_bytes(device.internal)
|
|
args.ext_output.write_bytes(device.external)
|
|
|
|
print(Fore.GREEN)
|
|
print("Binary Patching Complete!")
|
|
print(
|
|
f" Internal Firmware Used: {len(device.internal) - internal_remaining_free} bytes"
|
|
)
|
|
print(f" Free: {internal_remaining_free} bytes")
|
|
print(
|
|
f" Compressed Memory Used: {len(device.compressed_memory) - compressed_memory_remaining_free} bytes"
|
|
)
|
|
print(f" Free: {compressed_memory_remaining_free} bytes")
|
|
print(f" External Firmware Used: {len(device.external)} bytes")
|
|
print(Style.RESET_ALL)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|