mirror of https://github.com/zeldaret/mm.git
				
				
				
			
		
			
				
	
	
		
			197 lines
		
	
	
		
			7.4 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			197 lines
		
	
	
		
			7.4 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, 36, 0):
 | 
						|
         # Version should be kept up to date with requirements.txt
 | 
						|
        print(f"Error: spimdisasm>=1.36.0 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)
 | 
						|
 | 
						|
    progress_str = ""
 | 
						|
 | 
						|
    print("Analyzing...")
 | 
						|
    for i, file_splits in enumerate(all_file_splits):
 | 
						|
        f = i / len(all_file_splits)
 | 
						|
 | 
						|
        spimdisasm.common.Utils.printQuietless(f'{len(progress_str) * " "}\r', end="")
 | 
						|
        progress_str = f'{f*100:3.0f}% Analyzing {file_splits.name}\r'
 | 
						|
        spimdisasm.common.Utils.printQuietless(progress_str, end="", flush=True)
 | 
						|
 | 
						|
        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'{len(progress_str) * " "} \r', end="")
 | 
						|
        progress_str = f'{f*100:3.0f}% Writing {file_splits.name}\r'
 | 
						|
        spimdisasm.common.Utils.printQuietless(progress_str, end="", flush=True)
 | 
						|
 | 
						|
        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'{len(progress_str) * " "} \r', end="")
 | 
						|
            progress_str = f'{f*100:3.0f}% Writing {file_splits.name}\r'
 | 
						|
            spimdisasm.common.Utils.printQuietless(progress_str, end="", flush=True)
 | 
						|
 | 
						|
            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()
 |