mirror of https://github.com/zeldaret/oot.git
				
				
				
			
		
			
				
	
	
		
			568 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
			
		
		
	
	
			568 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
#!/usr/bin/env python3
 | 
						|
 | 
						|
# SPDX-FileCopyrightText: © 2024 ZeldaRET
 | 
						|
# SPDX-License-Identifier: CC0-1.0
 | 
						|
 | 
						|
# IDO symbol table parser for BSS ordering debugging. The compiler will assign
 | 
						|
# "block numbers" or "dense numbers" to symbols in order as it encounters them
 | 
						|
# in the source file, and the BSS section is sorted by this block number mod 256.
 | 
						|
# This script dumps the compiler-generated symbol table so you can see which
 | 
						|
# block numbers are assigned to each symbol.
 | 
						|
#
 | 
						|
# Resources:
 | 
						|
#   https://hackmd.io/@Roman971/BJ2DOyhBa
 | 
						|
#   https://github.com/decompals/ultralib/blob/main/tools/mdebug.py
 | 
						|
#   https://www.cs.unibo.it/~solmi/teaching/arch_2002-2003/AssemblyLanguageProgDoc.pdf
 | 
						|
#   https://github.com/decompals/IDO/blob/main/IDO_7.1/dist/compiler_eoe/usr/include/sym.h
 | 
						|
#   https://github.com/Synray/ido-ucode-utils
 | 
						|
 | 
						|
from __future__ import annotations
 | 
						|
 | 
						|
import argparse
 | 
						|
from dataclasses import dataclass
 | 
						|
import itertools
 | 
						|
from pathlib import Path
 | 
						|
import platform
 | 
						|
import struct
 | 
						|
import subprocess
 | 
						|
import shlex
 | 
						|
import sys
 | 
						|
from typing import Optional, Tuple
 | 
						|
 | 
						|
 | 
						|
class Header:
 | 
						|
    SIZE = 0x60
 | 
						|
 | 
						|
    def __init__(self, data):
 | 
						|
        (
 | 
						|
            self.magic,
 | 
						|
            self.vstamp,
 | 
						|
            self.ilineMax,
 | 
						|
            self.cbLine,
 | 
						|
            self.cbLineOffset,
 | 
						|
            self.idnMax,
 | 
						|
            self.cbDnOffset,
 | 
						|
            self.ipdMax,
 | 
						|
            self.cbPdOffset,
 | 
						|
            self.isymMax,
 | 
						|
            self.cbSymOffset,
 | 
						|
            self.ioptMax,
 | 
						|
            self.cbOptOffset,
 | 
						|
            self.iauxMax,
 | 
						|
            self.cbAuxOffset,
 | 
						|
            self.issMax,
 | 
						|
            self.cbSsOffset,
 | 
						|
            self.issExtMax,
 | 
						|
            self.cbSsExtOffset,
 | 
						|
            self.ifdMax,
 | 
						|
            self.cbFdOffset,
 | 
						|
            self.crfd,
 | 
						|
            self.cbRfdOffset,
 | 
						|
            self.iextMax,
 | 
						|
            self.cbExtOffset,
 | 
						|
        ) = struct.unpack(">2H23I", data)
 | 
						|
 | 
						|
 | 
						|
class FileDescriptor:
 | 
						|
    SIZE = 0x48
 | 
						|
 | 
						|
    def __init__(self, data):
 | 
						|
        (
 | 
						|
            self.adr,
 | 
						|
            self.rss,
 | 
						|
            self.issBase,
 | 
						|
            self.cbSs,
 | 
						|
            self.isymBase,
 | 
						|
            self.csym,
 | 
						|
            self.ilineBase,
 | 
						|
            self.cline,
 | 
						|
            self.ioptBase,
 | 
						|
            self.copt,
 | 
						|
            self.ipdFirst,
 | 
						|
            self.cpd,
 | 
						|
            self.iauxBase,
 | 
						|
            self.caux,
 | 
						|
            self.rfdBase,
 | 
						|
            self.crfd,
 | 
						|
            self.flags,
 | 
						|
            self.cbLineOffset,
 | 
						|
            self.cbLine,
 | 
						|
        ) = struct.unpack(">10I2H7I", data)
 | 
						|
 | 
						|
 | 
						|
