import struct import os import yaz0 import io from dataclasses import dataclass, field from pathlib import Path from typing import List, Optional R_PPC_NONE = 0 R_PPC_ADDR32 = 1 R_PPC_ADDR24 = 2 R_PPC_ADDR16 = 3 R_PPC_ADDR16_LO = 4 R_PPC_ADDR16_HI = 5 R_PPC_ADDR16_HA = 6 R_PPC_ADDR14 = 7 R_PPC_REL24 = 10 R_PPC_REL14 = 11 R_DOLPHIN_NOP = 201 R_DOLPHIN_SECTION = 202 R_DOLPHIN_END = 203 R_DOLPHIN_MRKREF = 204 RELOCATION_NAMES = { R_PPC_NONE: "R_PPC_NONE", R_PPC_ADDR32: "R_PPC_ADDR32", R_PPC_ADDR24: "R_PPC_ADDR24", R_PPC_ADDR16: "R_PPC_ADDR16", R_PPC_ADDR16_LO: "R_PPC_ADDR16_LO", R_PPC_ADDR16_HI: "R_PPC_ADDR16_HI", R_PPC_ADDR16_HA: "R_PPC_ADDR16_HA", R_PPC_ADDR14: "R_PPC_ADDR14", R_PPC_REL24: "R_PPC_REL24", R_PPC_REL14: "R_PPC_REL14", R_DOLPHIN_NOP: "R_DOLPHIN_NOP", R_DOLPHIN_SECTION: "R_DOLPHIN_SECTION", R_DOLPHIN_END: "R_DOLPHIN_END", R_DOLPHIN_MRKREF: "R_DOLPHIN_MRKREF" } @dataclass class Relocation: module: int # reference module section: int # reference section type: int offset: int addend: int # reference offset parent: "Section" = field(default=None, repr=False) access: "disassembler.Access" = None relative_offset: int = None @property def symbol_offset(self): return self.addend @property def replace_addr(self): return self.offset # self.parent.offset + """ @property def addr(self): shift = 0 if (self.type == R_PPC_ADDR16 or self.type == R_PPC_ADDR16_LO or self.type == R_PPC_ADDR16_HI or self.type == R_PPC_ADDR16_HA): shift -= 2 return self.offset + shift # self.parent.offset """ @dataclass class Section: index: int # Offset from the beginning of the REL to the section. If this is zero, the section is an uninitialized section (i.e. .bss) offset: int = 0 unknown_flag: bool = False # Executable flag; if this is 1 the section is executable. executable_flag: bool = False # Length in bytes of the section. If this is zero, this entry is skipped. length: int = 0 # not part of the section in the rel-file addr: int = None name: Optional[str] = None data: Optional[bytes] = field(default=None, repr=False) relocations: List[Relocation] = field(default_factory=list, repr=False) @dataclass class REL: # Arbitrary identification number. Must be unique amongst all RELs used by a game. Must not be 0. index: int = 0 next: int = 0 # Pointer to next module, filled at runtime. prev: int = 0 # Pointer to previous module, filled at runtime. numSections: int = 0 # Number of sections in the file. sectionInfoOffset: int = 0 # Offset to the start of the section table. # Offset to ASCII string containing name of module. May be NULL. Relative to start of REL file. nameOffset: int = 0 nameSize: int = 0 # Size of the module name in bytes. version: int = 0 # Version number of the REL file format. bssSize: int = 0 # Size of the '.bss' section. relOffset: int = 0 # Offset to the relocation table. impOffset: int = 0 # Offset to imp table. impSize: int = 0 # Size of imp table in bytes. # Index into section table which prolog is relative to. Skip if this field is 0. prologSection: int = 0 # Index into section table which epilog is relative to. Skip if this field is 0. epilogSection: int = 0 # Index into section table which unresolved is relative to. Skip if this field is 0. unresolvedSection: int = 0 bssSection: int = 0 # Index into section table which bss is relative to. Filled at runtime! prolog: int = 0 # Offset into specified section of the _prolog function. epilog: int = 0 # Offset into specified section of the _epilog function. # Offset into specified section of the _unresolved function. unresolved: int = 0 # Version ≥ 2 only. Alignment constraint on all sections, expressed as power of 2. align: int = 0 # Version ≥ 2 only. Alignment constraint on all '.bss' section, expressed as power of 2. bssAlign: int = 0 # Version ≥ 3 only. If REL is linked with OSLinkFixed (instead of OSLink), the space after this address can be used for other purposes (like BSS). fixSize: int = 0 # not part of the rel-file sections: List[Section] = field(default_factory=list, repr=False) relocations: List[Relocation] = field(default_factory=list, repr=False) path: Optional[Path] = None map: Optional[List[str]] = None def read_relocation(module, buffer): offset, type, section, addend = struct.unpack('>HBBI', buffer[:0x8]) return Relocation(module, section, type, offset, addend) def read_section(index, buffer): header = struct.unpack('>II', buffer[:0x8]) flag0 = ((header[0] >> 1) & 1) != 0 flag1 = ((header[0] >> 0) & 1) != 0 offset = header[0] & ~3 length = header[1] return Section(index, offset, flag0, flag1, length) def read(buffer): # check if the rel is compressed if struct.unpack('>I', buffer[:4])[0] == 0x59617A30: buffer = yaz0.decompress(io.BytesIO(buffer)) header_size = 0x40 header = struct.unpack('>IIIIIIIIIIIIBBBBIII', buffer[:0x40]) align = 0 bssAlign = 0 if header[5] >= 2: align, bssAlign = struct.unpack('>II', buffer[0x40:0x48]) header_size += 0x8 fixSize = 0 if header[5] >= 3: fixSize = struct.unpack('>I', buffer[0x48:0x4C])[0] header_size += 0x4 rel = REL(*header, align, bssAlign, fixSize) rel.sections = [] section_buffer = buffer[rel.sectionInfoOffset:] for i in range(rel.numSections): section_offset = 0x8 * i section = read_section(i, section_buffer[section_offset:]) rel.sections.append(section) addr = 0 for section in rel.sections: section.offset_padding = 0 section.first_padding = 0 section.addr = addr addr += section.length for section in reversed(rel.sections): if section.offset > 0 and section.length > 0: offset_aligned = (section.offset + 15) & ~15 shift = offset_aligned - section.offset section.offset_padding += 0 # shift addr_aligned = (section.addr + 7) & ~7 shift = addr_aligned - section.addr section.first_padding += 0 # shift break for section in rel.sections: if section.offset > 0 and section.length > 0: section.data = buffer[section.offset:][:section.length + section.first_padding+section.offset_padding] for bss_section in rel.sections[1:]: if bss_section.data == None: bss_section.addr = (last_end + 7) & ~7 last_end = bss_section.addr + bss_section.length rel.relocations = [] imp_buffer = buffer[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]) section = None offset = 0 while True: relocation = read_relocation(module_id, buffer[rel_offset:]) rel_offset += 0x8 if relocation.type == R_PPC_NONE: continue if relocation.type == R_DOLPHIN_NOP: # NOP is used to simulate have 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 == 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 != R_DOLPHIN_MRKREF if relocation.type == R_DOLPHIN_END: break section.relocations.append(relocation) rel.relocations.append(relocation) return rel RELOCATION_SIZES = { R_PPC_ADDR32: (0, 4), R_PPC_ADDR24: (1, 4), R_PPC_ADDR16: (0, 2), R_PPC_ADDR16_LO: (0, 2), R_PPC_ADDR16_HI: (0, 2), R_PPC_ADDR16_HA: (0, 2), R_PPC_REL24: (1, 4), R_PPC_REL14: (2, 4), } def apply_relocation(kind: int, module: int, data: bytearray, data_offset: int, P: int, S: int, A: int) -> bool: """ P = relocation position - the location where the relocation will occur (data[P - data_offset]) S = symbol address - the address of the symbol we are referencing A = append - the value added to the symbol address """ offset, size = RELOCATION_SIZES.get(kind, (0, 0)) #if module != 0: # P -= offset I = P - data_offset if size == 4: X = struct.unpack('>I', data[I:][:4])[0] elif size == 2: X = struct.unpack('>H', data[I:][:2])[0] else: return False if kind == R_PPC_ADDR32: R = (S + A) & 0xFFFFFFFF Y = struct.pack('>I', R) elif kind == R_PPC_ADDR16 or kind == R_PPC_ADDR16_LO: R = (S + A) & 0xFFFF Y = struct.pack('>H', R) elif kind == R_PPC_ADDR16_HI: R = ((S + A) >> 16) & 0xFFFF Y = struct.pack('>H', R) elif kind == R_PPC_ADDR16_HA: R = (S + A) & 0xFFFFFFFF H = (R >> 16) + (1 if (R & 0x8000) else 0) Y = struct.pack('>H', H & 0xFFFF) elif kind == R_PPC_REL24: R = (S + A - P) & 0xFFFFFFFF TM = (0b111111 << 26) & 0xFFFFFFFF BM = (0b11) """ print(f"P {P:08X} ({P+offset:08X})") print(f"S {S:08X}") print(f"A {A:08X}") print(f"R {R:08X}") print(f" {R:032b}") print(f"B {BM:032b}") print(f"T {TM:032b}") print(f"{(R & BM):08X} == {0:08X}") print(f"{(R & TM):08X} == {0:08X}") print(f"{(R & TM):08X} == {TM:08X}") print(" ".join([f"{x:02X}" for x in data[I:][:size]])) print(" ".join([f"{x:02X}" for x in Y])) """ assert (R & BM) == 0 assert (R & TM) == 0 or (R & TM) == TM Y = struct.pack(">I", (X & TM) | (R & ~TM)) elif kind == R_PPC_REL24: R = (S + A - P) & 0xFFFFFFFF TM = 0b1111111111111111 << 16 BM = 0b11 assert (R & BM) == 0 assert (R & TM) == 0 or (R & TM) == TM Y = struct.pack(">I", (X & TM)| (R & ~TM)) else: return False """ if P == 0x80006bb4: print(kind, module, hex(P), hex(A), hex(S)) print(" ".join([f"{x:02X}" for x in data[I:][:size]])) print(" ".join([f"{x:02X}" for x in Y])) sys.exit(1) """ data[I:I+size] = Y assert data[I:][:size] == Y return True