mirror of
https://github.com/zeldaret/oot.git
synced 2024-12-01 15:26:01 +00:00
ebbc820ef6
* Add "disasm sym_info.py" script for investigating the expected memory layout * review * add friendly error if context.csv is missing * rom as hexadecimal * require spimdisasm 1.28.1 (technically not *required* but may as well) * some fixups on symbol lookup logic
188 lines
6.9 KiB
Python
Executable file
188 lines
6.9 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
|
|
# SPDX-FileCopyrightText: © 2024 ZeldaRET
|
|
# SPDX-License-Identifier: CC0-1.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
from pathlib import Path
|
|
from typing import BinaryIO
|
|
|
|
import spimdisasm
|
|
|
|
from file_addresses import DmaFile, parse_file_addresses, get_z_name_for_overlay
|
|
|
|
|
|
def load_file_splits(
|
|
context: spimdisasm.common.Context,
|
|
config_dir: Path,
|
|
dma_file: DmaFile,
|
|
f: BinaryIO,
|
|
) -> spimdisasm.mips.FileSplits:
|
|
# Assume that we're reading from a decompressed ROM where the DMA file is
|
|
# now located at the same ROM offset as the VROM start
|
|
f.seek(dma_file.vrom_start)
|
|
data = f.read(dma_file.vrom_end - dma_file.vrom_start)
|
|
|
|
file_splits_path = config_dir / f"files_{dma_file.name}.csv"
|
|
if file_splits_path.exists():
|
|
default_filename = dma_file.name
|
|
splits_data = spimdisasm.common.FileSplitFormat()
|
|
splits_data.readCsvFile(file_splits_path)
|
|
reloc_section = None
|
|
elif dma_file.overlay_dir is not None:
|
|
z_name = get_z_name_for_overlay(dma_file.name)
|
|
default_filename = (
|
|
f"src/overlays/{dma_file.overlay_dir}/{dma_file.name}/{z_name}"
|
|
)
|
|
splits_data = None
|
|
reloc_section = spimdisasm.mips.sections.SectionRelocZ64(
|
|
context,
|
|
vromStart=0,
|
|
vromEnd=len(data),
|
|
vram=dma_file.vram_start,
|
|
filename=f"src/overlays/{dma_file.overlay_dir}/{dma_file.name}/{dma_file.name}",
|
|
array_of_bytes=data,
|
|
segmentVromStart=0,
|
|
overlayCategory=None,
|
|
)
|
|
else:
|
|
raise Exception(
|
|
f"DMA file {dma_file.name} is not an overlay but has no file splits"
|
|
)
|
|
|
|
return spimdisasm.mips.FileSplits(
|
|
context,
|
|
vromStart=0,
|
|
vromEnd=len(data),
|
|
vram=dma_file.vram_start,
|
|
filename=default_filename,
|
|
array_of_bytes=data,
|
|
segmentVromStart=0,
|
|
overlayCategory=None,
|
|
splitsData=splits_data,
|
|
relocSection=reloc_section,
|
|
)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Disassemble a ROM.")
|
|
parser.add_argument("rom", type=Path, help="Input ROM")
|
|
parser.add_argument(
|
|
"-o", "--output-dir", help="Output directory", type=Path, required=True
|
|
)
|
|
parser.add_argument(
|
|
"--config-dir", help="Config directory", type=Path, required=True
|
|
)
|
|
parser.add_argument(
|
|
"--split-functions", help="Write functions into separate files", type=Path
|
|
)
|
|
|
|
spimdisasm.common.Context.addParametersToArgParse(parser)
|
|
spimdisasm.common.GlobalConfig.addParametersToArgParse(parser)
|
|
spimdisasm.mips.InstructionConfig.addParametersToArgParse(parser)
|
|
|
|
args = parser.parse_args()
|
|
|
|
if spimdisasm.__version_info__ < (1, 28, 1):
|
|
print(f"Error: spimdisasm>=1.28.1 is required (you have {spimdisasm.__version__})")
|
|
print("Hint: run `make setup` to update the venv.")
|
|
exit(1)
|
|
|
|
context = spimdisasm.common.Context()
|
|
context.parseArgs(args)
|
|
context.changeGlobalSegmentRanges(0x00000000, 0x01000000, 0x8000000, 0x81000000)
|
|
context.addBannedSymbolRange(0x0000F000, 0x00010100)
|
|
context.addBannedSymbolRange(0x10000000, 0x80000300)
|
|
context.addBannedSymbolRange(0xA0000000, 0xFFFFFFFF)
|
|
|
|
spimdisasm.mips.InstructionConfig.parseArgs(args)
|
|
spimdisasm.common.GlobalConfig.parseArgs(args)
|
|
|
|
spimdisasm.common.GlobalConfig.PRODUCE_SYMBOLS_PLUS_OFFSET = True
|
|
spimdisasm.common.GlobalConfig.TRUST_USER_FUNCTIONS = True
|
|
|
|
dma_files = parse_file_addresses(args.config_dir / "file_addresses.csv")
|
|
|
|
print("Loading disasm info...")
|
|
all_file_splits: list[spimdisasm.mips.FileSplits] = []
|
|
with open(args.rom, "rb") as f:
|
|
for dma_file in dma_files:
|
|
file_splits = load_file_splits(context, args.config_dir, dma_file, f)
|
|
all_file_splits.append(file_splits)
|
|
|
|
print("Analyzing...")
|
|
for i, file_splits in enumerate(all_file_splits):
|
|
f = i / len(all_file_splits)
|
|
spimdisasm.common.Utils.printQuietless(
|
|
f"{f*100:3.0f}%", "Analyzing", file_splits.name, end=" \r"
|
|
)
|
|
file_splits.analyze()
|
|
print()
|
|
print("Analyzing done.")
|
|
|
|
output_dir: Path = args.output_dir
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
context.saveContextToFile(output_dir / "context.csv")
|
|
|
|
print("Writing disassembled sections...")
|
|
for i, file_splits in enumerate(all_file_splits):
|
|
f = i / len(all_file_splits)
|
|
spimdisasm.common.Utils.printQuietless(
|
|
f"{f*100:3.0f}%", "Writing", file_splits.name, end=" \r"
|
|
)
|
|
for sectDict in file_splits.sectionsDict.values():
|
|
for name, section in sectDict.items():
|
|
basepath = output_dir / name
|
|
basepath.parent.mkdir(parents=True, exist_ok=True)
|
|
if section.sectionType == spimdisasm.common.FileSectionType.Reloc:
|
|
# basepath is like
|
|
# .../ovl_Overlay_Name/z_overlay_name
|
|
# and we want to write relocs to
|
|
# .../ovl_Overlay_Name/ovl_Overlay_Name_reloc.s
|
|
path = basepath.parent / f"{basepath.parent.name}_reloc.s"
|
|
with path.open("w", encoding="UTF-8") as f:
|
|
section.disassembleToFile(f)
|
|
else:
|
|
section.saveToFile(str(basepath))
|
|
print()
|
|
print("Writing sections done.")
|
|
|
|
if args.split_functions is not None:
|
|
print("Writing disassembled functions individually...")
|
|
for i, file_splits in enumerate(all_file_splits):
|
|
f = i / len(all_file_splits)
|
|
spimdisasm.common.Utils.printQuietless(
|
|
f"{f*100:3.0f}%", "Writing", file_splits.name, end=" \r"
|
|
)
|
|
|
|
for section_name, text_section in file_splits.sectionsDict[
|
|
spimdisasm.common.FileSectionType.Text
|
|
].items():
|
|
rodata_section = file_splits.sectionsDict[
|
|
spimdisasm.common.FileSectionType.Rodata
|
|
].get(section_name)
|
|
# FunctionRodataEntry represents a function,
|
|
# plus any associated rodata (strings, floats, jump tables...)
|
|
# It can also be rodata that hasn't been associated to any function
|
|
for (
|
|
func_rodata_entry
|
|
) in spimdisasm.mips.FunctionRodataEntry.getAllEntriesFromSections(
|
|
text_section, rodata_section
|
|
):
|
|
output_dir = (
|
|
args.split_functions
|
|
/ section_name
|
|
/ f"{func_rodata_entry.getName()}.s"
|
|
)
|
|
output_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
with output_dir.open("w", encoding="UTF-8") as f:
|
|
func_rodata_entry.writeToFile(f, writeFunction=True)
|
|
print()
|
|
print("Writing functions done.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|