class Symbol:
 | 
						|
    SIZE = 0xC
 | 
						|
 | 
						|
    def __init__(self, data):
 | 
						|
        (
 | 
						|
            self.iss,
 | 
						|
            self.value,
 | 
						|
            self.flags,
 | 
						|
        ) = struct.unpack(">3I", data)
 | 
						|
 | 
						|
    def symbol_type(self):
 | 
						|
        symbol_types = {
 | 
						|
            0: "nil",
 | 
						|
            1: "global",
 | 
						|
            2: "static",
 | 
						|
            3: "param",
 | 
						|
            4: "local",
 | 
						|
            5: "label",
 | 
						|
            6: "proc",
 | 
						|
            7: "block",
 | 
						|
            8: "end",
 | 
						|
            9: "member",
 | 
						|
            10: "typedef",
 | 
						|
            11: "file",
 | 
						|
            14: "staticproc",
 | 
						|
            15: "constant",
 | 
						|
            26: "struct",
 | 
						|
            27: "union",
 | 
						|
            28: "enum",
 | 
						|
            34: "indirect",
 | 
						|
        }
 | 
						|
        return symbol_types[self.flags >> 26]
 | 
						|
 | 
						|
    def symbol_storage_class(self):
 | 
						|
        symbol_storage_classes = {
 | 
						|
            0: "nil",
 | 
						|
            1: "text",
 | 
						|
            2: "data",
 | 
						|
            3: "bss",
 | 
						|
            4: "register",
 | 
						|
            5: "abs",
 | 
						|
            6: "undefined",
 | 
						|
            8: "bits",
 | 
						|
            9: "dbx",
 | 
						|
            10: "regimage",
 | 
						|
            11: "info",
 | 
						|
        }
 | 
						|
        return symbol_storage_classes[(self.flags >> 21) & 0x1F]
 | 
						|
 | 
						|
 | 
						|
class ExternalSymbol:
 | 
						|
    SIZE = 0x10
 | 
						|
 | 
						|
    def __init__(self, data):
 | 
						|
        (
 | 
						|
            self.flags,
 | 
						|
            self.ifd,
 | 
						|
        ) = struct.unpack(">2H", data[0:4])
 | 
						|
        self.asym = Symbol(data[4:])
 | 
						|
 | 
						|
 | 
						|
def read_entry(data, base, offset, size):
 | 
						|
    start = base + offset * size
 | 
						|
    return data[start : start + size]
 | 
						|
 | 
						|
 | 
						|
def read_string(data, start):
 | 
						|
    size = 0
 | 
						|
    while data[start + size] != 0:
 | 
						|
        size += 1
 | 
						|
    return data[start : start + size].decode("ascii")
 | 
						|
 | 
						|
 | 
						|
@dataclass
 | 
						|
class SymbolTableEntry:
 | 
						|
    symbol: Optional[Symbol]
 | 
						|
    name: str
 | 
						|
    extern: bool
 | 
						|
 | 
						|
 | 
						|
