""" Common functions & definitions """ from dataclasses import dataclass from enum import Enum from hashlib import sha1 import json import os from subprocess import PIPE, run from sys import executable as PYTHON, platform from typing import List, Tuple, Union import yaml try: from yaml import CLoader as Loader except ImportError: from yaml import Loader ############# # Functions # ############# def get_file_sha1(path: str) -> bytes: """Gets the SHA1 hash of a file""" with open(path, 'rb') as f: return sha1(f.read()).digest() def get_cmd_stdout(cmd: str, text=True) -> str: """Run a command and get the stdout output as a string""" ret = run(cmd.split(), stdout=PIPE, text=text) assert ret.returncode == 0, f"Command '{cmd}' returned {ret.returncode}" return ret.stdout class Binary(Enum): DOL = 1 REL = 2 # ppcdis source output SourceDesc = Union[str, Tuple[str, int, int]] def get_containing_slice(addr: int) -> Tuple[Binary, SourceDesc]: """Finds the binary containing an address and its source file Source file is empty string if not decompiled""" dol_raw = get_cmd_stdout(f"{SLICES} {DOL_YML} {DOL_SLICES} -p {DOL_SRCDIR}/ --containing {addr:x}") containing = json.loads(dol_raw) if containing is None: rel_raw = get_cmd_stdout( f"{SLICES} {REL_YML} {REL_SLICES} -p {REL_SRCDIR}/ --containing {addr:x}" ) containing = json.loads(rel_raw) assert containing is not None, f"Unknown address {addr:x}" return (Binary.REL, containing) else: return (Binary.DOL, containing) def lookup_sym(sym: str, dol: bool = False, rel: bool = False, source_name: str = None) -> int: """Takes a symbol as a name or address and returns the address""" # Get binary if dol: binary_name = DOL_YML elif rel: binary_name = REL_YML else: binary_name = None # Determine type try: return int(sym, 16) except ValueError: return get_address(sym, binary_name, source_name) def lookup_sym_full(sym: str, dol: bool = False, rel: bool = False, source_name: str = None ) -> int: """Takes a symbol as a name or address and returns both the name and address""" # Get binary if dol: binary_name = DOL_YML elif rel: binary_name = REL_YML else: binary_name = None # Determine type try: return int(sym, 16), get_name(sym) except ValueError: return get_address(sym, binary_name, source_name), sym def get_address(name: str, binary: bool = None, source_name: bool = None) -> int: """Finds the address of a symbol""" args = [name] if binary is not None: args.append(f"-b {binary}") if source_name is not None: args.append(f"-n {source_name}") raw = get_cmd_stdout(f"{SYMBOLS} {GAME_SYMBOLS} --get-addr {' '.join(args)}") return json.loads(raw) def get_name(addr: int, binary: bool = None, source_name: bool = None) -> int: """Finds the name of a symbol""" args = [addr] if binary is not None: args.append(f"-b {binary}") if source_name is not None: args.append(f"-n {source_name}") raw = get_cmd_stdout(f"{SYMBOLS} {GAME_SYMBOLS} --get-name {' '.join(args)}") return json.loads(raw) def find_headers(dirname: str, base=None) -> List[str]: """Returns a list of all headers in a folder recursively""" if base is None: base = dirname ret = [] for name in os.listdir(dirname): path = dirname + '/' + name if os.path.isdir(path): ret.extend(find_headers(path, base)) elif name.endswith('.h'): ret.append(path[len(base)+1:]) return ret def load_from_yaml(path: str, default=None): """Loads an object from a yaml file""" if default is None: default = {} with open(path) as f: ret = yaml.load(f.read(), Loader) if ret is None: ret = default return ret ################ # Project dirs # ################ # Directory for decompiled dol code DOL_SRCDIR = "src/static" # Directory for decompiled rel code REL_SRCDIR = "src" # Include directory INCDIR = "include" # Directory for forest_1st data FOREST_1STDIR = "src/data/bin1" # Directory for forest_2nd data FOREST_2NDDIR = "src/data/bin2" # Build artifacts directory BUILDDIR = "build" # Build include directory BUILD_INCDIR = f"{BUILDDIR}/include" # Output binaries directory OUTDIR = "out" # Original binaries directory ORIG = "dump" # Tools directory TOOLS = "tools" # Config directory CONFIG = "config" # Extracted assets directory ASSETS = "assets" ######### # Tools # ######### # ppcdis PPCDIS = "tools/ppcdis" PPCDIS_INCDIR = f"{PPCDIS}/include" RELEXTERN = f"{PYTHON} {PPCDIS}/relextern.py" ANALYSER = f"{PYTHON} {PPCDIS}/analyser.py" DISASSEMBLER = f"{PYTHON} {PPCDIS}/disassembler.py" ORDERSTRINGS = f"{PYTHON} {PPCDIS}/orderstrings.py" ORDERFLOATS = f"{PYTHON} {PPCDIS}/orderfloats.py" ASSETRIP = f"{PYTHON} {PPCDIS}/assetrip.py" ASSETINC = f"{PYTHON} {PPCDIS}/assetinc.py" BATCHASSETRIP = f"{PYTHON} {PPCDIS}/batchassetrip.py" FORCEACTIVEGEN = f"{PYTHON} {PPCDIS}/forceactivegen.py" ELF2DOL = f"{PYTHON} {PPCDIS}/elf2dol.py" ELF2REL = f"{PYTHON} {PPCDIS}/elf2rel.py" SLICES = f"{PYTHON} {PPCDIS}/slices.py" PROGRESS = f"{PYTHON} {PPCDIS}/progress.py" SYMBOLS = f"{PYTHON} {PPCDIS}/symbols.py" FORCEFILESGEN = f"{PYTHON} {PPCDIS}/forcefilesgen.py" # Codewarrior TOOLS = "tools" CODEWARRIOR = os.path.join(TOOLS, "1.3.2") CODEWARRIOR_RODATA_POOL_FIX = os.path.join(TOOLS, "1.3.2r") ORTHRUS = os.path.join(TOOLS, "orthrus") SDK_CW = os.path.join(TOOLS, "1.2.5n") CC = os.path.join(CODEWARRIOR, "mwcceppc.exe") CC_R = os.path.join(CODEWARRIOR_RODATA_POOL_FIX, "mwcceppc.exe") OCC = os.path.join(SDK_CW, "mwcceppc.exe") LD = os.path.join(CODEWARRIOR, "mwldeppc.exe") if platform != "win32": LD = f"wibo {LD}" SJISWRAP = f"wibo {TOOLS}/sjiswrap.exe" else: ORTHRUS = os.path.join(TOOLS, "orthrus.exe") SJISWRAP = os.path.join(TOOLS, "sjiswrap.exe") # DevkitPPC DEVKITPPC = os.environ.get("DEVKITPPC") AS = os.path.join(DEVKITPPC, "bin", "powerpc-eabi-as") OBJDUMP = os.path.join(DEVKITPPC, "bin", "powerpc-eabi-objdump") CPP = os.path.join(DEVKITPPC, "bin", "powerpc-eabi-cpp") # Asset Converters VTXDIS = f"{PYTHON} {TOOLS}/converters/vtxdis.py" PAL16DIS = f"{PYTHON} {TOOLS}/converters/pal16dis.py" # JSystem JKernel archive tool ARC_TOOL = f"{PYTHON} {TOOLS}/arc_tool.py" # N64 SDK path for GBI N64SDK = os.environ.get("N64_SDK") assert N64SDK != None, "N64_SDK is not defined as a system environment variable" ######### # Files # ######### # Slices DOL_SLICES = f"{CONFIG}/dol_slices.yml" REL_SLICES = f"{CONFIG}/rel_slices.yml" # Overrides ANALYSIS_OVERRIDES = f"{CONFIG}/analysis_overrides.yml" DOL_DISASM_OVERRIDES = f"{CONFIG}/disasm_overrides.yml" REL_DISASM_OVERRIDES = f"{CONFIG}/rel_disasm_overrides.yml" # Binaries DOL = f"{ORIG}/static.dol" # read in python code REL = f"{ORIG}/foresta.rel" # read in python code REL_SZS = f"{ORIG}/foresta.rel.szs" DOL_YML = f"{CONFIG}/dol.yml" REL_YML = f"{CONFIG}/rel.yml" DOL_SHA = f"{ORIG}/static.dol.sha1" REL_SHA = f"{ORIG}/foresta.rel.sha1" DOL_OK = f"{BUILDDIR}/static.dol.ok" REL_OK = f"{BUILDDIR}/foresta.rel.ok" DOL_ASM_LIST = f"{BUILDDIR}/static.dol.asml" REL_ASM_LIST = f"{BUILDDIR}/foresta.rel.asml" # Symbols GAME_SYMBOLS = f"{CONFIG}/symbols.yml" # Assets ASSETS_YML = f"{CONFIG}/assets.yml" # Analysis outputs EXTERNS = f"{BUILDDIR}/externs.pickle" DOL_LABELS = f"{BUILDDIR}/labels.pickle" DOL_RELOCS = f"{BUILDDIR}/relocs.pickle" REL_LABELS = f"{BUILDDIR}/rel_labels.pickle" REL_RELOCS = f"{BUILDDIR}/rel_relocs.pickle" # Linker DOL_LCF_TEMPLATE = f"{CONFIG}/dol.lcf" DOL_LCF_TEMP = f"{BUILDDIR}/dol_temp.lcf" DOL_LCF = f"{BUILDDIR}/dol.lcf" REL_LCF = f"{CONFIG}/rel.lcf" # Outputs DOL_ELF = f"{BUILDDIR}/static.elf" REL_PLF = f"{BUILDDIR}/foresta.plf" DOL_OUT = f"{OUTDIR}/static.dol" REL_OUT = f"{OUTDIR}/foresta.rel" REL_SZS_OUT = f"{OUTDIR}/foresta.rel.szs" DOL_MAP = f"{OUTDIR}/static.map" REL_MAP = f"{OUTDIR}/foresta.map" # Optional full disassembly DOL_FULL = f"{OUTDIR}/dol.s" REL_FULL = f"{OUTDIR}/rel.s" ############## # Tool Flags # ############## ASFLAGS = ' '.join([ "-m gekko", f"-I {INCDIR}", f"-I {PPCDIS_INCDIR}", f"-I orig", f"-I {N64SDK}/ultra/usr/include" ]) CPPFLAGS = ' '.join([ "-nostdinc", f"-I {INCDIR}", f"-I {PPCDIS_INCDIR}", f"-I {BUILD_INCDIR}", f"-I {N64SDK}/ultra/usr/include" ]) DOL_SDATA2_SIZE = 8 REL_SDATA2_SIZE = 0 CFLAGS = [ "-O4", "-char unsigned", "-fp hard" ] CPLFLAGS = [ "-lang=c++", "-O0" ] COMMON_DEFINES = [ "-d _LANGUAGE_C", "-d F3DEX_GBI_2", "-d NDEBUG", "-d DEBUG=0", "-d MUST_MATCH" ] DOL_DEFINES = COMMON_DEFINES + [] REL_DEFINES = COMMON_DEFINES + [ #"-d OPTIMIZED_SQRTF", "-d IS_REL" ] BASE_DOL_CFLAGS = CFLAGS + [ "-inline on", "-sdata 8", f"-sdata2 {DOL_SDATA2_SIZE}" ] + DOL_DEFINES BOOT_CFLAGS = CFLAGS + [ "-inline on", "-sdata 0", "-sdata2 0" ] + DOL_DEFINES DOL_CFLAGS_NO_SDATA = CFLAGS + [ "-inline on", "-enum int", "-sdata 0", "-sdata2 0" ] + DOL_DEFINES DVDERR_CFLAGS = CFLAGS + [ "-inline on", "-sdata 0", "-sdata2 0", "-pool off" ] + DOL_DEFINES TRK_CFLAGS = [ "-O4,p", "-sdata 0", "-sdata2 0", "-fp hard", "-enum int", "-char unsigned", "-inline deferred", "-rostr" ] + DOL_DEFINES BASE_REL_CFLAGS = CFLAGS + [ "-sdata 0", f"-sdata2 {REL_SDATA2_SIZE}", "-enum int", "-requireprotos", #"-pool off", "-sym on" ] + REL_DEFINES LOCAL_CFLAGS = [ "-nostdinc", "-proc gekko", "-maxerrors 1", "-Cpp_exceptions off", "-I-", f"-i {INCDIR}", f"-i {PPCDIS_INCDIR}", f"-i {BUILD_INCDIR}", f"-ir {N64SDK}/ultra/usr/include" ] PREPROCESSOR_CFLAGS = [ "-E", "-P" ] SDK_CFLAG = [ "-O4,p", "-inline all", "-sdata 8", f"-sdata2 {DOL_SDATA2_SIZE}", "-fp hard" ] ALIGN16_CFLAG = [ "-func_align 16", ] JSYSTEM_BASE = [ "-lang=c++", "-inline on", "-fp fmadd", #"-fp_contract on", #"-pool off", # this is wrong "-Cpp_exceptions off", "-RTTI on", "-char signed", "-enum int", "-sym on", # might also be on for base flags? "-O4,s" # in mkdd some libraries use O4,p, might be the case here too ] JSYSTEM_JGADGET_BASE = [ "-lang=c++", "-inline on", "-fp fmadd", #"-fp_contract on", #"-pool off", # this is wrong "-Cpp_exceptions off", "-RTTI on", "-char signed", "-enum int", # "-sym on", # might also be on for base flags? "-O4,s" # in mkdd some libraries use O4,p, might be the case here too ] FAMICOM_BASE = CFLAGS + [ "-lang=c++", "-sdata 0", "-sdata2 0", "-enum int", "-sym on" ] + DOL_DEFINES JAUDIO_BASE = CFLAGS + [ "-lang=c++", "-char signed", "-sdata 8", "-sdata2 8", "-enum int", "-common on" ] + DOL_DEFINES JAUDIO_FUNC_ALIGN_32 = [ "-func_align 32", "-str readonly", "-inline off" ] JAUDIO_USER = [ "-d _LANGUAGE_C_PLUS_PLUS", "-O0", "-char unsigned", "-fp hard", "-lang=c++", "-sdata 8", "-sdata2 8", "-enum int", "-common on" ] JSYSTEM_CFLAGS = ' '.join(JSYSTEM_BASE + LOCAL_CFLAGS) JSYSTEM_JGADGET_CFLAGS = ' '.join(JSYSTEM_JGADGET_BASE + LOCAL_CFLAGS) DOL_CFLAGS = ' '.join(BASE_DOL_CFLAGS + LOCAL_CFLAGS) DOL_BOOT_CFLAGS = ' '.join(BOOT_CFLAGS + LOCAL_CFLAGS) DOL_DVDERR_CFLAGS = ' '.join(DVDERR_CFLAGS + LOCAL_CFLAGS) DOL_CFLAGS_SDATA0_CFLAGS = ' '.join(DOL_CFLAGS_NO_SDATA + LOCAL_CFLAGS) DOL_TRK_CFLAGS = ' '.join(TRK_CFLAGS + LOCAL_CFLAGS) SDK_FLAGS = ' '.join(SDK_CFLAG + LOCAL_CFLAGS) ALIGN16 = ' '.join(BASE_DOL_CFLAGS + LOCAL_CFLAGS + ALIGN16_CFLAG) DOL_CPPFLAGS = ' '.join(CPLFLAGS + BASE_DOL_CFLAGS + LOCAL_CFLAGS) REL_CFLAGS = ' '.join(BASE_REL_CFLAGS + LOCAL_CFLAGS) EXTERNAL_DOL_CFLAGS = ' '.join(BASE_DOL_CFLAGS) EXTERNAL_REL_CFLAGS = ' '.join(BASE_REL_CFLAGS) PREPROCESS_CFLAGS = ' '.join(PREPROCESSOR_CFLAGS) FAMICOM_CLFAGS = ' '.join(FAMICOM_BASE + LOCAL_CFLAGS) JAUDIO_CFLAGS = ' '.join(JAUDIO_BASE + LOCAL_CFLAGS) JAUDIO_FUNC_ALIGN_32_CFLAGS = ' '.join(JAUDIO_BASE + JAUDIO_FUNC_ALIGN_32 + LOCAL_CFLAGS) JAUDIO_USER_CFLAGS = ' '.join(JAUDIO_USER + LOCAL_CFLAGS) DOL_LDFLAGS = ' '.join([ "-maxerrors 1", "-mapunused", "-fp hardware" ]) LDFLAGS = ' '.join([ "-maxerrors 1", "-mapunused", "-opt_partial", "-strip_partial", "-fp hardware" ]) PPCDIS_ANALYSIS_FLAGS = ' '.join([ f"-o {ANALYSIS_OVERRIDES}", f"-l {EXTERNS}" ]) ############ # Contexts # ############ @dataclass class SourceContext: srcdir: str cflags: str binary: str labels: str relocs: str slices: str compiler: str sdata2_threshold: int disasm_overrides: int DOL_CTX = SourceContext(DOL_SRCDIR, DOL_CFLAGS, DOL_YML, DOL_LABELS, DOL_RELOCS, DOL_SLICES, CC, DOL_SDATA2_SIZE, DOL_DISASM_OVERRIDES) REL_CTX = SourceContext(REL_SRCDIR, REL_CFLAGS, REL_YML, REL_LABELS, REL_RELOCS, REL_SLICES, CC, REL_SDATA2_SIZE, REL_DISASM_OVERRIDES) #################### # diff.py Expected # #################### EXPECTED = "expected" DOL_EXPECTED = f"{EXPECTED}/build/main.elf" REL_EXPECTED = f"{EXPECTED}/build/foresta.plf"