mirror of https://github.com/zeldaret/tp.git
393 lines
13 KiB
Python
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 |