def parse_symbol_table(data: bytes) -> list[SymbolTableEntry]:
 | 
						|
    header = Header(data[0 : Header.SIZE])
 | 
						|
 | 
						|
    # File descriptors
 | 
						|
    fds = []
 | 
						|
    for i in range(header.ifdMax):
 | 
						|
        fds.append(
 | 
						|
            FileDescriptor(read_entry(data, header.cbFdOffset, i, FileDescriptor.SIZE))
 | 
						|
        )
 | 
						|
 | 
						|
    # Symbol identifiers ("dense numbers")
 | 
						|
    entries = []
 | 
						|
    for i in range(header.idnMax):
 | 
						|
        ifd, isym = struct.unpack(">II", read_entry(data, header.cbDnOffset, i, 8))
 | 
						|
 | 
						|
        if isym == 0xFFFFF:
 | 
						|
            sym = None
 | 
						|
            sym_name = ""
 | 
						|
            extern = False
 | 
						|
        else:
 | 
						|
            extern = ifd == 0x7FFFFFFF
 | 
						|
            if extern:
 | 
						|
                ext = ExternalSymbol(
 | 
						|
                    read_entry(data, header.cbExtOffset, isym, ExternalSymbol.SIZE)
 | 
						|
                )
 | 
						|
                sym = ext.asym
 | 
						|
                sym_name = read_string(data, header.cbSsExtOffset + sym.iss)
 | 
						|
            else:
 | 
						|
                fd = fds[ifd]
 | 
						|
                sym = Symbol(
 | 
						|
                    read_entry(
 | 
						|
                        data, header.cbSymOffset, fd.isymBase + isym, Symbol.SIZE
 | 
						|
                    )
 | 
						|
                )
 | 
						|
                sym_name = read_string(data, header.cbSsOffset + fd.issBase + sym.iss)
 | 
						|
 | 
						|
        entries.append(SymbolTableEntry(sym, sym_name, extern))
 | 
						|
 | 
						|
    return entries
 | 
						|
 | 
						|
 | 
						|
def print_symbol_table(symbol_table: list[SymbolTableEntry]):
 | 
						|
    print(f"block [mod 256]: linkage  type        class      name")
 | 
						|
    for i, entry in enumerate(symbol_table):
 | 
						|
        if not entry.symbol:
 | 
						|
            # TODO: is this always a string?
 | 
						|
            st = "string"
 | 
						|
            sc = ""
 | 
						|
        else:
 | 
						|
            st = entry.symbol.symbol_type()
 | 
						|
            sc = entry.symbol.symbol_storage_class()
 | 
						|
        print(
 | 
						|
            f'{i:>9} [{i%256:>3}]: {"extern" if entry.extern else "":<7}  {st:<10}  {sc:<9}  {entry.name:<40}'
 | 
						|
        )
 | 
						|
 | 
						|
 | 
						|
@dataclass
 | 
						|
class UcodeOp:
 | 
						|
    opcode: int
 | 
						|
    opcode_name: str
 | 
						|
    mtype: int
 | 
						|
    dtype: int
 | 
						|
    lexlev: int
 | 
						|
    i1: int
 | 
						|
    args: list[int]
 | 
						|
    const: Optional[int]
 | 
						|
    string: Optional[bytes]
 | 
						|
 | 
						|
 | 
						|
@dataclass
 | 
						|
class UcodeOpInfo:
 | 
						|
    opcode: int
 | 
						|
    name: str
 | 
						|
    length: int
 | 
						|
    has_const: bool
 | 
						|
 | 
						|
 | 
						|
