From e756a5c887dd63d7be6370085dfa8d7bcac831e9 Mon Sep 17 00:00:00 2001 From: ApfelTeeSaft <91074565+ApfelTeeSaft@users.noreply.github.com> Date: Sat, 30 May 2026 20:36:46 +0200 Subject: [PATCH] init --- dol.py | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 dol.py diff --git a/dol.py b/dol.py new file mode 100644 index 0000000..50070ec --- /dev/null +++ b/dol.py @@ -0,0 +1,243 @@ +import struct + +import idaapi +import ida_idp +import ida_loader +import ida_segment +import ida_entry +import ida_name +import ida_ua +import ida_bytes + +DOL_FORMAT_NAME = "Nintendo GameCube/Wii DOL" +PPC_PROCESSOR = "ppc" + +TEXT_COUNT = 7 +DATA_COUNT = 11 +DOL_HEADER_SIZE = 0x100 + + +def read_u32be(li): + b = li.read(4) + if len(b) != 4: + raise EOFError("Unexpected EOF") + return struct.unpack(">I", b)[0] + + +def get_file_size(li): + cur = li.tell() + li.seek(0, 2) + size = li.tell() + li.seek(cur, 0) + return size + + +def is_mem_addr(addr): + # GameCube/Wii MEM1 + common Wii MEM2 range. + return ( + 0x80000000 <= addr < 0x81800000 or + 0x90000000 <= addr < 0x94000000 + ) + + +def parse_dol(li): + li.seek(0) + + text_offsets = [read_u32be(li) for _ in range(TEXT_COUNT)] + data_offsets = [read_u32be(li) for _ in range(DATA_COUNT)] + + text_addrs = [read_u32be(li) for _ in range(TEXT_COUNT)] + data_addrs = [read_u32be(li) for _ in range(DATA_COUNT)] + + text_sizes = [read_u32be(li) for _ in range(TEXT_COUNT)] + data_sizes = [read_u32be(li) for _ in range(DATA_COUNT)] + + bss_addr = read_u32be(li) + bss_size = read_u32be(li) + entry = read_u32be(li) + + return { + "text_offsets": text_offsets, + "data_offsets": data_offsets, + "text_addrs": text_addrs, + "data_addrs": data_addrs, + "text_sizes": text_sizes, + "data_sizes": data_sizes, + "bss_addr": bss_addr, + "bss_size": bss_size, + "entry": entry, + } + + +def validate_dol(li): + size = get_file_size(li) + + if size < DOL_HEADER_SIZE: + return False + + li.seek(0) + magic = li.read(4) + + # Reject obvious non-DOL containers/files. + if magic == b"\x55\xAA\x38\x2D": # U8 archive + return False + if magic in (b"Yaz0", b"LZ77", b"RARC", b"RIFF", b"\x89PNG"): + return False + if magic[:2] in (b"PK", b"BM"): + return False + + try: + h = parse_dol(li) + except Exception: + return False + + sections = [] + + for i in range(TEXT_COUNT): + off = h["text_offsets"][i] + addr = h["text_addrs"][i] + sz = h["text_sizes"][i] + + if sz == 0: + continue + + if off < DOL_HEADER_SIZE or off >= size: + return False + if off + sz > size: + return False + if off % 4 != 0: + return False + if not is_mem_addr(addr): + return False + if not is_mem_addr(addr + sz - 1): + return False + + sections.append(("text", i, off, addr, sz)) + + for i in range(DATA_COUNT): + off = h["data_offsets"][i] + addr = h["data_addrs"][i] + sz = h["data_sizes"][i] + + if sz == 0: + continue + + if off < DOL_HEADER_SIZE or off >= size: + return False + if off + sz > size: + return False + if off % 4 != 0: + return False + if not is_mem_addr(addr): + return False + if not is_mem_addr(addr + sz - 1): + return False + + sections.append(("data", i, off, addr, sz)) + + if len(sections) < 2: + return False + + # Entry must point into a text section. + entry_ok = False + for kind, _idx, _off, addr, sz in sections: + if kind == "text" and addr <= h["entry"] < addr + sz: + entry_ok = True + break + + if not entry_ok: + return False + + # File ranges should not overlap. + ranges = sorted((off, off + sz) for _kind, _idx, off, _addr, sz in sections) + for a, b in zip(ranges, ranges[1:]): + if a[1] > b[0]: + return False + + return True + + +def accept_file(li, filename): + if validate_dol(li): + return { + "format": DOL_FORMAT_NAME, + "processor": PPC_PROCESSOR, + } + + return 0 + + +def add_segment(start, end, name, seg_class): + ida_segment.add_segm(0, start, end, name, seg_class) + + +def load_file(li, neflags, fmt): + print("[DOL] Loading Nintendo GameCube/Wii DOL") + + ida_idp.set_processor_type(PPC_PROCESSOR, ida_idp.SETPROC_LOADER) + + h = parse_dol(li) + + for i in range(TEXT_COUNT): + off = h["text_offsets"][i] + addr = h["text_addrs"][i] + sz = h["text_sizes"][i] + + if sz == 0: + continue + + end = addr + sz + name = ".text%d" % i + + print("[DOL] text%d file=0x%X addr=0x%X size=0x%X" % (i, off, addr, sz)) + + add_segment(addr, end, name, "CODE") + li.file2base(off, addr, end, True) + + seg = ida_segment.getseg(addr) + if seg: + seg.perm = ida_segment.SEGPERM_READ | ida_segment.SEGPERM_EXEC + ida_segment.update_segm(seg) + + ida_ua.create_insn(addr) + + for i in range(DATA_COUNT): + off = h["data_offsets"][i] + addr = h["data_addrs"][i] + sz = h["data_sizes"][i] + + if sz == 0: + continue + + end = addr + sz + name = ".data%d" % i + + print("[DOL] data%d file=0x%X addr=0x%X size=0x%X" % (i, off, addr, sz)) + + add_segment(addr, end, name, "DATA") + li.file2base(off, addr, end, True) + + seg = ida_segment.getseg(addr) + if seg: + seg.perm = ida_segment.SEGPERM_READ | ida_segment.SEGPERM_WRITE + ida_segment.update_segm(seg) + + if h["bss_size"] != 0 and is_mem_addr(h["bss_addr"]): + bss_start = h["bss_addr"] + bss_end = h["bss_addr"] + h["bss_size"] + + print("[DOL] bss addr=0x%X size=0x%X" % (bss_start, h["bss_size"])) + + add_segment(bss_start, bss_end, ".bss", "BSS") + + seg = ida_segment.getseg(bss_start) + if seg: + seg.perm = ida_segment.SEGPERM_READ | ida_segment.SEGPERM_WRITE + ida_segment.update_segm(seg) + + ida_entry.add_entry(h["entry"], h["entry"], "entry", True) + ida_name.set_name(h["entry"], "entry", ida_name.SN_CHECK) + + print("[DOL] Entry point: 0x%08X" % h["entry"]) + + return 1 \ No newline at end of file