tp/tools/libdol/dollib.py

152 lines
4.1 KiB
Python

"""
Simple library for reading and parsing from .dol files.
"""
import struct
from dataclasses import dataclass, field
from typing import List
@dataclass
class Section:
""" DOL Section """
offset: int
addr: int
size: int
aligned_size: int
type: str
name: str
data: bytearray = field(repr=False)
@property
def start(self):
return self.addr
@property
def end(self):
return self.addr + self.size
def get_data(self, start, end):
assert start >= self.start
assert end <= self.end
return self.data[start - self.start:end - self.start]
def __contains__(self, addr):
return addr >= self.start and addr < self.end
@dataclass
class DOL:
""" DOL File """
sections: List[Section]
bss_address: int
bss_size: int
entry_point: int
def get_named_section(self, name: str) -> Section:
for section in self.sections:
if section.name == name:
return section
return None
# TODO: Is this always the case?
NAMES_FOR_INDEX = {
0: ".init",
1: ".text",
7: ".extab",
8: ".extabindex",
9: ".ctors",
10: ".dtors",
11: ".rodata",
12: ".data",
13: ".sdata",
14: ".sdata2",
}
def read_section(index, offset, addr, size, buffer):
""" Reading section from DOL file """
type = "text"
if index >= 7:
type = "data"
name = NAMES_FOR_INDEX[index] if index in NAMES_FOR_INDEX else None
return Section(offset, addr, size, size, type, name, buffer[offset:][:size])
def read(buffer):
"""
Parse DOL file from buffer
"""
# TODO: Error checking
offsets = struct.unpack('>' + 'I' * 18, buffer[0x00:][:0x48])
addresses = struct.unpack('>' + 'I' * 18, buffer[0x48:][:0x48])
sizes = struct.unpack('>' + 'I' * 18, buffer[0x90:][:0x48])
bss_address, bss_size, entry_point = struct.unpack(
'>III', buffer[0xD8:][:0x0C])
sections = [
read_section(i, offsets[i], addresses[i], sizes[i], buffer)
for i in range(18)
if offsets[i] > 0 and addresses[i] > 0 and sizes[i] > 0
]
# The DOL format will align section to 32 bytes. We can get the "real" sizes of
# each section by reading the _rom_copy_info and _bss_init_info symbols from
# the .init section, as these are written by the linker before converting to DOL.
# @game NOTE: for other games this may or may not work and the offsets would probably be different
_rom_copy_info_offset = 0x80005544 - sections[0].addr + sections[0].offset
_bss_init_info_offset = 0x800055c8 - sections[0].addr + sections[0].offset
_rom_copy_info = dict()
while True:
src, dst, size = struct.unpack(
'>III', buffer[_rom_copy_info_offset:][:12])
if size == 0:
break
_rom_copy_info[dst] = size
_rom_copy_info_offset += 12
_bss_init_info = dict()
while True:
dst, size = struct.unpack('>II', buffer[_bss_init_info_offset:][:8])
if size == 0:
break
_bss_init_info[dst] = size
_bss_init_info_offset += 8
# Calculate the .bss, .sbss, and .sbss2 sections
temp_addr = bss_address
for section in sections:
if temp_addr < section.addr:
size = section.addr - temp_addr
sections += [Section(0, temp_addr, size, size, "bss", ".bss", None)]
temp_addr = section.addr + section.size
break
for section in sections:
if temp_addr < section.addr:
size = section.addr - temp_addr
sections += [Section(0, temp_addr, size, size, "bss", ".sbss", None)]
temp_addr = section.addr + section.size
break
sbss2_size = bss_size - (temp_addr - bss_address)
sections += [Section(0, temp_addr, sbss2_size, sbss2_size, "bss", ".sbss2", None)]
sections.sort(key=lambda x: x.addr)
for section in sections:
if section.addr in _rom_copy_info:
section.size = _rom_copy_info[section.addr]
if section.addr in _bss_init_info:
section.size = _bss_init_info[section.addr]
return DOL(sections, bss_address, bss_size, entry_point)