UCODE_OP_INFO = [
 | 
						|
    UcodeOpInfo(0x00, "abs", 2, False),
 | 
						|
    UcodeOpInfo(0x01, "add", 2, False),
 | 
						|
    UcodeOpInfo(0x02, "adj", 4, False),
 | 
						|
    UcodeOpInfo(0x03, "aent", 4, False),
 | 
						|
    UcodeOpInfo(0x04, "and", 2, False),
 | 
						|
    UcodeOpInfo(0x05, "aos", 2, False),
 | 
						|
    UcodeOpInfo(0x06, "asym", 4, False),
 | 
						|
    UcodeOpInfo(0x07, "bgn", 4, False),
 | 
						|
    UcodeOpInfo(0x08, "bgnb", 2, False),
 | 
						|
    UcodeOpInfo(0x09, "bsub", 2, False),
 | 
						|
    UcodeOpInfo(0x0A, "cg1", 2, False),
 | 
						|
    UcodeOpInfo(0x0B, "cg2", 2, False),
 | 
						|
    UcodeOpInfo(0x0C, "chkh", 2, False),
 | 
						|
    UcodeOpInfo(0x0D, "chkl", 2, False),
 | 
						|
    UcodeOpInfo(0x0E, "chkn", 2, False),
 | 
						|
    UcodeOpInfo(0x0F, "chkt", 2, False),
 | 
						|
    UcodeOpInfo(0x10, "cia", 4, True),
 | 
						|
    UcodeOpInfo(0x11, "clab", 4, False),
 | 
						|
    UcodeOpInfo(0x12, "clbd", 2, False),
 | 
						|
    UcodeOpInfo(0x13, "comm", 4, True),
 | 
						|
    UcodeOpInfo(0x14, "csym", 4, False),
 | 
						|
    UcodeOpInfo(0x15, "ctrl", 4, False),
 | 
						|
    UcodeOpInfo(0x16, "cubd", 2, False),
 | 
						|
    UcodeOpInfo(0x17, "cup", 4, False),
 | 
						|
    UcodeOpInfo(0x18, "cvt", 4, False),
 | 
						|
    UcodeOpInfo(0x19, "cvtl", 2, False),
 | 
						|
    UcodeOpInfo(0x1A, "dec", 2, False),
 | 
						|
    UcodeOpInfo(0x1B, "def", 4, False),
 | 
						|
    UcodeOpInfo(0x1C, "dif", 4, False),
 | 
						|
    UcodeOpInfo(0x1D, "div", 2, False),
 | 
						|
    UcodeOpInfo(0x1E, "dup", 2, False),
 | 
						|
    UcodeOpInfo(0x1F, "end", 2, False),
 | 
						|
    UcodeOpInfo(0x20, "endb", 2, False),
 | 
						|
    UcodeOpInfo(0x21, "ent", 4, False),
 | 
						|
    UcodeOpInfo(0x22, "ueof", 2, False),
 | 
						|
    UcodeOpInfo(0x23, "equ", 2, False),
 | 
						|
    UcodeOpInfo(0x24, "esym", 4, False),
 | 
						|
    UcodeOpInfo(0x25, "fill", 4, False),
 | 
						|
    UcodeOpInfo(0x26, "fjp", 2, False),
 | 
						|
    UcodeOpInfo(0x27, "fsym", 4, False),
 | 
						|
    UcodeOpInfo(0x28, "geq", 2, False),
 | 
						|
    UcodeOpInfo(0x29, "grt", 2, False),
 | 
						|
    UcodeOpInfo(0x2A, "gsym", 4, False),
 | 
						|
    UcodeOpInfo(0x2B, "hsym", 4, False),
 | 
						|
    UcodeOpInfo(0x2C, "icuf", 4, False),
 | 
						|
    UcodeOpInfo(0x2D, "idx", 2, False),
 | 
						|
    UcodeOpInfo(0x2E, "iequ", 4, False),
 | 
						|
    UcodeOpInfo(0x2F, "igeq", 4, False),
 | 
						|
    UcodeOpInfo(0x30, "igrt", 4, False),
 | 
						|
    UcodeOpInfo(0x31, "ijp", 2, False),
 | 
						|
    UcodeOpInfo(0x32, "ilda", 6, False),
 | 
						|
    UcodeOpInfo(0x33, "ildv", 4, False),
 | 
						|
    UcodeOpInfo(0x34, "ileq", 4, False),
 | 
						|
    UcodeOpInfo(0x35, "iles", 4, False),
 | 
						|
    UcodeOpInfo(0x36, "ilod", 4, False),
 | 
						|
    UcodeOpInfo(0x37, "inc", 2, False),
 | 
						|
    UcodeOpInfo(0x38, "ineq", 4, False),
 | 
						|
    UcodeOpInfo(0x39, "init", 6, True),
 | 
						|
    UcodeOpInfo(0x3A, "inn", 4, False),
 | 
						|
    UcodeOpInfo(0x3B, "int", 4, False),
 | 
						|
    UcodeOpInfo(0x3C, "ior", 2, False),
 | 
						|
    UcodeOpInfo(0x3D, "isld", 4, False),
 | 
						|
    UcodeOpInfo(0x3E, "isst", 4, False),
 | 
						|
    UcodeOpInfo(0x3F, "istr", 4, False),
 | 
						|
    UcodeOpInfo(0x40, "istv", 4, False),
 | 
						|
    UcodeOpInfo(0x41, "ixa", 2, False),
 | 
						|
    UcodeOpInfo(0x42, "lab", 4, False),
 | 
						|
    UcodeOpInfo(0x43, "lbd", 2, False),
 | 
						|
    UcodeOpInfo(0x44, "lbdy", 2, False),
 | 
						|
    UcodeOpInfo(0x45, "lbgn", 2, False),
 | 
						|
    UcodeOpInfo(0x46, "lca", 4, True),
 | 
						|
    UcodeOpInfo(0x47, "lda", 6, False),
 | 
						|
    UcodeOpInfo(0x48, "ldap", 2, False),
 | 
						|
    UcodeOpInfo(0x49, "ldc", 4, True),
 | 
						|
    UcodeOpInfo(0x4A, "ldef", 4, False),
 | 
						|
    UcodeOpInfo(0x4B, "ldsp", 2, False),
 | 
						|
    UcodeOpInfo(0x4C, "lend", 2, False),
 | 
						|
    UcodeOpInfo(0x4D, "leq", 2, False),
 | 
						|
    UcodeOpInfo(0x4E, "les", 2, False),
 | 
						|
    UcodeOpInfo(0x4F, "lex", 2, False),
 | 
						|
    UcodeOpInfo(0x50, "lnot", 2, False),
 | 
						|
    UcodeOpInfo(0x51, "loc", 2, False),
 | 
						|
    UcodeOpInfo(0x52, "lod", 4, False),
 | 
						|
    UcodeOpInfo(0x53, "lsym", 4, False),
 | 
						|
    UcodeOpInfo(0x54, "ltrm", 2, False),
 | 
						|
    UcodeOpInfo(0x55, "max", 2, False),
 | 
						|
    UcodeOpInfo(0x56, "min", 2, False),
 | 
						|
    UcodeOpInfo(0x57, "mod", 2, False),
 | 
						|
    UcodeOpInfo(0x58, "mov", 4, False),
 | 
						|
    UcodeOpInfo(0x59, "movv", 2, False),
 | 
						|
    UcodeOpInfo(0x5A, "mpmv", 4, False),
 | 
						|
    UcodeOpInfo(0x5B, "mpy", 2, False),
 | 
						|
    UcodeOpInfo(0x5C, "mst", 2, False),
 | 
						|
    UcodeOpInfo(0x5D, "mus", 4, False),
 | 
						|
    UcodeOpInfo(0x5E, "neg", 2, False),
 | 
						|
    UcodeOpInfo(0x5F, "neq", 2, False),
 | 
						|
    UcodeOpInfo(0x60, "nop", 2, False),
 | 
						|
    UcodeOpInfo(0x61, "not", 2, False),
 | 
						|
    UcodeOpInfo(0x62, "odd", 2, False),
 | 
						|
    UcodeOpInfo(0x63, "optn", 4, False),
 | 
						|
    UcodeOpInfo(0x64, "par", 4, False),
 | 
						|
    UcodeOpInfo(0x65, "pdef", 4, False),
 | 
						|
    UcodeOpInfo(0x66, "pmov", 4, False),
 | 
						|
    UcodeOpInfo(0x67, "pop", 2, False),
 | 
						|
    UcodeOpInfo(0x68, "regs", 4, False),
 | 
						|
    UcodeOpInfo(0x69, "rem", 2, False),
 | 
						|
    UcodeOpInfo(0x6A, "ret", 2, False),
 | 
						|
    UcodeOpInfo(0x6B, "rlda", 4, False),
 | 
						|
    UcodeOpInfo(0x6C, "rldc", 4, True),
 | 
						|
    UcodeOpInfo(0x6D, "rlod", 4, False),
 | 
						|
    UcodeOpInfo(0x6E, "rnd", 4, False),
 | 
						|
    UcodeOpInfo(0x6F, "rpar", 4, False),
 | 
						|
    UcodeOpInfo(0x70, "rstr", 4, False),
 | 
						|
    UcodeOpInfo(0x71, "sdef", 4, False),
 | 
						|
    UcodeOpInfo(0x72, "sgs", 4, False),
 | 
						|
    UcodeOpInfo(0x73, "shl", 2, False),
 | 
						|
    UcodeOpInfo(0x74, "shr", 2, False),
 | 
						|
    UcodeOpInfo(0x75, "sign", 2, False),
 | 
						|
    UcodeOpInfo(0x76, "sqr", 2, False),
 | 
						|
    UcodeOpInfo(0x77, "sqrt", 2, False),
 | 
						|
    UcodeOpInfo(0x78, "ssym", 4, True),
 | 
						|
    UcodeOpInfo(0x79, "step", 2, False),
 | 
						|
    UcodeOpInfo(0x7A, "stp", 2, False),
 | 
						|
    UcodeOpInfo(0x7B, "str", 4, False),
 | 
						|
    UcodeOpInfo(0x7C, "stsp", 2, False),
 | 
						|
    UcodeOpInfo(0x7D, "sub", 2, False),
 | 
						|
    UcodeOpInfo(0x7E, "swp", 4, False),
 | 
						|
    UcodeOpInfo(0x7F, "tjp", 2, False),
 | 
						|
    UcodeOpInfo(0x80, "tpeq", 2, False),
 | 
						|
    UcodeOpInfo(0x81, "tpge", 2, False),
 | 
						|
    UcodeOpInfo(0x82, "tpgt", 2, False),
 | 
						|
    UcodeOpInfo(0x83, "tple", 2, False),
 | 
						|
    UcodeOpInfo(0x84, "tplt", 2, False),
 | 
						|
    UcodeOpInfo(0x85, "tpne", 2, False),
 | 
						|
    UcodeOpInfo(0x86, "typ", 4, False),
 | 
						|
    UcodeOpInfo(0x87, "ubd", 2, False),
 | 
						|
    UcodeOpInfo(0x88, "ujp", 2, False),
 | 
						|
    UcodeOpInfo(0x89, "unal", 2, False),
 | 
						|
    UcodeOpInfo(0x8A, "uni", 4, False),
 | 
						|
    UcodeOpInfo(0x8B, "vreg", 4, False),
 | 
						|
    UcodeOpInfo(0x8C, "xjp", 8, False),
 | 
						|
    UcodeOpInfo(0x8D, "xor", 2, False),
 | 
						|
    UcodeOpInfo(0x8E, "xpar", 2, False),
 | 
						|
    UcodeOpInfo(0x8F, "mtag", 2, False),
 | 
						|
    UcodeOpInfo(0x90, "alia", 2, False),
 | 
						|
    UcodeOpInfo(0x91, "ildi", 4, False),
 | 
						|
    UcodeOpInfo(0x92, "isti", 4, False),
 | 
						|
    UcodeOpInfo(0x93, "irld", 4, False),
 | 
						|
    UcodeOpInfo(0x94, "irst", 4, False),
 | 
						|
    UcodeOpInfo(0x95, "ldrc", 4, False),
 | 
						|
    UcodeOpInfo(0x96, "msym", 4, False),
 | 
						|
    UcodeOpInfo(0x97, "rcuf", 4, False),
 | 
						|
    UcodeOpInfo(0x98, "ksym", 4, False),
 | 
						|
    UcodeOpInfo(0x99, "osym", 4, False),
 | 
						|
    UcodeOpInfo(0x9A, "irlv", 2, False),
 | 
						|
    UcodeOpInfo(0x9B, "irsv", 2, False),
 | 
						|
]
 | 
						|
 | 
						|
 | 
						|
