1
0
Fork 0
mirror of https://github.com/zeldaret/oot.git synced 2024-12-26 14:46:16 +00:00
oot/tools/check_ordering.py

216 lines
7.6 KiB
Python
Executable file

#!/usr/bin/env python3
# SPDX-FileCopyrightText: 2024 zeldaret
# SPDX-License-Identifier: CC0-1.0
from __future__ import annotations
import argparse
import dataclasses
import enum
from pathlib import Path
import sys
from typing import BinaryIO
import elftools.elf.elffile
import mapfile_parser.mapfile
@dataclasses.dataclass
class Reloc:
name: str
offset_32: int | None
offset_hi16: int | None
offset_lo16: int | None
addend: int
@dataclasses.dataclass
class Pointer:
name: str
addend: int
base_value: int
build_value: int
def read_relocs(object_path: Path, section_name: str) -> list[Reloc]:
with open(object_path, "rb") as f:
elffile = elftools.elf.elffile.ELFFile(f)
symtab = elffile.get_section_by_name(".symtab")
data = elffile.get_section_by_name(section_name).data()
reloc_section = elffile.get_section_by_name(f".rel{section_name}")
if reloc_section is None:
return []
relocs = []
offset_hi16 = 0
for reloc in reloc_section.iter_relocations():
reloc_offset = reloc.entry["r_offset"]
reloc_type = reloc.entry["r_info_type"]
reloc_name = symtab.get_symbol(reloc.entry["r_info_sym"]).name
if reloc_type == 2: # R_MIPS_32
offset_32 = reloc_offset
addend = int.from_bytes(
data[reloc_offset : reloc_offset + 4], "big", signed=True
)
relocs.append(Reloc(reloc_name, offset_32, None, None, addend))
elif reloc_type == 4: # R_MIPS_26
pass
elif reloc_type == 5: # R_MIPS_HI16
offset_hi16 = reloc_offset
elif reloc_type == 6: # R_MIPS_LO16
offset_lo16 = reloc_offset
addend_hi16 = int.from_bytes(
data[offset_hi16 + 2 : offset_hi16 + 4], "big", signed=False
)
addend_lo16 = int.from_bytes(
data[offset_lo16 + 2 : offset_lo16 + 4], "big", signed=True
)
addend = (addend_hi16 << 16) + addend_lo16
relocs.append(Reloc(reloc_name, None, offset_hi16, offset_lo16, addend))
else:
raise NotImplementedError(f"Unsupported relocation type: {reloc_type}")
return relocs
def read_u32(f: BinaryIO, offset: int) -> int:
f.seek(offset)
return int.from_bytes(f.read(4), "big")
def read_u16(f: BinaryIO, offset: int) -> int:
f.seek(offset)
return int.from_bytes(f.read(2), "big")
def read_s16(f: BinaryIO, offset: int) -> int:
f.seek(offset)
return int.from_bytes(f.read(2), "big", signed=True)
def main():
parser = argparse.ArgumentParser(
description="Report data/bss reorderings between the baserom and the current build "
"by parsing relocations from the built object files and comparing their final values "
"between the baserom and the current build. "
"Assumes that the only differences are due to ordering and that the text sections of the "
"ROMS are not shifted."
)
parser.add_argument(
"--oot-version",
"-v",
type=str,
default="gc-eu-mq-dbg",
help="OOT version (default: gc-eu-mq-dbg)",
)
parser.add_argument(
"--segment",
type=str,
help="ROM segment to check, e.g. 'boot', 'code', or 'ovl_player_actor' (default: all)",
)
args = parser.parse_args()
version = args.oot_version
mapfile = mapfile_parser.mapfile.MapFile()
mapfile.readMapFile(f"build/{version}/oot-{version}.map")
# Segments built from source code (filtering out assets)
source_code_segments = []
for mapfile_segment in mapfile:
if (
args.segment
and mapfile_segment.name != f"..{args.segment}"
and mapfile_segment.name != f"..{args.segment}.bss"
):
continue
if not (
mapfile_segment.name.startswith("..boot")
or mapfile_segment.name.startswith("..code")
or mapfile_segment.name.startswith("..buffers")
or mapfile_segment.name.startswith("..ovl_")
):
continue
source_code_segments.append(mapfile_segment)
base = open(f"baseroms/{version}/baserom-decompressed.z64", "rb")
build = open(f"build/{version}/oot-{version}.z64", "rb")
# Find all pointers with different values
pointers = []
for mapfile_segment in source_code_segments:
for file in mapfile_segment:
if not str(file.filepath).endswith(".o"):
continue
if file.sectionType == ".bss":
continue
for reloc in read_relocs(file.filepath, file.sectionType):
if reloc.offset_32 is not None:
base_value = read_u32(base, file.vrom + reloc.offset_32)
build_value = read_u32(build, file.vrom + reloc.offset_32)
elif reloc.offset_hi16 is not None and reloc.offset_lo16 is not None:
if (
read_u16(base, file.vrom + reloc.offset_hi16)
!= read_u16(build, file.vrom + reloc.offset_hi16)
) or (
read_u16(base, file.vrom + reloc.offset_lo16)
!= read_u16(build, file.vrom + reloc.offset_lo16)
):
print(
f"Error: Reference to {reloc.name} in {file.filepath} is in a shifted (or non-matching even ignoring relocs) portion of the ROM.\n"
"Please ensure that the only differences between the baserom and the current build are due to data ordering.",
file=sys.stderr,
)
sys.exit(1)
base_value = (
read_u16(base, file.vrom + reloc.offset_hi16 + 2) << 16
) + read_s16(base, file.vrom + reloc.offset_lo16 + 2)
build_value = (
read_u16(build, file.vrom + reloc.offset_hi16 + 2) << 16
) + read_s16(build, file.vrom + reloc.offset_lo16 + 2)
else:
assert False, "Invalid relocation"
if base_value != build_value:
pointers.append(
Pointer(reloc.name, reloc.addend, base_value, build_value)
)
# Remove duplicates and sort by baserom address
pointers = list({p.base_value: p for p in pointers}.values())
pointers.sort(key=lambda p: p.base_value)
# Go through sections and report differences
for mapfile_segment in source_code_segments:
for file in mapfile_segment:
pointers_in_section = [
p
for p in pointers
if file.vram <= p.build_value < file.vram + file.size
]
if not pointers_in_section:
continue
print(f"{file.filepath} {file.sectionType} is reordered:")
for i, p in enumerate(pointers_in_section):
if p.addend > 0:
addend_str = f"+0x{p.addend:X}"
elif p.addend < 0:
addend_str = f"-0x{-p.addend:X}"
else:
addend_str = ""
if i > 0 and p.build_value < pointers_in_section[i - 1].build_value:
print(" --------------------") # BSS wraps around
print(
f" {p.base_value:08X} -> {p.build_value:08X} {p.name}{addend_str}"
)
if __name__ == "__main__":
main()