tp/tools/rel.py

203 lines
6.6 KiB
Python

"""
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="<REL>",
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()