def parse_ucode(ucode: bytes) -> list[UcodeOp]:
 | 
						|
    ops = []
 | 
						|
    pos = 0
 | 
						|
    while pos < len(ucode):
 | 
						|
        opcode = ucode[pos]
 | 
						|
        mtype = ucode[pos + 1] >> 5
 | 
						|
        dtype = ucode[pos + 1] & 0x1F
 | 
						|
        lexlev = int.from_bytes(ucode[pos + 2 : pos + 4], "big")
 | 
						|
        i1 = int.from_bytes(ucode[pos + 4 : pos + 8], "big")
 | 
						|
        pos += 8
 | 
						|
 | 
						|
        info = UCODE_OP_INFO[opcode]
 | 
						|
        size = 4 * info.length
 | 
						|
 | 
						|
        args = []
 | 
						|
        for _ in range(info.length - 2):
 | 
						|
            args.append(int.from_bytes(ucode[pos : pos + 4], "big"))
 | 
						|
            pos += 4
 | 
						|
 | 
						|
        const = None
 | 
						|
        string = None
 | 
						|
        if info.has_const:
 | 
						|
            const = int.from_bytes(ucode[pos : pos + 4], "big")
 | 
						|
            pos += 8
 | 
						|
            if dtype in (9, 12, 13, 14, 16) or info.name == "comm":
 | 
						|
                string = ucode[pos : pos + const]
 | 
						|
                pos += (const + 7) & ~7
 | 
						|
 | 
						|
        ops.append(
 | 
						|
            UcodeOp(
 | 
						|
                opcode,
 | 
						|
                info.name,
 | 
						|
                mtype,
 | 
						|
                dtype,
 | 
						|
                lexlev,
 | 
						|
                i1,
 | 
						|
                args,
 | 
						|
                const,
 | 
						|
                string,
 | 
						|
            )
 | 
						|
        )
 | 
						|
    return ops
 | 
						|
 | 
						|
 | 
						|
