tp/tools/librel/rellib.py

393 lines
13 KiB
Python

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
rel_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
alignment: int = 0
# not part of the section in the rel-file
addr: int = None
write_offset: 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] = field(default=None, repr=False)
data: bytearray = field(default=None,repr=False)
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[7] >= 2:
v2 = struct.unpack('>II', buffer[0x40:0x48])
align = v2[0]
bssAlign = v2[1]
header_size += 0x8
fixSize = 0
if header[7] >= 3:
v3 = struct.unpack('>I', buffer[0x48:0x4C])
fixSize = v3[0]
header_size += 0x4
rel = REL(*header, align, bssAlign, fixSize)
rel.data = buffer
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
section.alignment = align
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
bss_section.alignment = bssAlign
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.rel_offset = rel_offset - 0x8
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
def write_header(file, rel):
header = struct.pack(
'>IIIIIIIIIIIIBBBBIII',
rel.index,
rel.next,
rel.prev,
rel.numSections,
rel.sectionInfoOffset,
rel.nameOffset,
rel.nameSize,
rel.version,
rel.bssSize,
rel.relOffset,
rel.impOffset,
rel.impSize,
rel.prologSection,
rel.epilogSection,
rel.unresolvedSection,
rel.bssSection,
rel.prolog,
rel.epilog,
rel.unresolved)
file.write(header)
if rel.version >= 2:
header = struct.pack(
'>II',
rel.align,
rel.bssAlign)
file.write(header)
if rel.version >= 3:
header = struct.pack(
'>I',
rel.fixSize)
file.write(header)
def write_section_header(file, section):
assert section.offset & 3 == 0
file.write(struct.pack('>II',
section.offset | ((section.unknown_flag & 1) << 1) | ((section.executable_flag & 1) << 0),
section.length
))
def write_section_data(file, section):
assert len(section.data) == section.length
file.write(section.data)
def write_relocation(file, offset, type, section, addend):
assert (offset & 0xFFFF) == offset
assert (type & 0xFF) == type
assert (section & 0xFF) == section
assert (addend & 0xFFFFFFFF) == addend
file.write(struct.pack('>HBBI',
offset,
type,
section,
addend
))
def write_imp(file, module, offset):
assert (module & 0xFFFFFFFF) == module
assert (offset & 0xFFFFFFFF) == offset
file.write(struct.pack('>II',
module,
offset
))
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),
}
class RELRelocationException(Exception):
...
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))
I = P - data_offset
if size == 4:
if len(data[I:]) < 4:
raise RELRelocationException(f"data length too short. length >= {I+4} (current length: {len(data)})")
X = struct.unpack('>I', data[I:][:4])[0]
elif size == 2:
if len(data[I:]) < 2:
raise RELRelocationException(f"data length too short. length >= {I+2} (current length: {len(data)})")
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)
assert (R & BM) == 0
assert (R & TM) == 0 or (R & TM) == TM
M = TM | BM
Y = struct.pack(">I", (X & M) | (R & ~M))
elif kind == R_PPC_REL14:
R = (S + A - P) & 0xFFFFFFFF
TM = 0b1111111111111111 << 16
BM = 0b11
assert (R & BM) == 0
assert (R & TM) == 0 or (R & TM) == TM
M = TM | BM
Y = struct.pack(">I", (X & M) | (R & ~M))
else:
return False
data[I:I+size] = Y
return True