Files
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

226 lines
8.7 KiB
Python

"""ASM Patcher library functions."""
import js
from randomizer.Patching.Library.Generic import Overlay
from randomizer.Patching.Library.DataTypes import float_to_hex
from randomizer.Patching.Patcher import ROM
HANDLED_OVERLAYS = (
Overlay.Static,
Overlay.Menu,
Overlay.Multiplayer,
Overlay.Minecart,
Overlay.Bonus,
Overlay.Race,
Overlay.Critter,
Overlay.Boss,
Overlay.Arcade,
Overlay.Jetpac,
)
BANNED_OFFSETS = (0, 0xFFFFFFFF)
OVERLAY_ENDS = {
Overlay.Static: 0x80761050,
Overlay.Menu: 0x80033F10,
Overlay.Multiplayer: 0x80027100,
Overlay.Minecart: 0x80028E10,
Overlay.Bonus: 0x8002DEF0,
Overlay.Race: 0x80030160,
Overlay.Critter: 0x8002A1B0,
Overlay.Boss: 0x80036DC0,
Overlay.Arcade: 0x8004AC00,
Overlay.Jetpac: 0x8002EC30,
Overlay.Custom: 0x805FAE00,
}
CUSTOM_ACTORS_START = 345
def populateOverlayOffsets(ROM_COPY) -> dict:
"""Populate the overlay offset database."""
result = {}
for ovl in HANDLED_OVERLAYS:
ROM_COPY.seek(0x1FFB000 + (8 * ovl))
code = int.from_bytes(ROM_COPY.readBytes(4), "big")
if code not in BANNED_OFFSETS:
result[ovl] = code
return result
def getROMAddress(address: int, overlay: Overlay, offset_dict: dict) -> int:
"""Get ROM Address corresponding to a specific RDRAM Address in an overlay."""
if overlay == Overlay.Custom:
rdram_start = 0x805FAE00 - 0x39DC0
overlay_start = 0x2000000
elif overlay == Overlay.Boot:
rdram_start = 0x80000450
overlay_start = 0x1050
else:
if overlay not in list(offset_dict.keys()):
return None
overlay_start = offset_dict[overlay]
rdram_start = 0x80024000
if overlay == Overlay.Static:
rdram_start = 0x805FB300
elif overlay == Overlay.Boot:
rdram_start = 0x80000450
if overlay in OVERLAY_ENDS:
if address >= OVERLAY_ENDS[overlay]:
raise Exception(f"Seeking out of bounds for this overlay. Attempted to seek to {hex(address)} in overlay {overlay.name}")
if address < rdram_start:
raise Exception(f"Seeking out of bounds for this overlay. Attempted to seek to {hex(address)} in overlay {overlay.name}")
return overlay_start + (address - rdram_start)
def writeValue(ROM_COPY, address: int, overlay: Overlay, value: int, offset_dict: dict, size: int = 2, signed: bool = False):
"""Write value to ROM based on overlay."""
if isinstance(ROM_COPY, ROM) and overlay == Overlay.Custom:
raise Exception("Cosmetics write to the custom code overlay.")
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
ROM_COPY.seek(rom_start)
passed_value = int(value)
if value < 0 and signed:
passed_value += 1 << (8 * size)
ROM_COPY.writeMultipleBytes(passed_value, size)
def readValue(ROM_COPY, address: int, overlay: Overlay, offset_dict: dict, size: int = 2, signed: bool = False):
"""Read value to ROM based on overlay."""
if isinstance(ROM_COPY, ROM) and overlay == Overlay.Custom:
raise Exception("Cosmetics write to the custom code overlay.")
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
ROM_COPY.seek(rom_start)
value = ROM_COPY.readBytes(size)
read_value = int.from_bytes(value)
if read_value < 0 and signed:
read_value += 1 << (8 * size)
return read_value
def writeFloat(ROM_COPY, address: int, overlay: Overlay, value: float, offset_dict: dict):
"""Write floating point variable to ROM."""
if isinstance(ROM_COPY, ROM) and overlay == Overlay.Custom:
raise Exception("Cosmetics write to the custom code overlay.")
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
ROM_COPY.seek(rom_start)
passed_value = int(float_to_hex(value), 16)
ROM_COPY.writeMultipleBytes(passed_value, 4)
def writeFloatUpper(ROM_COPY, address: int, overlay: Overlay, value: float, offset_dict: dict):
"""Write upper 16 bit portion of floating point variable to ROM."""
if isinstance(ROM_COPY, ROM) and overlay == Overlay.Custom:
raise Exception("Cosmetics write to the custom code overlay.")
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
ROM_COPY.seek(rom_start)
passed_value = int(float_to_hex(value), 16)
passed_value = (passed_value >> 16) & 0xFFFF
ROM_COPY.writeMultipleBytes(passed_value, 2)
def writeFunction(ROM_COPY, address: int, overlay: Overlay, func_name: str, offset_dict: dict):
"""Write function JAL to ROM."""
if isinstance(ROM_COPY, ROM):
raise Exception("Cosmetics cannot utilize writeFunction.")
# NOTE: This **CANNOT** be used for cosmetic changes
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
function_address = js.rom_symbols["symbols"].get(func_name.lower(), None)
if function_address is None:
raise Exception(f"Couldn't find function {func_name}.")
ROM_COPY.seek(rom_start)
value = 0x0C000000 | ((function_address & 0xFFFFFF) >> 2)
ROM_COPY.writeMultipleBytes(value, 4)
def writeHook(ROM_COPY, address: int, overlay: Overlay, hook_location: str, offset_dict):
"""Write hook to ROM."""
if isinstance(ROM_COPY, ROM):
raise Exception("Cosmetics cannot utilize writeHook.")
# NOTE: This **CANNOT** be used for cosmetic changes
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
hook_address = js.rom_symbols["symbols"].get(hook_location.lower(), None)
if hook_address is None:
raise Exception(f"Couldn't find hook {hook_location}.")
ROM_COPY.seek(rom_start)
value = 0x08000000 | ((hook_address & 0xFFFFFF) >> 2)
ROM_COPY.writeMultipleBytes(value, 4)
ROM_COPY.writeMultipleBytes(0, 4)
def writeLabelValue(ROM_COPY, address: int, overlay: Overlay, label_name: str, offset_dict) -> int:
"""Write value of label to ROM."""
if isinstance(ROM_COPY, ROM):
raise Exception("Cosmetics cannot utilize writeLabelValue.")
# NOTE: This **CANNOT** be used for cosmetic changes
rom_start = getROMAddress(address, overlay, offset_dict)
if rom_start is None:
raise Exception(f"Couldn't ascertain a ROM start for address {hex(address)} and Overlay {overlay.name}.")
label_address = js.rom_symbols["symbols"].get(label_name.lower(), None)
if label_address is None:
raise Exception(f"Couldn't find hook {label_name}.")
ROM_COPY.seek(rom_start)
ROM_COPY.writeMultipleBytes(label_address, 4)
def getLo(value: int) -> int:
"""Get the lower 16 bits for a 32-bit value that can be loaded into an addiu instruction."""
return value & 0xFFFF
def getHi(value: int) -> int:
"""Get the upper 16 bits for a 32-bit value that can be loaded into a lui instruction."""
hi = (value >> 16) & 0xFFFF
lo = getLo(value)
if lo & 0x8000:
return hi + 1
return hi
def getHiSym(ref: str) -> int:
"""Run getHi, but relative to a symbol rather than a value."""
label_address = js.rom_symbols["symbols"].get(ref.lower(), None)
if label_address is None:
raise Exception(f"Couldn't find symbol {ref}.")
return getHi(label_address)
def getLoSym(ref: str) -> int:
"""Run getLo, but relative to a symbol rather than a value."""
label_address = js.rom_symbols["symbols"].get(ref.lower(), None)
if label_address is None:
raise Exception(f"Couldn't find symbol {ref}.")
return getLo(label_address)
def getSym(ref: str) -> int:
"""Get symbol value."""
sym_value = js.rom_symbols["symbols"].get(ref.lower(), None)
if sym_value is None:
raise Exception(f"Couldn't find symbol {ref}.")
return sym_value
def getVar(ref: str) -> int:
"""Get variable value."""
label_value = js.rom_symbols["vars"].get(ref.lower(), None)
if label_value is None:
raise Exception(f"Couldn't find variable {ref}.")
return label_value
def getActorIndex(input: int) -> int:
"""Get actor index from provided value."""
if input & 0x8000:
return CUSTOM_ACTORS_START + (input & 0x7FFF)
return input