def print_ucode(ucode: list[UcodeOp]):
 | 
						|
    for op in ucode:
 | 
						|
        args = " ".join(f"0x{arg:X}" for arg in op.args)
 | 
						|
        print(
 | 
						|
            f"{op.opcode_name:<4} mtype={op.mtype:X} dtype={op.dtype:X} lexlev={op.lexlev} i1={op.i1} args={args}",
 | 
						|
            end="",
 | 
						|
        )
 | 
						|
        if op.const is not None:
 | 
						|
            print(f" const=0x{op.const:X}", end="")
 | 
						|
        if op.string is not None:
 | 
						|
            print(f" string={op.string!r}", end="")
 | 
						|
        print()
 | 
						|
 | 
						|
 | 
						|
def generate_make_log(oot_version: str) -> list[str]:
 | 
						|
    is_macos = platform.system() == "Darwin"
 | 
						|
    make = "gmake" if is_macos else "make"
 | 
						|
    make_command_line = [
 | 
						|
        make,
 | 
						|
        "--always-make",
 | 
						|
        "--dry-run",
 | 
						|
        f"VERSION={oot_version}",
 | 
						|
    ]
 | 
						|
    return subprocess.check_output(make_command_line).decode("utf-8").splitlines()
 | 
						|
 | 
						|
 | 
						|
