1
0
Fork 0
This repository has been archived on 2024-02-06. You can view files and clone it, but cannot push or open issues or pull requests.
GeckoLoader/lib/dolreader.py
2020-08-16 15:04:47 -05:00

252 lines
No EOL
8.9 KiB
Python

from io import BytesIO
from access import *
class DolFile(object):
def __init__(self, f):
self.rawData = BytesIO(f.read())
fileoffset = 0
addressoffset = 0x48
sizeoffset = 0x90
self.textSections = []
self.dataSections = []
self.maxTextSections = 7
self.maxDataSections = 11
nomoretext = False
nomoredata = False
self._current_end = None
# Read text and data section addresses and sizes
for i in range(18):
f.seek(fileoffset + (i << 2))
offset = read_uint32(f)
f.seek(addressoffset + (i << 2))
address = read_uint32(f)
f.seek(sizeoffset + (i << 2))
size = read_uint32(f)
if i <= 6:
if offset == 0:
nomoretext = True
elif not nomoretext:
self.textSections.append((offset, address, size))
# print("text{0}".format(i), hex(offset), hex(address), hex(size))
else:
#datanum = i - 7
if offset == 0:
nomoredata = True
elif not nomoredata:
self.dataSections.append((offset, address, size))
# print("data{0}".format(datanum), hex(offset), hex(address), hex(size))
f.seek(0xD8)
self.bssOffset = read_uint32(f)
self.bssSize = read_uint32(f)
self.entryPoint = read_uint32(f)
self.bss = BytesIO(self.rawData.getbuffer()[self.bssOffset:self.bssOffset + self.bssSize])
self.currAddr = self.textSections[0][1]
self.seek(self.currAddr)
f.seek(0)
# Internal function for
def resolve_address(self, gcAddr):
for offset, address, size in self.textSections:
if address <= gcAddr < address+size:
return offset, address, size
for offset, address, size in self.dataSections:
if address <= gcAddr < address+size:
return offset, address, size
raise RuntimeError(f"Unmapped address: 0x{gcAddr:X}")
def seek_safe_address(self, gcAddr, buffer=0):
for offset, address, size in self.textSections:
if address > (gcAddr + buffer) or address+size < gcAddr:
continue
gcAddr = address + size
for offset, address, size in self.dataSections:
if address > (gcAddr + buffer) or address+size < gcAddr:
continue
gcAddr = address + size
return gcAddr
# Unsupported: Reading an entire dol file
# Assumption: A read should not go beyond the current section
def read(self, size):
if self.currAddr + size > self._current_end:
raise RuntimeError("Read goes over current section")
self.currAddr += size
return self.rawData.read(size)
# Assumption: A write should not go beyond the current section
def write(self, data):
if self.currAddr + len(data) > self._current_end:
raise RuntimeError("Write goes over current section")
self.rawData.write(data)
self.currAddr += len(data)
def seek(self, where, whence=0):
if whence == 0:
offset, gc_start, gc_size = self.resolve_address(where)
self.rawData.seek(offset + (where-gc_start))
self.currAddr = where
self._current_end = gc_start + gc_size
elif whence == 1:
offset, gc_start, gc_size = self.resolve_address(self.currAddr + where)
self.rawData.seek(offset + ((self.currAddr + where)-gc_start))
self.currAddr += where
self._current_end = gc_start + gc_size
else:
raise RuntimeError("Unsupported whence type '{}'".format(whence))
def tell(self):
return self.currAddr
def save(self, f):
f.seek(0)
f.write(self.rawData.getbuffer())
def get_size(self):
oldpos = self.rawData.tell()
self.rawData.seek(0, 2)
size = self.rawData.tell()
self.rawData.seek(oldpos)
return size
def get_alignment(self, alignment):
size = self.get_size()
if size % alignment != 0:
return alignment - (size % alignment)
else:
return 0
def align(self, alignment):
oldpos = self.rawData.tell()
self.rawData.seek(0, 2)
self.rawData.write(bytes.fromhex("00" * self.get_alignment(alignment)))
self.rawData.seek(oldpos)
def append_text_sections(self, sections_list: list):
offset = len(self.textSections) << 2
if len(sections_list) + len(self.textSections) > self.maxTextSections:
return False
'''Write offset to each section in DOL file header'''
self.rawData.seek(offset)
for section_offset in sections_list:
self.rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file
self.rawData.seek(0x48 + offset)
'''Write in game memory addresses for each section in DOL file header'''
for section_addr in sections_list:
self.rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
size_list = []
for i, section_offset in enumerate(sections_list, start=1):
if i > len(sections_list) - 1:
size_list.append(self.get_size() - section_offset[1])
else:
size_list.append(sections_list[i][1] - section_offset[1])
'''Write size of each section into DOL file header'''
self.rawData.seek(0x90 + offset)
for size in size_list:
self.rawData.write(size.to_bytes(4, byteorder='big', signed=False))
return True
def append_data_sections(self, sections_list: list):
offset = len(self.dataSections) << 2
if len(sections_list) + len(self.dataSections) > self.maxDataSections:
return False
'''Write offset to each section in DOL file header'''
self.rawData.seek(offset)
for section_offset in sections_list:
self.rawData.write(section_offset[1].to_bytes(4, byteorder='big', signed=False)) #offset in file
self.rawData.seek(0x64 + offset)
'''Write in game memory addresses for each section in DOL file header'''
for section_addr in sections_list:
self.rawData.write(section_addr[0].to_bytes(4, byteorder='big', signed=False)) #absolute address in game
'''Get size of GeckoLoader + gecko codes, and the codehandler'''
size_list = []
for i, section_offset in enumerate(sections_list, start=1):
if i > len(sections_list) - 1:
size_list.append(self.get_size() - section_offset[1])
else:
size_list.append(sections_list[i][1] - section_offset[1])
'''Write size of each section into DOL file header'''
self.rawData.seek(0xAC + offset)
for size in size_list:
self.rawData.write(size.to_bytes(4, byteorder='big', signed=False))
return True
def set_entry_point(self, address):
oldpos = self.rawData.tell()
self.rawData.seek(0xE0)
self.rawData.write(bytes.fromhex('{:08X}'.format(address)))
self.rawData.seek(oldpos)
def insert_branch(self, to, _from, lk=0):
self.write(((to - _from) & 0x3FFFFFF | 0x48000000 | lk).to_bytes(4, byteorder='big', signed=False))
if __name__ == "__main__":
# Example usage (reading some enemy info from the Pikmin 2 demo from US demo disc 17)
def read_string(f):
start = f.tell()
length = 0
while f.read(1) != b"\x00":
length += 1
if length > 100:
break
f.seek(start)
return f.read(length)
entries = []
with open("main.dol", "rb") as f:
dol = DolFile(f)
start = 0x804ac478 # memory address to start of enemy info table.
for i in range(100):
dol.seek(start+0x34*i, 0)
# string offset would normally be pointing to a location in RAM and thus
# wouldn't be suitable as a file offset but because the seek function of DolFile
# takes into account the memory address at which the data sections of the dol file
# is loaded, we can use the string offset directly..
stringoffset = read_uint32(dol)
identifier = read_ubyte(dol)
dol.seek(stringoffset, 0)
name = read_string(dol)
entries.append((identifier,i, name, hex(stringoffset)))
entries.sort(key=lambda x: x[0])
for val in entries:
print(hex(val[0]), val)