""" rel.py - Tool for extracting information from .rel files """ import sys import struct from pathlib import Path try: import click import logging import librel from libdol2asm import settings from rich.logging import RichHandler from rich.console import Console except ImportError as e: MISSING_PREREQUISITES = ( f"Missing prerequisite python module {e}.\n" f"Run `python3 -m pip install --user -r tools/requirements.txt` to install prerequisites." ) print(MISSING_PREREQUISITES, file=sys.stderr) sys.exit(1) VERSION = "1.0" CONSOLE = Console() logging.basicConfig( level="NOTSET", format="%(message)s", datefmt="[%X]", handlers=[RichHandler(console=CONSOLE, rich_tracebacks=True)], ) LOG = logging.getLogger("rich") LOG.setLevel(logging.INFO) @click.group() @click.version_option(VERSION) def rel(): pass @rel.command(name="info") @click.option("--debug/--no-debug") @click.option("--header", "-t", "dump_header", is_flag=True, default=False) @click.option("--sections", "-s", "dump_sections", is_flag=True, default=False) @click.option("--data", "-d", "dump_data", is_flag=True, default=False) @click.option("--relocations", "-r", "dump_relocation", is_flag=True, default=False) @click.option("--imp", "-i", "dump_imp", is_flag=True, default=False) @click.option("--all", "-a", "dump_all", is_flag=True, default=False) @click.argument( "rel_path", metavar="", nargs=1, type=click.Path(exists=True, file_okay=True, dir_okay=False), ) def rel_info( debug, rel_path, dump_header, dump_sections, dump_data, dump_relocation, dump_imp, dump_all, ): if debug: LOG.setLevel(logging.DEBUG) path = Path(rel_path) if not path.exists(): LOG.error(f"File not found: '{path}'") sys.exit(1) with open(path, "rb") as file: buffer = file.read() rel = librel.read(buffer) if dump_all: dump_header = True dump_sections = True dump_data = True dump_relocation = True dump_imp = True if not (dump_header or dump_sections or dump_data or dump_relocation or dump_imp): CONSOLE.print("no dump options specified") if dump_header: CONSOLE.print(f"Header:") CONSOLE.print(f"\tindex: {rel.index}") CONSOLE.print(f"\tversion: {rel.version}") CONSOLE.print(f"\tsection count: {rel.numSections}") CONSOLE.print(f"\tsection offset: 0x{rel.sectionInfoOffset:04X}") CONSOLE.print(f"") CONSOLE.print(f"\tname offset: 0x{rel.nameOffset:04X}") CONSOLE.print(f"\tname size: {rel.nameSize}") CONSOLE.print(f"\talign: 0x{rel.align:02X}") CONSOLE.print(f"\tfix size: 0x{rel.fixSize:04X}") CONSOLE.print(f"\tbss size: 0x{rel.bssSection:X}") CONSOLE.print(f"\tbss align: 0x{rel.bssAlign:02X}") CONSOLE.print(f"\tbss section: 0x{rel.bssSize:04X}") CONSOLE.print(f"\trel offset: 0x{rel.relOffset:04X}") CONSOLE.print(f"\timp offset: 0x{rel.impOffset:04X}") CONSOLE.print(f"\timp size: 0x{rel.impSize:04X}") CONSOLE.print( f"\t_prolog: 0x{rel.prolog:04X} (section: 0x{rel.prologSection:X})" ) CONSOLE.print( f"\t_epilog: 0x{rel.epilog:04X} (section: 0x{rel.epilogSection:X})" ) CONSOLE.print( f"\t_unresolved: 0x{rel.unresolved:04X} (section: 0x{rel.unresolvedSection:X})" ) if dump_sections: CONSOLE.print(f"Sections:") for i, section in enumerate(rel.sections): CONSOLE.print( f"\t#{i:<2} offset: 0x{section.offset:08X}, length: 0x{section.length:04X}, unknown flag: {section.unknown_flag}, executable flag: {section.executable_flag}" ) if dump_imp: CONSOLE.print(f"Imp Table:") imp_buffer = rel.data[rel.impOffset :] for i in range(rel.impSize // 8): imp_offset = 0x8 * i module_id, rel_offset = struct.unpack(">II", imp_buffer[imp_offset:][:0x8]) CONSOLE.print( f"\t#{i:<2} module: {module_id:>4}, offset: 0x{rel_offset:04X}" ) if dump_relocation: CONSOLE.print(f"Relocations:") imp_buffer = rel.data[rel.impOffset :] for i in range(rel.impSize // 8): imp_offset = 0x8 * i module_id, rel_offset = struct.unpack(">II", imp_buffer[imp_offset:][:0x8]) CONSOLE.print(f"\t[ module: {module_id:>4} ]") section = None offset = 0 rel_index = 0 while True: relocation = librel.read_relocation(module_id, rel.data[rel_offset:]) rel_offset += 0x8 rel_index += 1 extra = "" if section and path.name in settings.REL_TEMP_LOCATION: base = settings.REL_TEMP_LOCATION[path.name] base += section.offset - rel.sections[1].offset extra = f" | {base:08X} {base+relocation.offset + offset:08X}" CONSOLE.print( f"\t#{rel_index:<3} {librel.RELOCATION_NAMES[relocation.type]:<20} {relocation.section:>4} 0x{relocation.offset+offset:08X} 0x{relocation.addend:08X}{extra}" ) if relocation.type == librel.R_PPC_NONE: continue if relocation.type == librel.R_DOLPHIN_NOP: # NOP is used to simulate long offset values, this is because # the offset field is limited to 2^16-1 values. Thus, any offsets # above 2^16 will be divided into a NOP + original relocation type. offset += relocation.offset continue if relocation.type == librel.R_DOLPHIN_SECTION: section = rel.sections[relocation.section] offset = 0 continue assert section relocation.parent = section relocation.relative_offset = relocation.offset relocation.offset += offset offset = relocation.offset assert relocation.type != librel.R_DOLPHIN_MRKREF if relocation.type == librel.R_DOLPHIN_END: break if dump_data: CONSOLE.print(f"Sections:") for i, section in enumerate(rel.sections): if section.data: CONSOLE.print(f"\t#{i:<2}") hexdata = hexdump.hexdump(section.data, result="return") CONSOLE.print(hexdata) if __name__ == "__main__": rel()