def find_compiler_command_line(
 | 
						|
    make_log: list[str], filename: Path
 | 
						|
) -> Optional[list[str]]:
 | 
						|
    found = 0
 | 
						|
    for line in make_log:
 | 
						|
        parts = line.split()
 | 
						|
        if "./tools/preprocess.sh" in parts and "-o" in parts and str(filename) in parts:
 | 
						|
            compiler_command_line = parts
 | 
						|
            found += 1
 | 
						|
 | 
						|
    if found != 1:
 | 
						|
        return None
 | 
						|
 | 
						|
    return compiler_command_line
 | 
						|
 | 
						|
 | 
						|
def run_cfe(
 | 
						|
    command_line: list[str], keep_files: bool
 | 
						|
) -> Tuple[list[SymbolTableEntry], list[UcodeOp]]:
 | 
						|
    # Assume command line is of the form:
 | 
						|
    # python3 tools/preprocess.py [COMPILER] [COMPILER_ARGS] [INPUT_FILE]
 | 
						|
    input_file = Path(command_line[-1])
 | 
						|
    rest = command_line[:-1]
 | 
						|
 | 
						|
    stem = input_file.stem
 | 
						|
    symbol_table_file = Path(f"{stem}.T")
 | 
						|
    ucode_file = Path(f"{stem}.B")
 | 
						|
 | 
						|
    try:
 | 
						|
        # Invoke compiler
 | 
						|
        # -Hf stops compilation after cfe so we can inspect the symbol table
 | 
						|
        subprocess.run(rest + ["-Hf", input_file], check=True)
 | 
						|
 | 
						|
        # Read symbol table
 | 
						|
        symbol_table = parse_symbol_table(symbol_table_file.read_bytes())
 | 
						|
        ucode = parse_ucode(ucode_file.read_bytes())
 | 
						|
        return (symbol_table, ucode)
 | 
						|
    finally:
 | 
						|
        # Cleanup
 | 
						|
        if not keep_files:
 | 
						|
            symbol_table_file.unlink(missing_ok=True)
 | 
						|
            ucode_file.unlink(missing_ok=True)
 | 
						|
 | 
						|
 | 
						|
