Initial status of repo, builds.

This commit is contained in:
Prakxo
2022-12-17 13:56:20 +01:00
commit 0e1a42ee02
26 changed files with 60197 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
__pycache__/
.vscode/
build/
out/
dump/*
!dump/foresta.rel.sha1
!dump/main.dol.sha1
*.dol
*.rel
*.exe
*.dll
build.ninja
+6
View File
@@ -0,0 +1,6 @@
[submodule "tools/libyaz0"]
path = tools/libyaz0
url = https://github.com/aboood40091/libyaz0
[submodule "tools/ppcdis"]
path = tools/ppcdis
url = https://github.com/SeekyCt/ppcdis
+29
View File
@@ -0,0 +1,29 @@
# Animal Crossing Decompilation
Decompilation in progress of Animal Crossing (GAFE01)
## Cloning
Use `--recursive` when cloning to have ppcdis and libyaz0 in the repository.
## Build
**Currently, it has only been tested with [wibo](https://github.com/decompals/wibo) under WSL2, but should build under other platforms and with Wine.**
### Building
- Dump a copy of the game and extract **main.dol** and **foresta.rel.szs**.
- Decompress **foresta.rel.szs** with libyaz0 found in *tools/libyaz0*.
- Place **main.dol** in *dump/sys/* and **foresta.rel** in *dump/root/*.
- Place CodeWarrior 1.3.2 in *tools/1.3.2/*.
- Install DevkitPPC, Ninja and Python:
- **Only tested with DevkitPPC r41 and Python 3.8.10**, however other versions should work fine.
- Install Python modules from requirements.txt (`pip install -r requirements.txt`)
- Run configure.py
- Run ninja
## Credits
- jamchamb, Cuyler36, NWPlayer123 and fraser125 for past documentation of Animal Crossing.
- SeekyCt for [ppcdis](https://github.com/SeekyCt/ppcdis/) and helping setting up the project.
- msg for helping with *tools/map.py*.
+360
View File
@@ -0,0 +1,360 @@
"""
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"
# Directory for decompiled rel code
REL_SRCDIR = "rel"
# Include directory
INCDIR = "include"
# 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"
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"
# Codewarrior
TOOLS = "tools"
CODEWARRIOR = os.path.join(TOOLS, "1.3.2")
CC = os.path.join(CODEWARRIOR, "mwcceppc.exe")
LD = os.path.join(CODEWARRIOR, "mwldeppc.exe")
if platform != "win32":
CC = f"wibo {CC}"
LD = f"wibo {LD}"
# 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")
ICONV = f"{PYTHON} tools/sjis.py" # TODO: get actual iconv working(?)
#########
# 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}/main.dol" # read in python code
REL = f"{ORIG}/foresta.rel" # read in python code
DOL_YML = f"{CONFIG}/dol.yml"
REL_YML = f"{CONFIG}/rel.yml"
DOL_SHA = f"{ORIG}/main.dol.sha1"
REL_SHA = f"{ORIG}/foresta.rel.sha1"
DOL_OK = f"{BUILDDIR}/main.dol.ok"
REL_OK = f"{BUILDDIR}/foresta.rel.ok"
DOL_ASM_LIST = f"{BUILDDIR}/main.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 = f"{BUILDDIR}/dol.lcf"
REL_LCF = f"{CONFIG}/rel.lcf"
# Outputs
DOL_ELF = f"{BUILDDIR}/main.elf"
REL_PLF = f"{BUILDDIR}/foresta.plf"
DOL_OUT = f"{OUTDIR}/main.dol"
REL_OUT = f"{OUTDIR}/foresta.rel"
DOL_MAP = f"{OUTDIR}/main.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"
])
CPPFLAGS = ' '.join([
"-nostdinc",
f"-I {INCDIR}",
f"-I {PPCDIS_INCDIR}",
f"-I {BUILD_INCDIR}"
])
DOL_SDATA2_SIZE = 4
REL_SDATA2_SIZE = 0
CFLAGS = [
"-O4",
]
BASE_DOL_CFLAGS = CFLAGS + [
"-inline all",
"-sdata 4",
f"-sdata2 {DOL_SDATA2_SIZE}"
]
BASE_REL_CFLAGS = CFLAGS + [
"-sdata 0",
f"-sdata2 {REL_SDATA2_SIZE}",
"-pool off",
"-ordered-fp-compares"
]
LOCAL_CFLAGS = [
"-nostdinc",
"-proc gekko",
"-maxerrors 1",
"-I-",
f"-i {INCDIR}",
f"-i {PPCDIS_INCDIR}",
f"-i {BUILD_INCDIR}"
]
DOL_CFLAGS = ' '.join(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)
LDFLAGS = ' '.join([
"-fp hard",
"-maxerrors 1",
"-mapunused"
])
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
sdata2_threshold: int
disasm_overrides: int
DOL_CTX = SourceContext(DOL_SRCDIR, DOL_CFLAGS, DOL_YML, DOL_LABELS, DOL_RELOCS, DOL_SLICES,
DOL_SDATA2_SIZE, DOL_DISASM_OVERRIDES)
REL_CTX = SourceContext(REL_SRCDIR, REL_CFLAGS, REL_YML, REL_LABELS, REL_RELOCS, REL_SLICES,
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"
+403
View File
@@ -0,0 +1,403 @@
blocked_pointers:
- 0x800A8440
- 0x800A8514
forced_types:
0x80003534: ENTRY
0x80005468: ENTRY
0x8003ab3c: ENTRY
0x8003ab70: ENTRY
0x8003ab70: ENTRY
0x8003ab70: ENTRY
0x8003ab70: ENTRY
0x8003ab78: ENTRY
0x8003ab88: ENTRY
0x8003abe0: ENTRY
0x8003abf0: ENTRY
0x8003abfc: ENTRY
0x8003ac0c: ENTRY
0x8003ac48: ENTRY
0x8003ac48: ENTRY
0x8003ac48: ENTRY
0x8003ac58: ENTRY
0x8003ac70: ENTRY
0x8003ac90: ENTRY
0x8003aca4: ENTRY
0x8003acb4: ENTRY
0x8003acd0: ENTRY
0x8003ace0: ENTRY
0x8003ada4: ENTRY
0x8003ada4: ENTRY
0x8003ada4: ENTRY
0x8003ada4: ENTRY
0x8003ada4: ENTRY
0x8003adac: ENTRY
0x8003b5d4: ENTRY
0x8003b5f4: ENTRY
0x8003b61c: ENTRY
0x8003b648: ENTRY
0x8003b674: ENTRY
0x8003b684: ENTRY
0x8003b698: ENTRY
0x8003b6c4: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b6f0: ENTRY
0x8003b700: ENTRY
0x8003b70c: ENTRY
0x8003b70c: ENTRY
0x8003b70c: ENTRY
0x8003b70c: ENTRY
0x8003b71c: ENTRY
0x8003b71c: ENTRY
0x8003b71c: ENTRY
0x8003b71c: ENTRY
0x8003b71c: ENTRY
0x8003b72c: ENTRY
0x8003b734: ENTRY
0x8003b73c: ENTRY
0x8003b744: ENTRY
0x8003b754: ENTRY
0x8003b764: ENTRY
0x8003b774: ENTRY
0x8003b784: ENTRY
0x8003b794: ENTRY
0x8003b7a4: ENTRY
0x8003b7bc: ENTRY
0x8003b7d4: ENTRY
0x8003b7fc: ENTRY
0x8003b82c: ENTRY
0x8003b834: ENTRY
0x8003b83c: ENTRY
0x8003b84c: ENTRY
0x8003b85c: ENTRY
0x8003b86c: ENTRY
0x8003b87c: ENTRY
0x8003b88c: ENTRY
0x8003b89c: ENTRY
0x8003b8ac: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8bc: ENTRY
0x8003b8d0: ENTRY
0x8003b8d0: ENTRY
0x8003b8d0: ENTRY
0x8003b8e4: ENTRY
0x8003b8e4: ENTRY
0x8003b8e4: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b8f8: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b928: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b960: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b970: ENTRY
0x8003b980: ENTRY
0x8003b980: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b990: ENTRY
0x8003b9a0: ENTRY
0x8003b9a0: ENTRY
0x8003b9a0: ENTRY
0x8003b9a0: ENTRY
0x8003b9b8: ENTRY
0x8003b9b8: ENTRY
0x8003b9b8: ENTRY
0x8003b9b8: ENTRY
0x8003b9d0: ENTRY
0x8003b9e4: ENTRY
0x8003b9e4: ENTRY
0x8003b9e4: ENTRY
0x8003b9e4: ENTRY
0x8003ba00: ENTRY
0x8003ba14: ENTRY
0x8003ba14: ENTRY
0x8003ba14: ENTRY
0x8003ba14: ENTRY
0x8003ba30: ENTRY
0x8003ba48: ENTRY
0x8003ba48: ENTRY
0x8003ba48: ENTRY
0x8003ba48: ENTRY
0x8003ba68: ENTRY
0x8003ba80: ENTRY
0x8003ba80: ENTRY
0x8003ba80: ENTRY
0x8003ba80: ENTRY
0x8003baa0: ENTRY
0x8003babc: ENTRY
0x8003bad8: ENTRY
0x8003baf4: ENTRY
0x8003bb10: ENTRY
0x8003bb2c: ENTRY
0x8003bb48: ENTRY
0x8003bb64: ENTRY
0x8003bb80: ENTRY
0x8003bb88: ENTRY
0x8003bbac: ENTRY
0x8003bbc0: ENTRY
0x8003bbdc: ENTRY
0x8003bbe0: ENTRY
0x8003bc10: ENTRY
0x8003bc18: ENTRY
0x8003bc44: ENTRY
0x8003bc54: ENTRY
0x8003bc5c: ENTRY
0x8003bcc8: ENTRY
0x8003bcd0: ENTRY
0x8003bd20: ENTRY
0x8003bd30: ENTRY
0x8003bd3c: ENTRY
0x8003bd60: ENTRY
0x8003bd70: ENTRY
0x8003bd78: ENTRY
0x8003bd88: ENTRY
0x8003bd98: ENTRY
0x8003bda8: ENTRY
0x8003bdb0: ENTRY
0x8003bdb4: ENTRY
0x8003bdbc: ENTRY
0x8003bdc8: ENTRY
0x8003bdc8: ENTRY
0x8003bdd4: ENTRY
0x8003be64: ENTRY
0x8003bf28: ENTRY
0x8003bf38: ENTRY
0x8003bf9c: ENTRY
0x8003bfd0: ENTRY
0x8003bfdc: ENTRY
0x8003bfe8: ENTRY
0x8003bfec: ENTRY
0x8003c000: ENTRY
0x8003c020: ENTRY
0x8003c030: ENTRY
0x8003c034: ENTRY
0x8003c094: ENTRY
0x8003c0e0: ENTRY
0x8003c1f4: ENTRY
0x8003c210: ENTRY
0x8003c214: ENTRY
0x8003c24c: ENTRY
0x8003c260: ENTRY
0x8003c274: ENTRY
0x8003c274: ENTRY
0x8003c274: ENTRY
0x8003c274: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c28c: ENTRY
0x8003c2ac: ENTRY
0x8003c2e0: ENTRY
0x8003c32c: ENTRY
0x8003c540: ENTRY
0x8003c5cc: ENTRY
0x8003c5cc: ENTRY
0x8003c5d8: ENTRY
0x8003c60c: ENTRY
0x8003c614: ENTRY
0x8003c640: ENTRY
0x8003c6e4: ENTRY
0x8003c6ec: ENTRY
0x8003c894: ENTRY
0x8003c8bc: ENTRY
0x8003c8e8: ENTRY
0x8003c930: ENTRY
0x8003caf0: ENTRY
0x8003cb20: ENTRY
0x8003cb6c: ENTRY
0x8003cb78: ENTRY
0x8003cb8c: ENTRY
0x8003cba0: ENTRY
0x8003ccf4: ENTRY
0x8003cd0c: ENTRY
0x8003ce58: ENTRY
0x8003ce58: ENTRY
0x8003ce58: ENTRY
0x8003ce58: ENTRY
0x8003ce58: ENTRY
0x8003ce64: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf80: ENTRY
0x8003cf98: ENTRY
0x8003cf98: ENTRY
0x8003cf98: ENTRY
0x8003cf98: ENTRY
0x8003cfb0: ENTRY
0x8003cfb0: ENTRY
0x8003cfbc: ENTRY
0x8003cfc8: ENTRY
0x8003d010: ENTRY
0x8003d010: ENTRY
0x8003d01c: ENTRY
0x8003d0ac: ENTRY
0x8003d0f8: ENTRY
0x8003d0fc: ENTRY
0x8003d13c: ENTRY
0x8003d16c: ENTRY
0x8003d1b0: ENTRY
0x8003d1b4: ENTRY
0x8003d1e4: ENTRY
0x8003d1e4: ENTRY
0x8003d1e4: ENTRY
0x8003d1e4: ENTRY
0x8003d1f8: ENTRY
0x8003d264: ENTRY
0x8003d2b0: ENTRY
0x8003d368: ENTRY
0x8003d3dc: ENTRY
0x8003d444: ENTRY
0x8003d45c: ENTRY
0x8003d4a4: ENTRY
0x8003d4ec: ENTRY
0x8003d514: ENTRY
0x8003d53c: ENTRY
0x8003d564: ENTRY
0x8003d584: ENTRY
0x8003d590: ENTRY
0x8003d5b4: ENTRY
0x8003d5d8: ENTRY
0x8003d614: ENTRY
0x8003d678: ENTRY
0x8003d6ac: ENTRY
0x8003d700: ENTRY
0x8003d754: ENTRY
0x8003d76c: ENTRY
0x8003d7e8: ENTRY
0x8003d7f4: ENTRY
0x8003d82c: ENTRY
0x8003d8d4: ENTRY
0x8003d928: ENTRY
0x8003d940: ENTRY
0x8003d96c: ENTRY
0x8003d990: ENTRY
0x8003da50: ENTRY
0x8003da88: ENTRY
0x8003da90: ENTRY
0x8003db68: ENTRY
0x8003dbd8: ENTRY
0x8003dbf4: ENTRY
0x8003dc20: ENTRY
0x8003dc28: ENTRY
0x8003dc28: ENTRY
0x8003dcd4: ENTRY
0x8003dd3c: ENTRY
0x8003ddb8: ENTRY
0x8003dddc: ENTRY
0x8003de50: ENTRY
0x8003de78: ENTRY
0x80078edc: ENTRY
0x80078f00: ENTRY
0x80078f00: ENTRY
0x80078f04: ENTRY
0x80078f34: ENTRY
0x80078f8c: ENTRY
0x80078f9c: ENTRY
0x80078fcc: ENTRY
0x8007ac24: ENTRY
0x8007ac34: ENTRY
0x8007db20: ENTRY
0x8007db3c: ENTRY
0x8009ae2c: ENTRY
0x8009ae34: ENTRY
0x8009ae38: ENTRY
0x8009ae3c: ENTRY
0x8009ae78: ENTRY
0x8009ae80: ENTRY
0x8009ae84: ENTRY
0x8009ae88: ENTRY
0x8009ae98: ENTRY
0x8009ae9c: ENTRY
0x8009aea0: ENTRY
0x8009aea4: ENTRY
0x8009aea8: ENTRY
0x8009aeac: ENTRY
0x8009aeb0: ENTRY
0x8009aeb4: ENTRY
0x8009aeb8: ENTRY
0x8009aebc: ENTRY
0x8009aec0: ENTRY
0x8009aec4: ENTRY
0x8009aec8: ENTRY
0x8009aecc: ENTRY
0x8009aed0: ENTRY
0x8009aed4: ENTRY
0x8009aee4: ENTRY
0x8009aee8: ENTRY
0x8009aeec: ENTRY
0x8009aef0: ENTRY
0x8009aef4: ENTRY
0x8009aef8: ENTRY
0x8009aefc: ENTRY
0x8009af00: ENTRY
0x8009af04: ENTRY
0x8009af08: ENTRY
0x8009af0c: ENTRY
0x8009af10: ENTRY
0x8009af14: ENTRY
0x8009af18: ENTRY
0x8009af1c: ENTRY
0x8009af20: ENTRY
0x800A6A74: FUNCTION # TRKExceptionHandler
0x800A6BD4: FUNCTION # TRKInterruptHandlerEnableInterrupts
0x800a8130: ENTRY
0x800a8138: ENTRY
0x800a8140: ENTRY
0x800a8148: ENTRY
0x800ab260: ENTRY
View File
View File
+41
View File
@@ -0,0 +1,41 @@
MEMORY {
text : origin = 0x80003100
forcestrip : origin = 0
}
SECTIONS
{
GROUP:{
.init ALIGN(0x20):{}
extab_ ALIGN(0x20):{}
extabindex_ ALIGN(0x20):{}
.text ALIGN(0x20):{}
.ctors ALIGN(0x20):{}
.dtors ALIGN(0x20):{}
.rodata ALIGN(0x20):{}
.data ALIGN(0x20):{}
.bss ALIGN(0x20):{}
.sdata ALIGN(0x20):{}
.sbss ALIGN(0x20):{}
.sdata2 ALIGN(0x20):{}
.sbss2 ALIGN(0x20):{}
.stack ALIGN(0x100):{}
} > text
GROUP:{
forcestrip ALIGN(0x20):{}
} > forcestrip
_stack_addr = (_f_sbss2 + SIZEOF(.sbss2) + 65536 + 0x7) & ~0x7;
_stack_end = _f_sbss2 + SIZEOF(.sbss2);
_db_stack_addr = (_stack_addr + 0x2000);
_db_stack_end = _stack_addr;
__ArenaLo = (_db_stack_addr + 0x1f) & ~0x1f;
__ArenaHi = 0x81700000;
_eti_init_info = 0x80005684;
}
FORCEACTIVE {
PPCDIS_FORCEACTIVE
}
__dummy_str = 0;
__dummy_float = 0;
__dummy_double = 0;
__dummy_pointer = 0;
+23
View File
@@ -0,0 +1,23 @@
path: dump/main.dol
r13: 0x8021fb80
r2: 0x80220be0
section_defs:
text:
- name: .init
- name: .text
data:
- name: extab_
attr: a
- name: extabindex_
attr: a
- name: .ctors
balign: 0
- name: .dtors
balign: 0
- name: .rodata
- name: .data
- name: .sdata
- name: .sdata2
bss:
- name: .bss
- name: .sbss
View File
+21
View File
@@ -0,0 +1,21 @@
SECTIONS {
GROUP:{
.init:{}
.text:{}
.ctors:{}
.dtors:{}
.rodata:{}
.data:{}
.bss:{}
// Removed by elf2rel
forcestrip:{}
relsymdef:{}
}
}
FORCEFILES {
}
__dummy_str = 0;
__dummy_float = 0;
__dummy_double = 0;
+4
View File
@@ -0,0 +1,4 @@
path: dump/foresta.rel
address: 0x803701C0
bss_address: 0x8125A7C0
dol: config/dol.yml
+5
View File
@@ -0,0 +1,5 @@
trim_ctors: true
trim_dtors: true
symbol_aligns:
0x8064d500 : 0x20
0x8125a7c0 : 0x20
View File
+58130
View File
File diff suppressed because it is too large Load Diff
+786
View File
@@ -0,0 +1,786 @@
"""
Creates a build script for ninja
"""
from abc import ABC, abstractmethod
from collections import defaultdict
from dataclasses import dataclass
import json
import os
import pickle
import re
from io import StringIO
from typing import List, Tuple
from ninja_syntax import Writer
import common as c
####################
# Setup Validation #
####################
# Check CodeWarrior was added
assert os.path.exists("tools/1.3.2/mwcceppc.exe") and \
os.path.exists("tools/1.3.2/mwldeppc.exe"), \
"Error: Codewarrior not found in tools/1.3.2"
# Check binaries were added
assert os.path.exists(c.DOL) and os.path.exists(c.REL), \
"Error: Base binaries not found!"
#Check if binaries are correct
dol_hash = c.get_file_sha1(c.DOL)
rel_hash = c.get_file_sha1(c.REL)
assert dol_hash == bytes.fromhex("2ae8f56e7791d37e165bd5900921f2269f9515bf") and rel_hash == bytes.fromhex("c59d278ad8542bb05d6cbb632f60a0db05bef203"), \
"Error: Base binaries hashes aren't correct!"
#Check submodules added
assert os.path.exists(c.PPCDIS), \
"Error: Git submodules not initialised!"
###############
# Ninja Setup #
###############
outbuf = StringIO()
n = Writer(outbuf)
n.variable("ninja_required_version", "1.3")
n.newline()
################
# Project Dirs #
################
n.variable("builddir", c.BUILDDIR)
n.variable("outdir", c.OUTDIR)
n.variable("dump", c.ORIG)
n.variable("tools", c.TOOLS)
n.variable("config", c.CONFIG)
n.newline()
os.makedirs(c.BUILDDIR, exist_ok=True)
#########
# Tools #
#########
n.variable("python", c.PYTHON)
n.variable("ppcdis", c.PPCDIS)
n.variable("relextern", c.RELEXTERN)
n.variable("analyser", c.ANALYSER)
n.variable("disassembler", c.DISASSEMBLER)
n.variable("orderstrings", c.ORDERSTRINGS)
n.variable("orderfloats", c.ORDERFLOATS)
n.variable("assetrip", c.ASSETRIP)
n.variable("assetinc", c.ASSETINC)
n.variable("forceactivegen", c.FORCEACTIVEGEN)
n.variable("elf2dol", c.ELF2DOL)
n.variable("elf2rel", c.ELF2REL)
n.variable("codewarrior", c.CODEWARRIOR)
n.variable("cc", c.CC)
n.variable("ld", c.LD)
n.variable("devkitppc", c.DEVKITPPC)
n.variable("as", c.AS)
n.variable("cpp", c.CPP)
n.variable("iconv", c.ICONV)
n.newline()
##############
# Tool Flags #
##############
n.variable("asflags", c.ASFLAGS)
n.variable("ldflags", c.LDFLAGS)
n.variable("cppflags", c.CPPFLAGS)
n.variable("ppcdis_analysis_flags", c.PPCDIS_ANALYSIS_FLAGS)
n.newline()
#########
# Rules #
#########
# Windows can't use && without this statement
ALLOW_CHAIN = "cmd /c " if os.name == "nt" else ""
n.rule(
"relextern",
command = "$relextern $out $in",
description = "ppcdis rel extern $in"
)
n.rule(
"analyse",
command = "$analyser $in $out $analysisflags",
description = "ppcdis analysis $in",
pool="console"
)
n.rule(
"disasm",
command = "$disassembler $in $out -q $disasmflags",
description = "ppcdis full disassembly $out"
)
n.rule(
"disasm_slice",
command = "$disassembler $in $out -q $disasmflags -s $slice",
description = "ppcdis disassembly $out"
)
n.rule(
"disasm_single",
command = "$disassembler $in $out -j $addr -q $disasmflags",
description = "ppcdis function disassembly $addr"
)
n.rule(
"jumptable",
command = "$disassembler $in $out -j $addr -q $disassembler",
description = "Jumptable $addr"
)
n.rule(
"orderstrings",
command = "$orderstrings $in $addrs $out $flags --enc shift-jis --pool",
description = "Order strings $in $addrs"
)
n.rule(
"orderfloats",
command = "$orderfloats $in $addrs $out $flags",
description = "Order floats $in $addrs"
)
n.rule(
"assetrip",
command = "$assetrip $in $addrs $out",
description = "Asset rip $out"
)
n.rule(
"assetinc",
command = "$assetinc $in $out",
description = "Asset include generation $out"
)
n.rule(
"forceactivegen",
command = "$forceactivegen $in $out",
description = "LCF FORCEACTIVEGEN generation $in"
)
n.rule(
"elf2dol",
command = "$elf2dol $in -o $out",
description = "elf2dol $in"
)
n.rule(
"elf2rel",
command = "$elf2rel $in -o $out $flags",
description = "elf2rel $in"
)
n.rule(
"sha1sum",
command = ALLOW_CHAIN + "sha1sum -c $in && touch $out",
description ="Verify $in",
pool ="console"
)
n.rule(
"as",
command = f"$as $asflags -c $in -o $out",
description = "AS $in"
)
n.rule(
"cc",
command = ALLOW_CHAIN + f"$cpp -M $in -MF $out.d $cppflags && $cc $cflags -c $in -o $out",
description = "CC $in",
deps = "gcc",
depfile = "$out.d"
)
n.rule(
"css",
command = ALLOW_CHAIN + f"$cpp -M $in -MF $out.d $cppflags && $cc $cflags -S $in -o $out",
description = "CC -S $in",
deps = "gcc",
depfile = "$out.d"
)
n.rule(
"ld",
command = "$ld $ldflags -map $map -lcf $lcf $in -o $out",
description = "LD $out",
)
n.rule(
"iconv",
command = "$iconv $in $out",
description = "iconv $in",
)
##########
# Assets #
##########
@dataclass
class Asset:
binary: str
path: str
start: int
end: int
def load(yml_path: str):
return {
asset : Asset(binary, asset, *adat["addrs"])
for binary, bdat in c.load_from_yaml(yml_path).items()
for asset, adat in bdat.items()
}
assets = Asset.load(c.ASSETS_YML)
###########
# Sources #
###########
class GeneratedInclude(ABC):
def __init__(self, ctx: c.SourceContext, source_name: str, path: str):
self.ctx = ctx
self.source_name = source_name
self.path = path
@abstractmethod
def build(self):
raise NotImplementedError
def find(ctx: c.SourceContext, source_name: str, txt: str) -> List["GeneratedInclude"]:
return [
cl(ctx, source_name, match)
for cl in (
AsmInclude,
JumptableInclude,
StringInclude,
FloatInclude,
DoubleInclude,
AssetInclude
)
for match in re.findall(cl.REGEX, txt)
]
class AsmInclude(GeneratedInclude):
REGEX = r'#include "asm\/([0-9a-f]{8})\.s"'
def __init__(self, ctx: c.SourceContext, source_name: str, match: str):
self.addr = match
super().__init__(ctx, source_name, f"{c.BUILD_INCDIR}/asm/{self.addr}.s")
def build(includes: List["AsmInclude"]):
# Skip empty list
if len(includes) == 0:
return
# Get ctx from first include (all should be equal)
ctx = includes[0].ctx
# Sort by source name
batches = defaultdict(list)
for inc in includes:
batches[inc.source_name].append(inc)
# Compile by source name
for source_name, incs in batches.items():
n.build(
[inc.path for inc in incs],
rule="disasm_single",
inputs=[ctx.binary, ctx.labels, ctx.relocs],
implicit=[c.GAME_SYMBOLS, ctx.disasm_overrides],
variables={
"disasmflags" : f"-m {c.GAME_SYMBOLS} -o {ctx.disasm_overrides} -n {source_name}",
"addr" : ' '.join(inc.addr for inc in incs) }
)
def _repr__(self):
return f"AsmInclude({self.addr})"
class JumptableInclude(GeneratedInclude):
REGEX = r'#include "jumptable\/([0-9a-f]{8})\.inc"'
def __init__(self, ctx: c.SourceContext, source_name: str, match: str):
self.addr = match
super().__init__(ctx, source_name, f"{c.BUILD_INCDIR}/jumptable/{self.addr}.inc")
def build(includes: List["JumptableInclude"]):
# Skip empty list
if len(includes) == 0:
return
# Get context from first include (all should be equal)
ctx = includes[0].ctx
# Sort by source name
batches = defaultdict(list)
for inc in includes:
batches[inc.source_name].append(inc)
# Compile by source name
# TODO: subdivide large batches
for source_name, incs in batches.items():
n.build(
[inc.path for inc in incs],
rule="jumptable",
inputs=[ctx.binary, ctx.labels, ctx.relocs],
implicit=[c.GAME_SYMBOLS, ctx.disasm_overrides],
variables={
"disasmflags" : f"-m {c.GAME_SYMBOLS} -o {ctx.disasm_overrides} -n {source_name}",
"addr" : ' '.join(inc.addr for inc in incs)
}
)
def __repr__(self):
return f"JumptableInclude({self.addr})"
class StringInclude(GeneratedInclude):
REGEX = r'#include "orderstrings\/([0-9a-f]{8})_([0-9a-f]{8})\.inc"'
def __init__(self, ctx: c.SourceContext, source_name: str, match: Tuple[str]):
self.start, self.end = match
super().__init__(ctx, source_name,
f"{c.BUILD_INCDIR}/orderstrings/{self.start}_{self.end}.inc")
def build(includes: List["StringInclude"]):
# Skip empty list
if len(includes) == 0:
return
# Get context from first include (all should be equal)
ctx = includes[0].ctx
# Build
for inc in includes:
n.build(
inc.path,
rule="orderstrings",
inputs=ctx.binary,
variables={
"addrs" : f"{inc.start} {inc.end}"
}
)
def __repr__(self):
return f"StringInclude({self.start}, {self.end})"
class FloatInclude(GeneratedInclude):
REGEX = r'#include "(orderfloats(m?))\/([0-9a-f]{8})_([0-9a-f]{8})\.inc"'
def __init__(self, ctx: c.SourceContext, source_name: str, match: Tuple[str]):
folder, manual, self.start, self.end = match
self.manual = manual != ''
super().__init__(ctx, source_name,
f"{c.BUILD_INCDIR}/{folder}/{self.start}_{self.end}.inc")
def build(includes: List["FloatInclude"]):
# Skip empty list
if len(includes) == 0:
return
# Get context from first include (all should be equal)
ctx = includes[0].ctx
# Build
for inc in includes:
sda = "--sda " if ctx.sdata2_threshold >= 4 else ""
asm = "" if inc.manual else "--asm"
n.build(
inc.path,
rule="orderfloats",
inputs=inc.ctx.binary,
variables={
"addrs" : f"{inc.start} {inc.end}",
"flags" : f"{sda} {asm}"
}
)
def __repr__(self):
return f"FloatInclude({self.start}, {self.end})"
class DoubleInclude(GeneratedInclude):
REGEX = r'#include "orderdoubles\/([0-9a-f]{8})_([0-9a-f]{8})\.inc"'
def __init__(self, ctx: c.SourceContext, source_name: str, match: Tuple[str]):
self.start, self.end = match
super().__init__(ctx, source_name,
f"{c.BUILD_INCDIR}/orderdoubles/{self.start}_{self.end}.inc")
def build(includes: List["DoubleInclude"]):
# Skip empty list
if len(includes) == 0:
return
# Get context from first include (all should be equal)
ctx = includes[0].ctx
# Build
for inc in includes:
n.build(
inc.path,
rule="orderfloats",
inputs=ctx.binary,
variables={
"addrs" : f"{inc.start} {inc.end}",
"flags" : f"--double"
}
)
def __repr__(self):
return f"DoubleInclude({self.start}, {self.end})"
class AssetInclude(GeneratedInclude):
REGEX = r'#include "assets\/(.+)\.inc"'
def __init__(self, ctx: c.SourceContext, source_name: str, match: Tuple[str]):
self.asset = assets[match]
self.asset_path = f"{c.ASSETS}/{self.asset.path}"
assert ctx.binary == self.asset.binary, f"Tried to include from other binary"
super().__init__(ctx, source_name, f"{c.BUILD_INCDIR}/assets/{self.asset.path}.inc")
def build(includes: List["AssetInclude"]):
# Skip empty list
if len(includes) == 0:
return
# Build
for inc in includes:
n.build(
inc.asset_path,
rule="assetrip",
inputs=inc.asset.binary,
variables={
"addrs" : f"{inc.asset.start:x} {inc.asset.end:x}"
}
)
n.build(
inc.path,
rule="assetinc",
inputs=inc.asset_path
)
def __repr__(self):
return f"AssetInclude({self.asset})"
class Source(ABC):
def __init__(self, decompiled: bool, src_path: str, o_path: str,
gen_includes: List[GeneratedInclude] = []):
self.decompiled = decompiled
self.src_path = src_path
self.o_path = o_path
self.o_stem = o_path[:-2]
self.gen_includes = gen_includes
def build(self):
raise NotImplementedError
def make(ctx: c.SourceContext, source: c.SourceDesc):
if isinstance(source, str):
print(source)
ext = source.split('.')[-1].lower()
if ext in ("c", "cpp", "cxx", "cc"):
return CSource(ctx, source)
elif ext == "s":
return AsmSource(ctx, source)
else:
assert 0, f"Unknown source type .{ext}"
else:
return GenAsmSource(ctx, *source)
class GenAsmSource(Source):
def __init__(self, ctx: c.SourceContext, section: str, start: int, end: int):
self.start = start
self.end = end
self.ctx = ctx
name = f"{section}_{start:x}_{end:x}.s"
src_path = f"$builddir/asm/{name}"
super().__init__(False, src_path, src_path + ".o")
def build(self):
n.build(
self.src_path,
rule = "disasm_slice",
inputs = [self.ctx.binary, self.ctx.labels, self.ctx.relocs],
implicit = [c.GAME_SYMBOLS, self.ctx.disasm_overrides],
variables = {
"slice" : f"{self.start:x} {self.end:x}",
"disasmflags" : f"-m {c.GAME_SYMBOLS} -o {self.ctx.disasm_overrides}"
}
)
n.build(
self.o_path,
rule="as",
inputs=self.src_path
)
def batch_build(sources: List["GenAsmSource"], batch_size=20):
# TODO: configure batch size based on cpu core count
# Skip empty list
if len(sources) == 0:
return
# Get context from first include (all should be equal)
ctx = sources[0].ctx
for src in sources:
n.build(
src.o_path,
rule="as",
inputs=src.src_path
)
while len(sources) > 0:
batch, sources = sources[:batch_size], sources[batch_size:]
n.build(
[src.src_path for src in batch],
rule = "disasm_slice",
inputs = [ctx.binary, ctx.labels, ctx.relocs],
implicit = [c.GAME_SYMBOLS, ctx.disasm_overrides],
variables = {
"slice" : ' '.join(
f"{src.start:x} {src.end:x}"
for src in batch
),
"disasmflags" : f"-m {c.GAME_SYMBOLS} -o {ctx.disasm_overrides}"
}
)
class AsmSource(Source):
def __init__(self, ctx: c.SourceContext, path: str):
super().__init__(True, path, f"$builddir/{path}.o")
def build(self):
n.build(
self.o_path,
rule = "as",
inputs = self.src_path
)
class CSource(Source):
def __init__(self, ctx: c.SourceContext, path: str):
self.cflags = ctx.cflags
self.iconv_path = f"$builddir/iconv/{path}"
# Find generated includes
with open(path, encoding="utf-8") as f:
gen_includes = GeneratedInclude.find(ctx, path, f.read())
self.s_path = f"$builddir/{path}.s"
super().__init__(True, path, f"$builddir/{path}.o", gen_includes)
def build(self):
n.build(
self.iconv_path,
rule="iconv",
inputs=self.src_path
)
n.build(
self.o_path,
rule = "cc",
inputs = self.iconv_path,
implicit = [inc.path for inc in self.gen_includes],
variables = {
"cflags" : self.cflags
}
)
# Optional manual debug target
n.build(
self.s_path,
rule = "ccs",
inputs = self.iconv_path,
implicit = [inc.path for inc in self.gen_includes],
variables = {
"cflags" : self.cflags
}
)
def load_sources(ctx: c.SourceContext):
raw = c.get_cmd_stdout(
f"{c.SLICES} {ctx.binary} {ctx.slices} -o -p {ctx.srcdir}/"
)
return [Source.make(ctx, s) for s in json.loads(raw)]
def find_gen_includes(sources: List[Source]):
ret = defaultdict(list)
for source in sources:
if not isinstance(source, CSource):
continue
for inc in source.gen_includes:
ret[type(inc)].append(inc)
return ret
def make_asm_list(path: str, asm_includes: List[AsmInclude]):
with open(path, 'wb') as f:
pickle.dump(
[
int(inc.addr, 16)
for inc in asm_includes
],
f
)
dol_sources = load_sources(c.DOL_CTX)
dol_gen_includes = find_gen_includes(dol_sources)
make_asm_list(c.DOL_ASM_LIST, dol_gen_includes[AsmInclude])
rel_sources = load_sources(c.REL_CTX)
rel_gen_includes = find_gen_includes(rel_sources)
make_asm_list(c.REL_ASM_LIST, rel_gen_includes[AsmInclude])
##########
# Builds #
##########
n.build(
c.EXTERNS,
rule = "relextern",
inputs = c.REL_YML
)
n.build(
[c.REL_LABELS, c.REL_RELOCS],
rule = "analyse",
inputs = c.REL_YML,
implicit = [c.ANALYSIS_OVERRIDES, c.EXTERNS],
variables = {
"analysisflags" : "$ppcdis_analysis_flags"
}
)
n.build(
[c.DOL_LABELS, c.DOL_RELOCS],
rule = "analyse",
inputs = c.DOL_YML,
implicit = [c.ANALYSIS_OVERRIDES, c.EXTERNS],
variables = {
"analysisflags" : f"$ppcdis_analysis_flags"
}
)
for cl, includes in dol_gen_includes.items():
cl.build(includes)
for cl, includes in rel_gen_includes.items():
cl.build(includes)
dol_gen_asm = []
for source in dol_sources:
if isinstance(source, GenAsmSource):
dol_gen_asm.append(source)
else:
source.build()
GenAsmSource.batch_build(dol_gen_asm)
rel_gen_asm = []
for source in rel_sources:
if isinstance(source, GenAsmSource):
rel_gen_asm.append(source)
else:
source.build()
GenAsmSource.batch_build(rel_gen_asm)
n.build(
c.DOL_LCF,
rule="forceactivegen",
inputs=[c.DOL_LCF_TEMPLATE, c.DOL_YML, c.DOL_LABELS, c.GAME_SYMBOLS, c.EXTERNS]
)
n.build(
c.DOL_ELF,
rule="ld",
inputs=[s.o_path for s in dol_sources],
implicit=c.DOL_LCF,
implicit_outputs=c.DOL_MAP,
variables={
"map" : c.DOL_MAP,
"lcf" : c.DOL_LCF
}
)
n.build(
c.DOL_OUT,
rule="elf2dol",
inputs=c.DOL_ELF,
)
n.build(
c.DOL_OK,
rule = "sha1sum",
inputs = c.DOL_SHA,
implicit = [c.DOL_OUT]
)
n.default(c.DOL_OK)
n.build(
c.REL_PLF,
rule="ld",
inputs=[s.o_path for s in rel_sources],
implicit=c.REL_LCF,
implicit_outputs=c.REL_MAP,
variables={
"map" : c.REL_MAP,
"lcf" : c.REL_LCF,
"ldflags" : c.LDFLAGS + " -r1 -m _prolog"
}
)
n.build(
c.REL_OUT,
rule="elf2rel",
inputs=[c.REL_PLF, c.DOL_ELF],
variables={
"flags" : f"-n 20 --name-size 0x38 -v 2 -r {c.REL_YML}"
}
)
n.build(
c.REL_OK,
rule = "sha1sum",
inputs = c.REL_SHA,
implicit = [c.REL_OUT]
)
n.default(c.REL_OK)
# Optional full binary disassembly
n.build(
c.DOL_FULL,
rule = "disasm",
inputs=[c.DOL_YML, c.DOL_LABELS, c.DOL_RELOCS],
implicit=[c.GAME_SYMBOLS, c.DOL_DISASM_OVERRIDES],
variables={
"disasmflags" : f"-m {c.GAME_SYMBOLS} -o {c.REL_DISASM_OVERRIDES}"
}
)
n.build(
c.REL_FULL,
rule = "disasm",
inputs=[c.REL_YML, c.REL_LABELS, c.REL_RELOCS],
implicit=[c.GAME_SYMBOLS, c.REL_DISASM_OVERRIDES],
variables={
"disasmflags" : f"-m {c.GAME_SYMBOLS} -o {c.REL_DISASM_OVERRIDES}"
}
)
##########
# Ouptut #
##########
with open("build.ninja", 'w') as f:
f.write(outbuf.getvalue())
n.close()
+1
View File
@@ -0,0 +1 @@
c59d278ad8542bb05d6cbb632f60a0db05bef203 *out/foresta.rel
+1
View File
@@ -0,0 +1 @@
2ae8f56e7791d37e165bd5900921f2269f9515bf *out/main.dol
+94
View File
@@ -0,0 +1,94 @@
"""
Progress message generation
"""
from argparse import ArgumentParser
import os.path
import pickle
import json
from typing import Dict, List, Tuple
from prettytable import PrettyTable
import common as c
def load_progress_info(ctx: c.SourceContext, asm_list: str
) -> Tuple[Dict[str, int], Dict[str, int]]:
assert os.path.exists(ctx.labels), "Error: analysis has not ran!"
# Get data
raw = c.get_cmd_stdout(f"{c.PROGRESS} {ctx.binary} {ctx.labels} {ctx.slices}")
dat = json.loads(raw)
assert dat.get("version") == 2, "Outdated progress json version, try a clean & rebuild"
decomp_sizes = dat["decomp_slices_sizes"]
total_sizes = dat["total_sizes"]
symbol_sizes = dat["symbol_sizes"]
# Subtract undecompiled functions in decompiled slices
# TODO: this assumes none of .init is decompiled
with open(asm_list, 'rb') as f:
funcs = pickle.load(f)
for func in funcs:
decomp_sizes[".text"] -= symbol_sizes[str(func)]
return decomp_sizes, total_sizes
def print_binary_progress(sections: List[str], decomp_sizes: Dict[str, int],
total_sizes: Dict[str, int], binary: str, show_total: bool):
table = PrettyTable(["Section", "Decompiled Bytes", "Total Bytes", "Percentage"])
bin_decomp_size = 0
bin_total_size = 0
pct = lambda d, t: f"{(d/t)*100:.4f}"
for sec in sections:
decomp_size = decomp_sizes[sec]
total_size = total_sizes[sec]
bin_decomp_size += decomp_size
bin_total_size += total_size
table.add_row([
sec,
hex(decomp_size),
hex(total_size),
pct(decomp_size, total_size)
])
if show_total:
table.add_row([
"Total",
hex(bin_decomp_size),
hex(bin_total_size),
pct(bin_decomp_size, bin_total_size)
])
print(f"{binary} progress:")
print(table)
if __name__=="__main__":
parser = ArgumentParser()
parser.add_argument("-f", "--full", action="store_true", help="Print progress of all sections")
args = parser.parse_args()
decomp_sizes, total_sizes = load_progress_info(c.DOL_CTX, c.DOL_ASM_LIST)
rel_decomp_sizes, rel_total_sizes = load_progress_info(c.REL_CTX, c.REL_ASM_LIST)
if args.full:
print("Warning: progress for sections other than .text isn't fully accurate")
dol_secs = [
".init",
"extab_",
"extabindex_",
".text",
".ctors",
".dtors",
".rodata",
".data",
".bss",
".sdata",
".sbss",
".sdata2",
]
rel_secs = [
".text",
".rodata",
".data",
".bss",
]
else:
dol_secs = rel_secs = [".text"]
print_binary_progress(dol_secs, decomp_sizes, total_sizes, "main.dol", args.full)
print_binary_progress(rel_secs, rel_decomp_sizes, rel_total_sizes, "foresta.rel", args.full)
+4
View File
@@ -0,0 +1,4 @@
-r ./tools/ppcdis/requirements.txt
ninja_syntax
prettytable
Submodule
+1
Submodule tools/libyaz0 added at 5b5986401d
+128
View File
@@ -0,0 +1,128 @@
import re
from argparse import ArgumentParser
parser = ArgumentParser(description="Fetch symbols from map file")
parser.add_argument('mapf', type=str, help="Map file path")
parser.add_argument('relf', type=str, help="Rel file path")
parser.add_argument('yml_p', type=str, help="YML output path")
args = parser.parse_args()
class entry:
def __init__(self, address, size, virt, num, symbol, file, source):
self.address = address
self.size = size
self.virt = virt
self.num = num
self.symbol = symbol
self.file = file
self.source = source
def yaml(self):
return f"0x{self.virt}: {self.symbol}"
def shiftvirt(self, shift):
cast = int(self.virt, base=16)
self.virt = hex(cast+shift)[2:].upper()
def sourcefile(self):
if self.source != None:
return self.source
else:
if self.file.endswith(".o"):
return self.file.replace(".o", ".c")
else:
return self.file
allentries = []
for isrel, filename in enumerate((args.mapf, args.relf)):
with open(filename, 'r') as f:
data = f.readlines()
line = 0
while True:
while line < len(data) and not data[line].endswith("section layout\n"):
line += 1
if line >= len(data): break #end when the sections are exhausted
sect = data[line].replace(" section layout\n", '')
line += 4 #skip section header
entries = []
while data[line] != "\n":
entryof = data[line].find("(entry of") != -1
if entryof:
d = re.sub(r"\(entry of .*?\)", "", data[line])
else:
d = data[line]
d = d.strip().replace("\t", "").split(" ")
d = [x for x in d if x != ""]
#if sect == "extab":
# print(data[line])
# print(d)
if entryof:
source = d[5] if len(d) > 5 else None
entries.append(entry(d[0], d[1], d[2], None, d[3], d[4], source))
else:
source = d[6] if len(d) > 6 else None
entries.append(entry(d[0], d[1], d[2], d[3], d[4], d[5], source))
if isrel:
relshifts = {".bss": 0x8125A7C0, ".text": 0x803702A9, ".rodata": 0x80641260, ".data": 0x8064D500}
entries[-1].file = "rel/" + entries[-1].file
if entries[-1].source is not None:
entries[-1].source = "rel/" + entries[-1].source
if sect in relshifts:
entries[-1].shiftvirt(relshifts[sect])
else:
entries[-1].file = "src/" + entries[-1].file
if entries[-1].source is not None:
entries[-1].source = "src/" + entries[-1].source
line += 1
allentries.append(entries)
#print(sect, len(entries))
#if sect == "extab":
# for i in entries:
# print(i.yaml())
# print(i.address, i.size, i.virt, i.num, i.symbol, i.file)
line += 1
filereg = {"global": {}, }
duplicates = []
for sect in allentries:
if len(sect) == 0: continue
for e in sect:
if (e.num != "1"
and e.num is not None
and "$" not in e.symbol
and "@" not in e.symbol
and "..." not in e.symbol):
newfile = e.sourcefile()
if e.symbol not in filereg["global"] and e.symbol not in duplicates:
filereg["global"][e.symbol] = [e]
elif e.symbol in duplicates: # previously removed from global
if newfile not in filereg:
filereg[newfile] = {}
if e.symbol in filereg[newfile]:
print("same file collision:", newfile, e.symbol)
filereg[newfile][e.symbol].append(e)
else:
filereg[newfile][e.symbol] = [e]
else: # encountered a wild duplicate in global
duplicates.append(e.symbol)
oldfile = filereg["global"][e.symbol][0].sourcefile()
if oldfile not in filereg:
filereg[oldfile] = {}
if newfile not in filereg:
filereg[newfile] = {}
filereg[oldfile][e.symbol] = filereg["global"][e.symbol]
if oldfile == newfile:
print("same file collision:", newfile, e.symbol)
filereg[newfile][e.symbol].append(e)
else:
filereg[newfile][e.symbol] = [e]
filereg["global"].pop(e.symbol)
with open(args.yml_p, 'w') as d:
for file, entries in filereg.items():
print(f"{file}:", file=d)
flat_list = [item for sublist in entries.values() for item in sublist]
for e in sorted(flat_list, key=lambda x: x.virt):
print(" " + e.yaml(), file=d)
Submodule
+1
Submodule tools/ppcdis added at 0ab8975ba8
+12
View File
@@ -0,0 +1,12 @@
from argparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("input")
parser.add_argument("output")
args = parser.parse_args()
with open(args.input, encoding="utf-8") as f:
txt = f.read()
with open(args.output, 'w', encoding="shift-jis") as f:
f.write(txt)
+66
View File
@@ -0,0 +1,66 @@
import re
from argparse import ArgumentParser
parser = ArgumentParser(description="Fetch symbols from map file")
parser.add_argument('mapf', type=str, help="Map file path")
parser.add_argument('yml_p', type=str, help="YML output path")
args = parser.parse_args()
with open(args.mapf, 'r') as f:
data = f.readlines()
class entry:
def __init__(self, address, size, virt, num, symbol, file):
self.address = address
self.size = size
self.virt = virt
self.num = num
self.symbol = symbol
self.file = file
def yaml(self):
return f"0x{self.virt}: ENTRY"
allentries = []
line = 0
while True:
while line < len(data) and not data[line].endswith("section layout\n"):
line += 1
if line >= len(data): break #end when the sections are exhausted
sect = data[line].replace(" section layout\n", '')
line += 4 #skip section header
entries = []
while data[line] != "\n":
entryof = data[line].find("(entry of") != -1
if entryof:
d = re.sub(r"\(entry of .*?\)", "", data[line])
else:
d = data[line]
d = d.strip().replace("\t", "").split(" ")
d = [x for x in d if x != ""]
#if sect == "extab":
# print(data[line])
# print(d)
if entryof:
entries.append(entry(d[0], d[1], d[2], None, d[3], d[4]))
else:
entries.append(entry(d[0], d[1], d[2], d[3], d[4], d[5]))
line += 1
allentries.append(entries)
#print(sect, len(entries))
#if sect == "extab":
# for i in entries:
# print(i.yaml())
# print(i.address, i.size, i.virt, i.num, i.symbol, i.file)
line += 1
with open(args.yml_p, 'w') as d:
print("global:", file=d)
for sect in allentries:
if len(sect) == 0: continue
# print(" #" + sect[0].yaml())
for e in sect:
if e.num is None and "..." not in e.symbol:
print(" " + e.yaml(), file=d)
+69
View File
@@ -0,0 +1,69 @@
import re
from argparse import ArgumentParser
parser = ArgumentParser(description="Fetch symbols from map file")
parser.add_argument('mapf', type=str, help="Map file path")
parser.add_argument('yml_p', type=str, help="YML output path")
args = parser.parse_args()
with open(args.mapf, 'r') as f:
data = f.readlines()
class entry:
def __init__(self, address, size, virt, num, symbol, file):
self.address = address
self.size = size
self.virt = virt
self.num = num
self.symbol = symbol
self.file = file
def yaml(self):
cast = int(self.virt, base=16)
Sum = cast+0x803701C0
Sum = hex(Sum)
return f"0x{Sum}: ENTRY"
allentries = []
line = 0
while True:
while line < len(data) and not data[line].endswith("section layout\n"):
line += 1
if line >= len(data): break #end when the sections are exhausted
sect = data[line].replace(" section layout\n", '')
line += 4 #skip section header
entries = []
while data[line] != "\n":
entryof = data[line].find("(entry of") != -1
if entryof:
d = re.sub(r"\(entry of .*?\)", "", data[line])
else:
d = data[line]
d = d.strip().replace("\t", "").split(" ")
d = [x for x in d if x != ""]
#if sect == "extab":
# print(data[line])
# print(d)
if entryof:
entries.append(entry(d[0], d[1], d[2], None, d[3], d[4]))
else:
entries.append(entry(d[0], d[1], d[2], d[3], d[4], d[5]))
line += 1
allentries.append(entries)
#print(sect, len(entries))
#if sect == "extab":
# for i in entries:
# print(i.yaml())
# print(i.address, i.size, i.virt, i.num, i.symbol, i.file)
line += 1
with open(args.yml_p, 'w') as d:
print("global:", file=d)
for sect in allentries:
if len(sect) == 0: continue
# print(" #" + sect[0].yaml())
for e in sect:
if e.num is None and "..." not in e.symbol:
print(" - " + e.yaml(), file=d)