def main():
 | 
						|
    parser = argparse.ArgumentParser(
 | 
						|
        description="Dump IDO symbol table for debugging BSS ordering"
 | 
						|
    )
 | 
						|
    parser.add_argument("filename", metavar="FILE", type=Path, help="C source file")
 | 
						|
    parser.add_argument(
 | 
						|
        "-v",
 | 
						|
        "--version",
 | 
						|
        dest="oot_version",
 | 
						|
        type=str,
 | 
						|
        default="gc-eu-mq-dbg",
 | 
						|
        help="OOT version (default: gc-eu-mq-dbg)",
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--print-ucode", action="store_true", help="Print cfe ucode output"
 | 
						|
    )
 | 
						|
    parser.add_argument(
 | 
						|
        "--keep-files",
 | 
						|
        action="store_true",
 | 
						|
        help="Keep temporary files (symbol table and ucode)",
 | 
						|
    )
 | 
						|
 | 
						|
    args = parser.parse_args()
 | 
						|
 | 
						|
    print(f"Running make to find compiler command line ...", file=sys.stderr)
 | 
						|
    make_log = generate_make_log(args.oot_version)
 | 
						|
 | 
						|
    command_line = find_compiler_command_line(make_log, args.filename)
 | 
						|
    if command_line is None:
 | 
						|
        print(
 | 
						|
            f"Error: could not determine compiler command line for {args.filename}",
 | 
						|
            file=sys.stderr,
 | 
						|
        )
 | 
						|
        sys.exit(1)
 | 
						|
    print(f"Compiler command: {shlex.join(command_line)}", file=sys.stderr)
 | 
						|
 | 
						|
    symbol_table, ucode = run_cfe(command_line, args.keep_files)
 | 
						|
    print_symbol_table(symbol_table)
 | 
						|
    if args.print_ucode:
 | 
						|
        print_ucode(ucode)
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 |