From 607753a08f8db72f0fc4e842276c6bb9da17a882 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Sun, 27 Nov 2022 23:06:16 -0500 Subject: [PATCH] Use decomp-toolkit --- Makefile | 232 --------- README.md | 27 +- configure.py | 745 +++++++++++++++++---------- dtk_version | 1 + tools/calcprogress.py => progress.py | 21 +- tools/Makefile | 15 - tools/decompctx.py | 55 +- tools/dol_diff.py | 22 - tools/download_dtk.py | 40 ++ tools/elf2dol.c | 504 ------------------ tools/metroidbuildinfo.c | 114 ---- 11 files changed, 570 insertions(+), 1206 deletions(-) delete mode 100644 Makefile create mode 100644 dtk_version rename tools/calcprogress.py => progress.py (94%) delete mode 100644 tools/Makefile delete mode 100644 tools/dol_diff.py create mode 100644 tools/download_dtk.py delete mode 100644 tools/elf2dol.c delete mode 100644 tools/metroidbuildinfo.c diff --git a/Makefile b/Makefile deleted file mode 100644 index f97732f6..00000000 --- a/Makefile +++ /dev/null @@ -1,232 +0,0 @@ -ifneq ($(findstring MINGW,$(shell uname)),) - WINDOWS := 1 -endif -ifneq ($(findstring MSYS,$(shell uname)),) - WINDOWS := 1 -endif - -# If 0, tells the console to chill out. (Quiets the make process.) -VERBOSE ?= 0 - -# If GENERATE_MAP set to 1, tells LDFLAGS to generate a mapfile, which makes linking take several minutes. -GENERATE_MAP ?= 0 - -# Enable non-matching code & various fixes -NONMATCHING ?= 0 - -ifeq ($(VERBOSE),0) - QUIET := @ -endif - -#------------------------------------------------------------------------------- -# Files -#------------------------------------------------------------------------------- - -NAME := mp1 -VERSION ?= 0 - -VERSION_NUM := $(VERSION) -ifeq ($(VERSION),kor) - VERSION_NUM := 2 -endif - -BUILD_DIR := build/$(NAME).$(VERSION) - -# Inputs -S_FILES := $(wildcard asm/*.s) -C_FILES := $(wildcard src/*.c) -CPP_FILES := $(wildcard src/*.cpp) -CPP_FILES += $(wildcard src/*.cp) -LDSCRIPT := $(BUILD_DIR)/ldscript.lcf - -# Outputs -DOL := $(BUILD_DIR)/main.dol -ELF := $(DOL:.dol=.elf) -MAP := $(BUILD_DIR)/MetroidPrime.MAP - - -ifeq ($(GENERATE_MAP),1) - MAPGEN := -map $(MAP) -endif - -include obj_files.mk - -O_FILES := $(METROTRK_FILES) \ - $(METROIDPRIME) $(WORLDFORMAT) $(WEAPONS) $(METARENDER) $(GUISYS) $(COLLISION) \ - $(KYOTO_1) $(ZLIB_FILES) $(KYOTO_2) $(AI_FILES) \ - $(AR_FILES) $(BASE_FILES) $(DB_FILES) $(DSP_FILES) $(DVD_FILES) $(GX_FILES) $(MTX_FILES) \ - $(OS_FILES) $(PAD_FILES) $(VI_FILES) $(MSL_PPCEABI_BARE_H) $(MSL_COMMON_MATH) $(MUSYX_FILES) \ - $(DTK_FILES) $(CARD_FILES) $(SI_FILES) $(EXI_FILES) $(THP_FILES) \ - $(GBA_FILES) -DEPENDS := $(O_FILES:.o=.d) -# If a specific .o file is passed as a target, also process its deps -DEPENDS += $(MAKECMDGOALS:.o=.d) - -#------------------------------------------------------------------------------- -# Tools -#------------------------------------------------------------------------------- - -MWCC_VERSION := 1.3.2 -MWLD_VERSION := 1.3.2 - -# Programs -export WINEDEBUG ?= -all -ifeq ($(WINDOWS),1) - WINE := - AS := $(DEVKITPPC)/bin/powerpc-eabi-as.exe - CPP := $(DEVKITPPC)/bin/powerpc-eabi-cpp.exe -P - PYTHON := py - SHA1SUM := sha1sum -else - WIBO := $(shell command -v wibo 2> /dev/null) - ifdef WIBO - WINE ?= wibo - else - WINE ?= wine - endif - DEVKITPPC ?= /opt/devkitpro/devkitPPC - DEPENDS := $(DEPENDS:.d=.d.unix) - AS := $(DEVKITPPC)/bin/powerpc-eabi-as - CPP := $(DEVKITPPC)/bin/powerpc-eabi-cpp -P - PYTHON := python3 - SHA1SUM := shasum -a 1 -endif -CC = $(WINE) tools/mwcc_compiler/$(MWCC_VERSION)/mwcceppc.exe -LD := $(WINE) tools/mwcc_compiler/$(MWLD_VERSION)/mwldeppc.exe -ELF2DOL := tools/elf2dol -METROIDBUILDINFO := tools/metroidbuildinfo - -TRANSFORM_DEP := tools/transform-dep.py -FRANK := tools/franklite.py - -# Options -INCLUDES := -i include/ -i libc/ -ASM_INCLUDES := -I include/ - -# DotKuribo/llvm-project -CLANG_CC ?= clang-kuribo -CLANG_CFLAGS := --target=ppc32-kuribo -mcpu=750 -nostdlib -fno-exceptions -fno-rtti -O3 -Wall -Wno-trigraphs -Wno-inline-new-delete -Wno-unused-private-field -fpermissive -std=gnu++11 $(ASM_INCLUDES) - -ASFLAGS := -mgekko $(ASM_INCLUDES) --defsym version=$(VERSION_NUM) -ifeq ($(VERBOSE),1) -# this set of LDFLAGS outputs warnings. -LDFLAGS := $(MAPGEN) -fp fmadd -nodefaults -endif -ifeq ($(VERBOSE),0) -# this set of LDFLAGS generates no warnings. -LDFLAGS := $(MAPGEN) -fp fmadd -nodefaults -w off -endif -DEFINES = -DPRIME1 -DVERSION=$(VERSION_NUM) -DNONMATCHING=$(NONMATCHING) -CFLAGS_BASE = -proc gekko -nodefaults -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -O4,p -maxerrors 1 -enum int -inline auto -str reuse -nosyspath -MMD $(DEFINES) $(INCLUDES) -CFLAGS = $(CFLAGS_BASE) -use_lmw_stmw on -str reuse,pool,readonly -gccinc -inline deferred,noauto -common on -CFLAGS_RUNTIME = $(CFLAGS_BASE) -use_lmw_stmw on -str reuse,pool,readonly -gccinc -inline deferred,auto -CFLAGS_MUSYX = $(CFLAGS_BASE) -fp hard -fp_contract off -str reuse,pool,readonly - -ifeq ($(VERBOSE),0) -# this set of ASFLAGS generates no warnings. -ASFLAGS += -W -endif - -$(METROTRK_FILES): MWCC_VERSION := 1.2.5 -$(METROTRK_FILES): CFLAGS := $(CFLAGS_BASE) -$(BASE_FILES): MWCC_VERSION := 1.2.5 -$(BASE_FILES): CFLAGS := $(CFLAGS_BASE) -$(AI_FILES): MWCC_VERSION := 1.2.5 -$(AI_FILES): CFLAGS := $(CFLAGS_BASE) -$(OS_FILES): MWCC_VERSION := 1.2.5 -$(OS_FILES): CFLAGS := $(CFLAGS_BASE) -$(CARD_FILES): MWCC_VERSION := 1.2.5 -$(CARD_FILES): CFLAGS := $(CFLAGS_BASE) -$(DVD_FILES): MWCC_VERSION := 1.2.5 -$(DVD_FILES): CFLAGS := $(CFLAGS_BASE) -$(DSP_FILES): MWCC_VERSION := 1.2.5 -$(DSP_FILES): CFLAGS := $(CFLAGS_BASE) -$(MUSYX_FILES): CFLAGS := $(CFLAGS_MUSYX) -$(ZLIB_FILES): CFLAGS := $(CFLAGS_RUNTIME) -$(MSL_PPCEABI_BARE_H): CFLAGS := $(CFLAGS_RUNTIME) -$(MSL_COMMON_MATH): CFLAGS := $(CFLAGS_RUNTIME) -$(PAD_FILES): MWCC_VERSION := 1.2.5 -$(PAD_FILES): CFLAGS := $(CFLAGS_BASE) -$(DTK_FILES): MWCC_VERSION := 1.2.5 -$(DTK_FILES): CFLAGS := $(CFLAGS_BASE) -$(SI_FILES): MWCC_VERSION := 1.2.5 -$(SI_FILES): CFLAGS := $(CFLAGS_BASE) -$(DB_FILES): MWCC_VERSION := 1.2.5 -$(DB_FILES): CFLAGS := $(CFLAGS_BASE) -$(GBA_FILES): MWCC_VERSION := 1.2.5 -$(GBA_FILES): CFLAGS := $(CFLAGS_BASE) - -#------------------------------------------------------------------------------- -# Recipes -#------------------------------------------------------------------------------- - -### Default target ### - -default: all - -all: $(DOL) - -.PHONY: tools - -$(LDSCRIPT): ldscript.lcf - $(QUIET) $(CPP) -MMD -MP -MT $@ -MF $@.d -I include/ -I . -DBUILD_DIR=$(BUILD_DIR) -o $@ $< - -$(DOL): $(ELF) | tools - $(QUIET) $(ELF2DOL) $< $@ - $(METROIDBUILDINFO) $@ buildstrings/$(NAME).$(VERSION).build - $(QUIET) $(SHA1SUM) -c sha1/$(NAME).$(VERSION).sha1 -ifneq ($(findstring -map,$(LDFLAGS)),) - $(QUIET) $(PYTHON) tools/calcprogress.py $(DOL) $(MAP) -endif - -clean: - $(RM) -r $(BUILD_DIR) - $(MAKE) -C tools clean -tools: - $(MAKE) -C tools - -# ELF creation makefile instructions -$(ELF): $(O_FILES) $(LDSCRIPT) - @echo Linking ELF $@ - $(QUIET) @echo $(O_FILES) > $(BUILD_DIR)/o_files - $(QUIET) $(LD) $(LDFLAGS) -o $@ -lcf $(LDSCRIPT) @$(BUILD_DIR)/o_files - -%.d.unix: %.d $(TRANSFORM_DEP) - @echo Processing $< - $(QUIET) $(PYTHON) $(TRANSFORM_DEP) $< $@ - --include $(DEPENDS) - -$(BUILD_DIR)/%.o: %.s - @echo "Assembling" $< - $(QUIET) mkdir -p $(dir $@) - $(QUIET) $(AS) $(ASFLAGS) -o $@ $< - -$(BUILD_DIR)/%.clang.o: %.cpp - @echo "Clang " $< - $(QUIET) mkdir -p $(dir $@) - $(QUIET) $(CLANG_CC) $(CLANG_CFLAGS) -c -o $@ $< - -$(BUILD_DIR)/%.ep.o: $(BUILD_DIR)/%.o - @echo Frank is fixing $< - $(QUIET) mkdir -p $(dir $@) - $(QUIET) $(PYTHON) $(FRANK) $< $@ - -$(BUILD_DIR)/%.o: %.c - @echo "Compiling " $< - $(QUIET) mkdir -p $(dir $@) - $(QUIET) $(CC) $(CFLAGS) -c -o $(dir $@) $< - -$(BUILD_DIR)/%.o: %.cp - @echo "Compiling " $< - $(QUIET) mkdir -p $(dir $@) - $(QUIET) $(CC) $(CFLAGS) -c -o $(dir $@) $< - -$(BUILD_DIR)/%.o: %.cpp - @echo "Compiling " $< - $(QUIET) mkdir -p $(dir $@) - $(QUIET) $(CC) $(CFLAGS) -c -o $(dir $@) $< - -### Debug Print ### - -print-% : ; $(info $* is a $(flavor $*) variable set to [$($*)]) @true diff --git a/README.md b/README.md index c7053e47..41a18e1b 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,10 @@ Dependencies Windows: -------- +- Install [ninja](https://github.com/ninja-build/ninja/releases) and add it to `%PATH%`. - Install [devkitPro](https://github.com/devkitPro/installer/releases/latest) with GameCube development package. - Open `C:\devkitPro\msys2\msys2.exe` -- Run the following: +- Install GameCube development packages: ``` pacman -Sy --noconfirm --needed msys2-keyring pacman -Su --noconfirm --needed gcc git gamecube-dev @@ -33,25 +34,28 @@ Windows: macOS: ------ -- Install wine: +- Install [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages): ``` - brew tap gcenx/wine - brew install wine-crossover - sudo xattr -r -d com.apple.quarantine "/Applications/Wine Crossover.app" + brew install ninja + ``` +- Install [wine-crossover](https://github.com/Gcenx/homebrew-wine): + ``` + brew install --cask --no-quarantine gcenx/wine/wine-crossover ``` - Install [devkitPro](https://github.com/devkitPro/pacman/releases/latest). -- Run the following: +- Install GameCube development packages: ``` sudo dkp-pacman -Syu --noconfirm --needed gamecube-dev ``` Linux: ------ +- Install [ninja](https://github.com/ninja-build/ninja/wiki/Pre-built-Ninja-packages). - Install wine from your package manager. - Faster alternative: [WiBo](https://github.com/decompals/WiBo), a minimal 32-bit Windows binary wrapper. Ensure the binary is in `PATH`. - Install [devkitPro](https://devkitpro.org/wiki/devkitPro_pacman). -- Run the following: +- Install GameCube development packages: ``` sudo dkp-pacman -Syu --noconfirm --needed gamecube-dev ``` @@ -64,8 +68,13 @@ Building git clone https://github.com/PrimeDecomp/prime.git ``` - Download [GC_WII_COMPILERS.zip](https://cdn.discordapp.com/attachments/727918646525165659/917185027656286218/GC_WII_COMPILERS.zip) -- Extract the contents of the `GC` directory to `tools/mwcc_compiler` +- Extract the _contents_ of the `GC` directory to `tools/mwcc_compiler`. + - Resulting structure should be (for example) `tools/mwcc_compiler/1.3.2/mwcceppc.exe` +- Configure: + ``` + python configure.py + ``` - Build: ``` - make -j + ninja ``` diff --git a/configure.py b/configure.py index 6193c84e..2b67bfb7 100755 --- a/configure.py +++ b/configure.py @@ -1,26 +1,4 @@ #!/usr/bin/env python3 -import os -import io -import sys -import argparse - -from shutil import which -from tools import ninja_syntax - -parser = argparse.ArgumentParser() -parser.add_argument('--version', dest='version', - default='0', help='version to build (0, 1, kor)') -parser.add_argument('--map', dest='map', action='store_true', - help='generate map file') -parser.add_argument('--no-check', dest='check', action='store_false', - help='don\'t check hash of resulting dol') -parser.add_argument('--static-libs', dest='static_libs', action='store_true', - help='build and use static libs') -parser.add_argument('--devkitppc', dest='devkitppc', help='path to devkitPPC') -if os.name != "nt" and not "_NT-" in os.uname().sysname: - parser.add_argument('--wine', dest='wine', help='path to wine (or wibo)') -args = parser.parse_args() - LIBS = [ { "lib": "TRK_MINNOW_DOLPHIN", @@ -652,7 +630,7 @@ LIBS = [ ["Kyoto/zlib/inftrees", True], ["Kyoto/zlib/infutil", True], ["Kyoto/zlib/zutil", True], - ] + ], }, { "lib": "Kyoto_CW2", @@ -861,9 +839,7 @@ LIBS = [ "mwcc_version": "1.2.5", "cflags": "$cflags_base", "host": False, - "objects": [ - "Dolphin/vi" - ], + "objects": ["Dolphin/vi"], }, { "lib": "MSL_C.PPCEABI.bare.H", @@ -1056,284 +1032,495 @@ LIBS = [ }, ] -# Create & link static libraries -# Disabled by default for now until we can get it working on windows/macOS -ENABLE_STATIC_LIBS = args.static_libs +if __name__ == "__main__": + import os + import io + import sys + import argparse -# On Windows, we need this to use && in commands -ALLOW_CHAIN = "cmd /c " if os.name == "nt" else "" + from shutil import which + from tools import ninja_syntax -out = io.StringIO() -n = ninja_syntax.Writer(out) + parser = argparse.ArgumentParser() + parser.add_argument( + "--version", + dest="version", + default="0", + help="version to build (0, 1, kor)", + ) + parser.add_argument( + "--map", + dest="map", + action="store_true", + help="generate map file", + ) + parser.add_argument( + "--no-check", + dest="check", + action="store_false", + help="don't check hash of resulting dol", + ) + parser.add_argument( + "--static-libs", + dest="static_libs", + action="store_true", + help="build and use static libs", + ) + parser.add_argument( + "--devkitppc", + dest="devkitppc", + help="path to devkitPPC", + ) + if os.name != "nt" and not "_NT-" in os.uname().sysname: + parser.add_argument( + "--wine", + dest="wine", + help="path to wine (or wibo)", + ) + parser.add_argument( + "--build-dtk", + dest="build_dtk", + help="path to decomp-toolkit source", + ) + args = parser.parse_args() -n.variable("ninja_required_version", "1.3") -n.newline() + # On Windows, we need this to use && in commands + ALLOW_CHAIN = "cmd /c " if os.name == "nt" else "" -n.comment("The arguments passed to configure.py, for rerunning it.") -configure_args = sys.argv[1:] -# Ignore DEVKITPPC env var on Windows -if os.name != "nt" and "DEVKITPPC" in os.environ and not args.devkitppc: - configure_args.extend(["--devkitppc", os.environ["DEVKITPPC"]]) -n.variable("configure_args", configure_args) -n.newline() + out = io.StringIO() + n = ninja_syntax.Writer(out) -### -# Variables -### -n.variable("version", args.version.lower()) -if args.version.lower() == 'kor': - n.variable("version_num", "2") -elif args.version.isnumeric() and int(args.version) in [0, 1]: - n.variable("version_num", args.version) -else: - sys.exit(f"Invalid version \"{args.version}\"") -n.variable("builddir", "build/mp1.$version") -if args.devkitppc: - n.variable("devkitppc", args.devkitppc) -elif os.name == "nt": - n.variable("devkitppc", "C:\devkitPro\devkitPPC") -elif "DEVKITPPC" in os.environ: - n.variable("devkitppc", os.environ["DEVKITPPC"]) -else: - n.variable("devkitppc", "/opt/devkitpro/devkitPPC") -n.variable("cflags_base", "-proc gekko -nodefaults -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -O4,p -maxerrors 1 -enum int -inline auto -str reuse -nosyspath -MMD -DPRIME1 -DVERSION=$version_num -DNONMATCHING=0 -i include/ -i libc/") -n.variable("cflags_retro", - "$cflags_base -use_lmw_stmw on -str reuse,pool,readonly -gccinc -inline deferred,noauto -common on") -n.variable("cflags_runtime", - "$cflags_base -use_lmw_stmw on -str reuse,pool,readonly -gccinc -inline deferred,auto") -n.variable("cflags_musyx", "$cflags_base -str reuse,pool,readonly") -n.variable("asflags", "-mgekko -I include/ --defsym version=$version_num -W") -ldflags = "-fp fmadd -nodefaults -lcf ldscript.lcf -w off" -if args.map: - ldflags += " -map $builddir/MetroidPrime.MAP" -n.variable("ldflags", ldflags) -n.variable("mwcc_version", "1.3.2") -n.variable("python", sys.executable) -if os.name == "nt": - n.variable("sha1sum", "sha1sum") - n.variable("exe", ".exe") -else: - n.variable("sha1sum", "shasum -a 1") - if "_NT-" in os.uname().sysname: - # MSYS2 - n.variable("wine", "") - elif args.wine: - n.variable("wine", args.wine + " ") - elif which("wibo") is not None: - n.variable("wine", "wibo ") + n.variable("ninja_required_version", "1.3") + n.newline() + + n.comment("The arguments passed to configure.py, for rerunning it.") + configure_args = sys.argv[1:] + # Ignore DEVKITPPC env var on Windows + if os.name != "nt" and "DEVKITPPC" in os.environ and not args.devkitppc: + configure_args.extend(["--devkitppc", os.environ["DEVKITPPC"]]) + n.variable("configure_args", configure_args) + n.newline() + + ### + # Variables + ### + n.comment("Variables") + n.variable("version", args.version.lower()) + if args.version.lower() == "kor": + n.variable("version_num", "2") + elif args.version.isnumeric() and int(args.version) in [0, 1]: + n.variable("version_num", args.version) else: - n.variable("wine", "wine ") - n.variable("exe", "") -n.newline() -n.variable("host_cflags", "-I include/ -Wno-trigraphs") -n.variable("host_cppflags", - "-std=c++98 -I include/ -fno-exceptions -fno-rtti -D_CRT_SECURE_NO_WARNINGS -Wno-trigraphs -Wno-c++11-extensions") - -### -# Rules -### -n.newline() -if os.name == "nt": - n.rule(name="mwcc", command="tools\\mwcc_compiler\\$mwcc_version\\mwcceppc.exe $cflags -c $in -o $basedir", - description="MWCC $out", depfile="$basefile.d", deps="gcc") - n.newline() - n.rule(name="mwcc_frank", command=ALLOW_CHAIN+"tools\\mwcc_compiler\\$mwcc_version\\mwcceppc.exe $cflags -c $in -o $basedir && " + - "$python tools/franklite.py $out $out", - description="FRANK $out", depfile="$basefile.d", deps="gcc") - n.newline() - n.rule(name="link", command="tools\\mwcc_compiler\\$mwcc_version\\mwldeppc.exe $ldflags -o $out @$out.rsp", - description="LINK $out", rspfile="$out.rsp", rspfile_content="$in") - n.newline() - n.rule(name="as", command="$devkitppc\\bin\\powerpc-eabi-as.exe $asflags -o $out $in -MD $out.d", - description="AS $out", depfile="$out.d", deps="gcc") - n.newline() - n.rule(name="ar", command="$devkitppc\\bin\\powerpc-eabi-ar.exe crs $out $in", - description="AR $out") - n.newline() -else: - n.rule(name="mwcc", command="${wine}tools/mwcc_compiler/$mwcc_version/mwcceppc.exe $cflags -c $in -o $basedir && " + - "$python tools/transform-dep.py $basefile.d $basefile.d", - description="MWCC $out", depfile="$basefile.d", deps="gcc") - n.newline() - n.rule(name="mwcc_frank", command="${wine}tools/mwcc_compiler/$mwcc_version/mwcceppc.exe $cflags -c $in -o $basedir && " + - "$python tools/franklite.py $out $out && " + - "$python tools/transform-dep.py $basefile.d $basefile.d", - description="FRANK $out", depfile="$basefile.d", deps="gcc") - n.newline() - n.rule(name="link", command="${wine}tools/mwcc_compiler/$mwcc_version/mwldeppc.exe $ldflags -o $out @$out.rsp", - description="LINK $out", rspfile="$out.rsp", rspfile_content="$in") - n.newline() - n.rule(name="as", command="$devkitppc/bin/powerpc-eabi-as $asflags -o $out $in -MD $out.d", - description="AS $out", depfile="$out.d", deps="gcc") - n.newline() - n.rule(name="ar", command="$devkitppc/bin/powerpc-eabi-ar crs $out $in", - description="AR $out") - n.newline() -n.rule(name="host_cc", command="clang $host_cflags -c -o $out $in", - description="host_cc $out") -n.rule(name="host_cpp", command="clang++ $host_cppflags -c -o $out $in", - description="host_c++ $out") -n.newline() - -### -# Build -### -all_source_files = [] -all_host_source_files = [] -for lib in LIBS: - inputs = [] - if "lib" in lib: - lib_name = lib["lib"] - n.comment(f"{lib_name}.a") + sys.exit(f'Invalid version "{args.version}"') + n.variable("builddir", "build/mp1.$version") + if args.devkitppc: + n.variable("devkitppc", args.devkitppc) + elif os.name == "nt": + n.variable("devkitppc", "C:\devkitPro\devkitPPC") + elif "DEVKITPPC" in os.environ: + n.variable("devkitppc", os.environ["DEVKITPPC"]) else: - n.comment("Loose files") - for object in lib["objects"]: - completed = None - add_to_all = True - if type(object) is list: - if len(object) > 2: - add_to_all = object[2] - completed = object[1] - object = object[0] + n.variable("devkitppc", "/opt/devkitpro/devkitPPC") + n.variable( + "cflags_base", + "-proc gekko -nodefaults -Cpp_exceptions off -RTTI off -fp hard -fp_contract on -O4,p -maxerrors 1 -enum int -inline auto -str reuse -nosyspath -MMD -DPRIME1 -DVERSION=$version_num -DNONMATCHING=0 -i include/ -i libc/", + ) + n.variable( + "cflags_retro", + "$cflags_base -use_lmw_stmw on -str reuse,pool,readonly -gccinc -inline deferred,noauto -common on", + ) + n.variable( + "cflags_runtime", + "$cflags_base -use_lmw_stmw on -str reuse,pool,readonly -gccinc -inline deferred,auto", + ) + n.variable("cflags_musyx", "$cflags_base -str reuse,pool,readonly") + n.variable("asflags", "-mgekko -I include/ --defsym version=$version_num -W") + ldflags = "-fp fmadd -nodefaults -lcf ldscript.lcf -w off" + if args.map: + ldflags += " -map $builddir/MetroidPrime.MAP" + n.variable("ldflags", ldflags) + n.variable("mwcc_version", "1.3.2") + n.variable("python", sys.executable) + if os.name == "nt": + n.variable("exe", ".exe") + else: + if "_NT-" in os.uname().sysname: + # MSYS2 + n.variable("wine", "") + elif args.wine: + n.variable("wine", args.wine + " ") + elif which("wibo") is not None: + n.variable("wine", "wibo ") + else: + n.variable("wine", "wine ") + n.variable("exe", "") + n.newline() - mwcc_version = lib["mwcc_version"] - c_file = None - if os.path.exists(os.path.join("src", f"{object}.cpp")): - c_file = os.path.join("src", f"{object}.cpp") - elif os.path.exists(os.path.join("src", f"{object}.c")): - c_file = os.path.join("src", f"{object}.c") - if c_file is not None: - if completed is None: - print(f"Mark as incomplete: {c_file}") - rule = "mwcc" - if mwcc_version == "1.2.5e": - mwcc_version = "1.2.5" - rule = "mwcc_frank" - n.build(f"$builddir/src/{object}.o", rule, c_file, + ### + # Rules + ### + if os.name == "nt": + n.comment("MWCC build") + n.rule( + name="mwcc", + command="tools\\mwcc_compiler\\$mwcc_version\\mwcceppc.exe $cflags -c $in -o $basedir", + description="MWCC $out", + depfile="$basefile.d", + deps="gcc", + ) + n.newline() + n.comment("MWCC build with franklite") + n.rule( + name="mwcc_frank", + command=ALLOW_CHAIN + + "tools\\mwcc_compiler\\$mwcc_version\\mwcceppc.exe $cflags -c $in -o $basedir && " + + "$python tools/franklite.py $out $out", + description="FRANK $out", + depfile="$basefile.d", + deps="gcc", + ) + n.newline() + n.comment("Link ELF file") + n.rule( + name="link", + command="tools\\mwcc_compiler\\$mwcc_version\\mwldeppc.exe $ldflags -o $out @$out.rsp", + description="LINK $out", + rspfile="$out.rsp", + rspfile_content="$in", + ) + n.newline() + n.comment("Assemble asm") + n.rule( + name="as", + command="$devkitppc\\bin\\powerpc-eabi-as.exe $asflags -o $out $in -MD $out.d", + description="AS $out", + depfile="$out.d", + deps="gcc", + ) + n.newline() + n.comment("Create static library") + n.rule( + name="ar", + command="$devkitppc\\bin\\powerpc-eabi-ar.exe crs $out $in", + description="AR $out", + ) + n.newline() + else: + n.comment("MWCC build") + n.rule( + name="mwcc", + command="${wine}tools/mwcc_compiler/$mwcc_version/mwcceppc.exe $cflags -c $in -o $basedir && " + + "$python tools/transform-dep.py $basefile.d $basefile.d", + description="MWCC $out", + depfile="$basefile.d", + deps="gcc", + ) + n.newline() + n.comment("MWCC build with franklite") + n.rule( + name="mwcc_frank", + command="${wine}tools/mwcc_compiler/$mwcc_version/mwcceppc.exe $cflags -c $in -o $basedir && " + + "$python tools/franklite.py $out $out && " + + "$python tools/transform-dep.py $basefile.d $basefile.d", + description="FRANK $out", + depfile="$basefile.d", + deps="gcc", + ) + n.newline() + n.comment("Link ELF file") + n.rule( + name="link", + command="${wine}tools/mwcc_compiler/$mwcc_version/mwldeppc.exe $ldflags -o $out @$out.rsp", + description="LINK $out", + rspfile="$out.rsp", + rspfile_content="$in", + ) + n.newline() + n.comment("Assemble asm") + n.rule( + name="as", + command="$devkitppc/bin/powerpc-eabi-as $asflags -o $out $in -MD $out.d", + description="AS $out", + depfile="$out.d", + deps="gcc", + ) + n.newline() + n.comment("Create static library") + n.rule( + name="ar", + command="$devkitppc/bin/powerpc-eabi-ar crs $out $in", + description="AR $out", + ) + n.newline() + n.comment("Host build") + n.variable("host_cflags", "-I include/ -Wno-trigraphs") + n.variable( + "host_cppflags", + "-std=c++98 -I include/ -fno-exceptions -fno-rtti -D_CRT_SECURE_NO_WARNINGS -Wno-trigraphs -Wno-c++11-extensions", + ) + n.rule( + name="host_cc", + command="clang $host_cflags -c -o $out $in", + description="host_cc $out", + ) + n.rule( + name="host_cpp", + command="clang++ $host_cppflags -c -o $out $in", + description="host_c++ $out", + ) + n.newline() + + ### + # Rules for source files + ### + n.comment("Source files") + all_source_files = [] + all_host_source_files = [] + for lib in LIBS: + inputs = [] + if "lib" in lib: + lib_name = lib["lib"] + n.comment(f"{lib_name}.a") + else: + n.comment("Loose files") + for object in lib["objects"]: + completed = None + add_to_all = True + if type(object) is list: + if len(object) > 2: + add_to_all = object[2] + completed = object[1] + object = object[0] + + mwcc_version = lib["mwcc_version"] + c_file = None + if os.path.exists(os.path.join("src", f"{object}.cpp")): + c_file = os.path.join("src", f"{object}.cpp") + elif os.path.exists(os.path.join("src", f"{object}.c")): + c_file = os.path.join("src", f"{object}.c") + if c_file is not None: + if completed is None: + print(f"Mark as incomplete: {c_file}") + rule = "mwcc" + if mwcc_version == "1.2.5e": + mwcc_version = "1.2.5" + rule = "mwcc_frank" + n.build( + outputs=f"$builddir/src/{object}.o", + rule=rule, + inputs=c_file, variables={ "mwcc_version": mwcc_version, "cflags": lib["cflags"], "basedir": os.path.dirname(f"$builddir/src/{object}"), - "basefile": f"$builddir/src/{object}" - }) - if lib["host"]: - n.build(f"$builddir/host/{object}.o", "host_cc" if c_file.endswith(".c") else "host_cpp", c_file, + "basefile": f"$builddir/src/{object}", + }, + ) + if lib["host"]: + n.build( + outputs=f"$builddir/host/{object}.o", + rule="host_cc" if c_file.endswith(".c") else "host_cpp", + inputs=c_file, variables={ "basedir": os.path.dirname(f"$builddir/src/{object}"), - "basefile": f"$builddir/src/{object}" - }) + "basefile": f"$builddir/src/{object}", + }, + ) + if add_to_all: + all_host_source_files.append(f"$builddir/host/{object}.o") if add_to_all: - all_host_source_files.append(f"$builddir/host/{object}.o") - if add_to_all: - all_source_files.append(f"$builddir/src/{object}.o") - if os.path.exists(os.path.join("asm", f"{object}.s")): - n.build(f"$builddir/asm/{object}.o", "as", f"asm/{object}.s") - if completed: - inputs.append(f"$builddir/src/{object}.o") - else: - inputs.append(f"$builddir/asm/{object}.o") - if ENABLE_STATIC_LIBS and "lib" in lib: - lib_name = lib["lib"] - n.build(f"$builddir/lib/{lib_name}.a", "ar", inputs) - n.newline() - -n.comment("main.elf") -inputs = [] -for lib in LIBS: - if ENABLE_STATIC_LIBS and "lib" in lib: - lib_name = lib["lib"] - inputs.append(f"$builddir/lib/{lib_name}.a") - else: - for object in lib["objects"]: - completed = False - - if type(object) is list: - completed = object[1] - object = object[0] - + all_source_files.append(f"$builddir/src/{object}.o") + if os.path.exists(os.path.join("asm", f"{object}.s")): + n.build( + outputs=f"$builddir/asm/{object}.o", + rule="as", + inputs=f"asm/{object}.s", + ) if completed: inputs.append(f"$builddir/src/{object}.o") else: inputs.append(f"$builddir/asm/{object}.o") -if args.map: - n.build("$builddir/main.elf", "link", inputs, - implicit_outputs="$builddir/MetroidPrime.MAP") -else: - n.build("$builddir/main.elf", "link", inputs) -n.newline() + if args.static_libs and "lib" in lib: + lib_name = lib["lib"] + n.build( + outputs=f"$builddir/lib/{lib_name}.a", + rule="ar", + inputs=inputs, + ) + n.newline() -### -# Helper rule for building all source files -### -n.comment("Adds a command for building all source files") -n.build("all_source", "phony", all_source_files) -n.newline() + ### + # Link + ### + n.comment("Link") + inputs = [] + for lib in LIBS: + if args.static_libs and "lib" in lib: + lib_name = lib["lib"] + inputs.append(f"$builddir/lib/{lib_name}.a") + else: + for object in lib["objects"]: + completed = False -### -# Helper rule for building all source files, with a host compiler -### -n.comment("Adds a command for building all source files with a host compiler") -n.build("all_source_host", "phony", all_host_source_files) -n.newline() + if type(object) is list: + completed = object[1] + object = object[0] -### -# Generate DOL -### -n.comment("main.dol") -# TODO MSVC? -n.rule(name="cc", command="cc -MMD -MT $out -MF $out.d $in -o $out", - description="CC $out", depfile="$out.d", deps="gcc") -n.build("build/elf2dol$exe", "cc", "tools/elf2dol.c") -n.build("build/metroidbuildinfo$exe", "cc", "tools/metroidbuildinfo.c") -n.rule(name="elf2dol", - command=ALLOW_CHAIN+os.path.join("build", "elf2dol$exe")+" $in $out && " + - os.path.join("build", "metroidbuildinfo$exe") + - " $out buildstrings/mp1.$version.build", - description="DOL $out") -n.build("$builddir/main.dol", "elf2dol", "$builddir/main.elf", - implicit=["build/elf2dol$exe", "build/metroidbuildinfo$exe"]) -n.newline() - -### -# Check DOL hash -### -if args.check: - n.rule(name="check", command=ALLOW_CHAIN+"$sha1sum -c $in && touch $out", - description="CHECK $in") - n.build("$builddir/main.dol.ok", "check", - "sha1/mp1.$version.sha1", implicit="$builddir/main.dol") + if completed: + inputs.append(f"$builddir/src/{object}.o") + else: + inputs.append(f"$builddir/asm/{object}.o") + if args.map: + n.build( + outputs="$builddir/main.elf", + rule="link", + inputs=inputs, + implicit_outputs="$builddir/MetroidPrime.MAP", + ) + else: + n.build( + outputs="$builddir/main.elf", + rule="link", + inputs=inputs, + ) n.newline() -### -# Progress script -### -if args.map: - n.rule(name="progress", command=ALLOW_CHAIN+"$python tools/calcprogress.py $in -o $out", - description="PROGRESS $in") - n.build("$builddir/main.dol.progress", "progress", - ["$builddir/main.dol", "$builddir/MetroidPrime.MAP"]) + ### + # Helper rule for building all source files + ### + n.comment("Build all source files") + n.build( + outputs="all_source", + rule="phony", + inputs=all_source_files, + ) n.newline() -### -# Regenerate on change -### -n.comment("Regenerate build files if build script changes.") -n.rule(name="configure", command="$python configure.py $configure_args", generator=True) -n.build("build.ninja", "configure", implicit=[ - "configure.py", "tools/ninja_syntax.py"]) -n.newline() + ### + # Helper rule for building all source files, with a host compiler + ### + n.comment("Build all source files with a host compiler") + n.build( + outputs="all_source_host", + rule="phony", + inputs=all_host_source_files, + ) + n.newline() -### -# Default rule -### -dol_out = "$builddir/main.dol" -if args.check: - dol_out = "$builddir/main.dol.ok" -if args.map: - n.default([dol_out, "$builddir/main.dol.progress"]) -else: - n.default(dol_out) + ### + # Tooling + ### + n.comment("decomp-toolkit") + if args.build_dtk: + n.variable("dtk", os.path.join("build", "tools", "release", "dtk$exe")) + n.rule( + name="cargo", + command="cargo build --release --manifest-path $in --bin $bin --target-dir $target", + description="CARGO $bin", + depfile="$target/release/$bin.d", + deps="gcc", + ) + n.build( + outputs="$dtk", + rule="cargo", + inputs=os.path.join(args.build_dtk, "Cargo.toml"), + variables={ + "bin": "dtk", + "target": os.path.join("build", "tools"), + }, + ) + else: + n.variable("dtk", os.path.join("build", "tools", "dtk$exe")) + n.rule( + name="download_dtk", + command="$python tools/download_dtk.py $in $out", + description="DOWNLOAD $out", + ) + n.build( + outputs="$dtk", + rule="download_dtk", + inputs="dtk_version", + implicit=["tools/download_dtk.py"], + ) + n.rule( + name="elf2dol", + command=ALLOW_CHAIN + + "$dtk elf2dol $in $out && " + + "$dtk metroidbuildinfo $out buildstrings/mp1.$version.build", + description="DOL $out", + ) + n.build( + outputs="$builddir/main.dol", + rule="elf2dol", + inputs="$builddir/main.elf", + implicit="$dtk", + ) + n.newline() -with open("build.ninja", 'w') as f: - f.write(out.getvalue()) -n.close() + ### + # Check DOL hash + ### + if args.check: + n.comment("Check DOL hash") + n.rule( + name="check", + command=ALLOW_CHAIN + "$dtk shasum -c $in && touch $out", + description="CHECK $in", + ) + n.build( + outputs="$builddir/main.dol.ok", + rule="check", + inputs="sha1/mp1.$version.sha1", + implicit=["$builddir/main.dol", "$dtk"], + ) + n.newline() + + ### + # Progress script + ### + if args.map: + n.comment("Check progress") + n.rule( + name="progress", + command="$python progress.py $in -o $out", + description="PROGRESS $in", + ) + n.build( + outputs="$builddir/main.dol.progress", + rule="progress", + inputs=["$builddir/main.dol", "$builddir/MetroidPrime.MAP"], + ) + n.newline() + + ### + # Regenerate on change + ### + n.comment("Reconfigure on change") + n.rule( + name="configure", + command="$python configure.py $configure_args", + generator=True, + ) + n.build( + outputs="build.ninja", + rule="configure", + implicit=["configure.py", "tools/ninja_syntax.py"], + ) + n.newline() + + ### + # Default rule + ### + n.comment("Default rule") + if args.check: + dol_out = "$builddir/main.dol.ok" + else: + dol_out = "$builddir/main.dol" + if args.map: + n.default([dol_out, "$builddir/main.dol.progress"]) + else: + n.default([dol_out]) + + with open("build.ninja", "w") as f: + f.write(out.getvalue()) + n.close() diff --git a/dtk_version b/dtk_version new file mode 100644 index 00000000..b82608c0 --- /dev/null +++ b/dtk_version @@ -0,0 +1 @@ +v0.1.0 diff --git a/tools/calcprogress.py b/progress.py similarity index 94% rename from tools/calcprogress.py rename to progress.py index 78019d59..d90bd3f6 100644 --- a/tools/calcprogress.py +++ b/progress.py @@ -27,6 +27,8 @@ import math import argparse import json +from configure import LIBS + ############################################### # # # Constants # @@ -88,13 +90,22 @@ if __name__ == "__main__": parser.add_argument("-o", "--output", help="JSON output file") args = parser.parse_args() - # HACK: Check asm or src in obj_file.mk + # HACK: Check asm or src in configure.py # to avoid counting .comm/.lcomm as decompiled asm_objs = [] - with open('obj_files.mk', 'r') as file: - for line in file: - if "asm/" in line: - asm_objs.append(line.strip().rsplit('/', 1)[-1].rstrip('\\')) + for lib in LIBS: + for obj in lib["objects"]: + is_asm = False + obj_name = None + if type(obj) is list: + obj_name = obj[0] + is_asm = not obj[1] + else: + obj_name = obj + is_asm = True + if is_asm: + name = obj_name.split('/')[-1] + asm_objs.append(f"{name}.o") # Sum up DOL section sizes dol_handle = open(args.dol, "rb") diff --git a/tools/Makefile b/tools/Makefile deleted file mode 100644 index 60fbadca..00000000 --- a/tools/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -CC := gcc -CFLAGS := -O3 -Wall -s - -default: all - -all: elf2dol metroidbuildinfo - -elf2dol: elf2dol.c - $(CC) $(CFLAGS) -o $@ $^ - -metroidbuildinfo: metroidbuildinfo.c - $(CC) $(CFLAGS) -o $@ $^ - -clean: - $(RM) elf2dol metroidbuildinfo diff --git a/tools/decompctx.py b/tools/decompctx.py index e043a79c..d25d8c11 100755 --- a/tools/decompctx.py +++ b/tools/decompctx.py @@ -8,50 +8,53 @@ script_dir = os.path.dirname(os.path.realpath(__file__)) root_dir = os.path.abspath(os.path.join(script_dir, "..")) src_dir = os.path.join(root_dir, "src") include_dirs = [ - os.path.join(root_dir, "include"), - os.path.join(root_dir, "libc"), + os.path.join(root_dir, "include"), + os.path.join(root_dir, "libc"), ] include_pattern = re.compile(r'^#include\s*[<"](.+?)[>"]$') -guard_pattern = re.compile(r'^#ifndef\s+(.*)$') +guard_pattern = re.compile(r"^#ifndef\s+(.*)$") defines = set() + def import_h_file(in_file, r_path) -> str: rel_path = os.path.join(root_dir, r_path, in_file) if os.path.exists(rel_path): - return import_c_file(rel_path) + return import_c_file(rel_path) for include_dir in include_dirs: - inc_path = os.path.join(include_dir, in_file) - if os.path.exists(inc_path): - return import_c_file(inc_path) + inc_path = os.path.join(include_dir, in_file) + if os.path.exists(inc_path): + return import_c_file(inc_path) else: - print("Failed to locate", in_file) - return "" + print("Failed to locate", in_file) + return "" + def import_c_file(in_file) -> str: in_file = os.path.relpath(in_file, root_dir) - out_text = '' + out_text = "" with open(in_file) as file: - for idx, line in enumerate(file): - guard_match = guard_pattern.match(line.strip()) - if idx == 0: - if guard_match: - if guard_match[1] in defines: - break - defines.add(guard_match[1]) - print("Processing file", in_file) - include_match = include_pattern.match(line.strip()) - if include_match: - out_text += f"/* \"{in_file}\" line {idx} \"{include_match[1]}\" */\n" - out_text += import_h_file(include_match[1], os.path.dirname(in_file)) - out_text += f"/* end \"{include_match[1]}\" */\n" - else: - out_text += line + for idx, line in enumerate(file): + guard_match = guard_pattern.match(line.strip()) + if idx == 0: + if guard_match: + if guard_match[1] in defines: + break + defines.add(guard_match[1]) + print("Processing file", in_file) + include_match = include_pattern.match(line.strip()) + if include_match: + out_text += f'/* "{in_file}" line {idx} "{include_match[1]}" */\n' + out_text += import_h_file(include_match[1], os.path.dirname(in_file)) + out_text += f'/* end "{include_match[1]}" */\n' + else: + out_text += line return out_text + def main(): parser = argparse.ArgumentParser( description="""Create a context file which can be used for decomp.me""" @@ -69,4 +72,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/tools/dol_diff.py b/tools/dol_diff.py deleted file mode 100644 index 3322e1df..00000000 --- a/tools/dol_diff.py +++ /dev/null @@ -1,22 +0,0 @@ -from sys import argv - -def nextU32(f): - dat = f.read(4) - return int.from_bytes(dat, 'big') - -with open(argv[1], 'rb') as dol: - offsets = [nextU32(dol) for i in range(18)] - addresses = [nextU32(dol) for i in range(18)] - sizes = [nextU32(dol) for i in range(18)] - - target = int(argv[2], 16) - - for i in range(0, 0x18): - offset = offsets[i] - size = sizes[i] - if offset <= target < offset + size: - section = i - delta = target - offset - break - - print(hex(addresses[section] + delta)) diff --git a/tools/download_dtk.py b/tools/download_dtk.py new file mode 100644 index 00000000..3ce4f9af --- /dev/null +++ b/tools/download_dtk.py @@ -0,0 +1,40 @@ +import argparse +import urllib.request +import os +import stat +from pathlib import Path + +REPO = "https://github.com/encounter/decomp-toolkit" + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("tag_file", help="file containing GitHub tag") + parser.add_argument("output", type=Path, help="output file path") + args = parser.parse_args() + + with open(args.tag_file, "r") as f: + tag = f.readline().rstrip() + + uname = os.uname() + suffix = "" + platform = uname.sysname.lower() + if platform == "darwin": + platform = "macos" + elif platform == "windows": + suffix = ".exe" + arch = uname.machine.lower() + if arch == "amd64": + arch = "x86_64" + + url = f"{REPO}/releases/download/{tag}/dtk-{platform}-{arch}{suffix}" + output = args.output + # print(f"Downloading {url} to {output}") + urllib.request.urlretrieve(url, output) + + st = os.stat(output) + os.chmod(output, st.st_mode | stat.S_IEXEC) + + +if __name__ == "__main__": + main() diff --git a/tools/elf2dol.c b/tools/elf2dol.c deleted file mode 100644 index c71a7ce5..00000000 --- a/tools/elf2dol.c +++ /dev/null @@ -1,504 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#ifndef MAX -//! Get the maximum of two values -#define MAX(a, b) (((a) > (b)) ? (a) : (b)) -#endif - -#ifndef MIN -//! Get the minimum of two values -#define MIN(a, b) (((a) < (b)) ? (a) : (b)) -#endif - -#define ARRAY_COUNT(arr) (sizeof(arr) / sizeof((arr)[0])) - -#define EI_NIDENT 16 - -typedef struct { - unsigned char e_ident[EI_NIDENT]; - uint16_t e_type; - uint16_t e_machine; - uint32_t e_version; - uint32_t e_entry; - uint32_t e_phoff; - uint32_t e_shoff; - uint32_t e_flags; - uint16_t e_ehsize; - uint16_t e_phentsize; - uint16_t e_phnum; - uint16_t e_shentsize; - uint16_t e_shnum; - uint16_t e_shstrndx; -} Elf32_Ehdr; - -#define EI_CLASS 4 -#define EI_DATA 5 -#define EI_VERSION 6 -#define EI_PAD 7 -#define EI_NIDENT 16 - -#define ELFCLASS32 1 -#define ELFDATA2MSB 2 -#define EV_CURRENT 1 - -#define ET_EXEC 2 -#define EM_PPC 20 - -typedef struct { - uint32_t p_type; - uint32_t p_offset; - uint32_t p_vaddr; - uint32_t p_paddr; - uint32_t p_filesz; - uint32_t p_memsz; - uint32_t p_flags; - uint32_t p_align; -} Elf32_Phdr; - -#define PT_LOAD 1 -#define PF_R 4 -#define PF_W 2 -#define PF_X 1 - -int verbosity = 0; - -#if BYTE_ORDER == BIG_ENDIAN - -#define swap32(x) (x) -#define swap16(x) (x) - -#else - -static inline uint32_t swap32(uint32_t v) { - return (v >> 24) | ((v >> 8) & 0x0000FF00) | ((v << 8) & 0x00FF0000) | - (v << 24); -} - -static inline uint16_t swap16(uint16_t v) { return (v >> 8) | (v << 8); } - -#endif /* BIG_ENDIAN */ - -typedef struct { - - uint32_t text_off[7]; - uint32_t data_off[11]; - uint32_t text_addr[7]; - uint32_t data_addr[11]; - uint32_t text_size[7]; - uint32_t data_size[11]; - uint32_t bss_addr; - uint32_t bss_size; - uint32_t entry; - uint32_t pad[7]; -} DOL_hdr; - -#define HAVE_BSS 1 - -#define MAX_TEXT_SEGMENTS 7 -#define MAX_DATA_SEGMENTS 11 - -#define DOL_ALIGNMENT 32 - -#define DOL_ALIGN(x) (((x) + DOL_ALIGNMENT - 1) & ~(DOL_ALIGNMENT - 1)) - -typedef struct { - DOL_hdr header; - int text_cnt; - int data_cnt; - uint32_t text_elf_off[7]; - uint32_t data_elf_off[11]; - uint32_t flags; - FILE* elf; -} DOL_map; - -void usage(const char* name) { - fprintf(stderr, "Usage: %s [-h] [-v] [--] elf-file dol-file\n", name); - fprintf(stderr, " Convert an ELF file to a DOL file (by segments)\n"); - fprintf(stderr, " Options:\n"); - fprintf(stderr, " -h Show this help\n"); - fprintf(stderr, " -v Be more verbose (twice for even more)\n"); -} - -#define die(x) \ - { \ - fprintf(stderr, x "\n"); \ - exit(1); \ - } -#define perrordie(x) \ - { \ - perror(x); \ - exit(1); \ - } - -void ferrordie(FILE* f, const char* str) { - if (ferror(f)) { - fprintf(stderr, "Error while "); - perrordie(str); - } else if (feof(f)) { - fprintf(stderr, "EOF while %s\n", str); - exit(1); - } else { - fprintf(stderr, "Unknown error while %s\n", str); - exit(1); - } -} - -void add_bss(DOL_map* map, uint32_t paddr, uint32_t memsz) { - if (map->flags & HAVE_BSS) { - uint32_t curr_start = swap32(map->header.bss_addr); - uint32_t curr_size = swap32(map->header.bss_size); - if (paddr < curr_start) - map->header.bss_addr = swap32(paddr); - // Total BSS size should be the end of the last bss section minus the - // start of the first bss section. - if (paddr + memsz > curr_start + curr_size) - map->header.bss_size = swap32(paddr + memsz - curr_start); - } else { - map->header.bss_addr = swap32(paddr); - map->header.bss_size = swap32(memsz); - map->flags |= HAVE_BSS; - } -} - -void read_elf_segments(DOL_map* map, const char* elf) { - int read, i; - Elf32_Ehdr ehdr; - - if (verbosity >= 2) - fprintf(stderr, "Reading ELF file...\n"); - - map->elf = fopen(elf, "rb"); - if (!map->elf) - perrordie("Could not open ELF file"); - - read = fread(&ehdr, sizeof(ehdr), 1, map->elf); - if (read != 1) - ferrordie(map->elf, "reading ELF header"); - - if (memcmp(&ehdr.e_ident[0], "\177ELF", 4)) - die("Invalid ELF header"); - if (ehdr.e_ident[EI_CLASS] != ELFCLASS32) - die("Invalid ELF class"); - if (ehdr.e_ident[EI_DATA] != ELFDATA2MSB) - die("Invalid ELF byte order"); - if (ehdr.e_ident[EI_VERSION] != EV_CURRENT) - die("Invalid ELF ident version"); - if (swap32(ehdr.e_version) != EV_CURRENT) - die("Invalid ELF version"); - if (swap16(ehdr.e_type) != ET_EXEC) - die("ELF is not an executable"); - if (swap16(ehdr.e_machine) != EM_PPC) - die("Machine is not PowerPC"); - if (!swap32(ehdr.e_entry)) - die("ELF has no entrypoint"); - - map->header.entry = ehdr.e_entry; - - if (verbosity >= 2) - fprintf(stderr, "Valid ELF header found\n"); - - uint16_t phnum = swap16(ehdr.e_phnum); - uint32_t phoff = swap32(ehdr.e_phoff); - Elf32_Phdr* phdrs; - - if (!phnum || !phoff) - die("ELF has no program headers"); - - if (swap16(ehdr.e_phentsize) != sizeof(Elf32_Phdr)) - die("Invalid program header entry size"); - - phdrs = malloc(phnum * sizeof(Elf32_Phdr)); - - if (fseek(map->elf, phoff, SEEK_SET) < 0) - ferrordie(map->elf, "reading ELF program headers"); - read = fread(phdrs, sizeof(Elf32_Phdr), phnum, map->elf); - if (read != phnum) - ferrordie(map->elf, "reading ELF program headers"); - - for (i = 0; i < phnum; i++) { - if (swap32(phdrs[i].p_type) == PT_LOAD) { - uint32_t offset = swap32(phdrs[i].p_offset); - uint32_t paddr = swap32(phdrs[i].p_vaddr); - uint32_t filesz = swap32(phdrs[i].p_filesz); - uint32_t memsz = swap32(phdrs[i].p_memsz); - uint32_t flags = swap32(phdrs[i].p_flags); - if (memsz) { - if (verbosity >= 2) - fprintf(stderr, "PHDR %d: 0x%x [0x%x] -> 0x%08x [0x%x] flags 0x%x\n", - i, offset, filesz, paddr, memsz, flags); - if (flags & PF_X) { - // TEXT segment - if (!(flags & PF_R)) - fprintf(stderr, "Warning: non-readable segment %d\n", i); - if (flags & PF_W) - fprintf(stderr, "Warning: writable and executable segment %d\n", i); - if (filesz > memsz) { - fprintf(stderr, - "Error: TEXT segment %d memory size (0x%x) smaller than " - "file size (0x%x)\n", - i, memsz, filesz); - exit(1); - } else if (memsz > filesz) { - add_bss(map, paddr + filesz, memsz - filesz); - } - if (map->text_cnt >= MAX_TEXT_SEGMENTS) { - die("Error: Too many TEXT segments"); - } - map->header.text_addr[map->text_cnt] = swap32(paddr); - map->header.text_size[map->text_cnt] = swap32(filesz); - map->text_elf_off[map->text_cnt] = offset; - map->text_cnt++; - } else { - // DATA or BSS segment - if (!(flags & PF_R)) - fprintf(stderr, "Warning: non-readable segment %d\n", i); - if (filesz == 0) { - // BSS segment - add_bss(map, paddr, memsz); - } else { - // DATA segment - if (filesz > memsz) { - fprintf(stderr, - "Error: segment %d memory size (0x%x) is smaller than " - "file size (0x%x)\n", - i, memsz, filesz); - exit(1); - } - if (map->data_cnt >= MAX_DATA_SEGMENTS) { - die("Error: Too many DATA segments"); - } - map->header.data_addr[map->data_cnt] = swap32(paddr); - map->header.data_size[map->data_cnt] = swap32(filesz); - map->data_elf_off[map->data_cnt] = offset; - map->data_cnt++; - } - } - - } else { - if (verbosity >= 1) - fprintf(stderr, "Skipping empty program header %d\n", i); - } - } else if (verbosity >= 1) { - fprintf(stderr, "Skipping program header %d of type %d\n", i, - swap32(phdrs[i].p_type)); - } - } - if (verbosity >= 2) { - fprintf(stderr, "Segments:\n"); - for (i = 0; i < map->text_cnt; i++) { - fprintf(stderr, " TEXT %d: 0x%08x [0x%x] from ELF offset 0x%x\n", i, - swap32(map->header.text_addr[i]), - swap32(map->header.text_size[i]), map->text_elf_off[i]); - } - for (i = 0; i < map->data_cnt; i++) { - fprintf(stderr, " DATA %d: 0x%08x [0x%x] from ELF offset 0x%x\n", i, - swap32(map->header.data_addr[i]), - swap32(map->header.data_size[i]), map->data_elf_off[i]); - } - if (map->flags & HAVE_BSS) - fprintf(stderr, " BSS segment: 0x%08x [0x%x]\n", - swap32(map->header.bss_addr), swap32(map->header.bss_size)); - } -} - -void map_dol(DOL_map* map) { - uint32_t fpos; - int i; - - if (verbosity >= 2) - fprintf(stderr, "Laying out DOL file...\n"); - - fpos = DOL_ALIGN(sizeof(DOL_hdr)); - - for (i = 0; i < map->text_cnt; i++) { - if (verbosity >= 2) - fprintf(stderr, " TEXT segment %d at 0x%x\n", i, fpos); - map->header.text_off[i] = swap32(fpos); - fpos = DOL_ALIGN(fpos + swap32(map->header.text_size[i])); - } - for (i = 0; i < map->data_cnt; i++) { - if (verbosity >= 2) - fprintf(stderr, " DATA segment %d at 0x%x\n", i, fpos); - map->header.data_off[i] = swap32(fpos); - fpos = DOL_ALIGN(fpos + swap32(map->header.data_size[i])); - } - - if (map->text_cnt == 0) { - if (verbosity >= 1) - fprintf(stderr, - "Note: adding dummy TEXT segment to work around IOS bug\n"); - map->header.text_off[0] = swap32(DOL_ALIGN(sizeof(DOL_hdr))); - } - if (map->data_cnt == 0) { - if (verbosity >= 1) - fprintf(stderr, - "Note: adding dummy DATA segment to work around IOS bug\n"); - map->header.data_off[0] = swap32(DOL_ALIGN(sizeof(DOL_hdr))); - } -} - -#define BLOCK (1024 * 1024) - -void fcpy(FILE* dst, FILE* src, uint32_t dst_off, uint32_t src_off, - uint32_t size) { - int left = size; - int read; - int written; - int block; - void* blockbuf; - - if (fseek(src, src_off, SEEK_SET) < 0) - ferrordie(src, "reading ELF segment data"); - if (fseek(dst, dst_off, SEEK_SET) < 0) - ferrordie(dst, "writing DOL segment data"); - - blockbuf = malloc(MIN(BLOCK, left)); - - while (left) { - block = MIN(BLOCK, left); - read = fread(blockbuf, 1, block, src); - if (read != block) { - free(blockbuf); - ferrordie(src, "reading ELF segment data"); - } - written = fwrite(blockbuf, 1, block, dst); - if (written != block) { - free(blockbuf); - ferrordie(dst, "writing DOL segment data"); - } - left -= block; - } - free(blockbuf); -} - -void fpad(FILE* dst, uint32_t dst_off, uint32_t size) { - uint32_t i; - - if (fseek(dst, dst_off, SEEK_SET) < 0) - ferrordie(dst, "writing DOL segment data"); - for (i = 0; i < size; i++) - fputc(0, dst); -} - -void write_dol(DOL_map* map, const char* dol) { - FILE* dolf; - int written; - int i; - - if (verbosity >= 2) - fprintf(stderr, "Writing DOL file...\n"); - - dolf = fopen(dol, "wb"); - if (!dolf) - perrordie("Could not open DOL file"); - - if (verbosity >= 2) { - fprintf(stderr, "DOL header:\n"); - for (i = 0; i < MAX(1, map->text_cnt); i++) - fprintf(stderr, " TEXT %d @ 0x%08x [0x%x] off 0x%x\n", i, - swap32(map->header.text_addr[i]), - swap32(map->header.text_size[i]), - swap32(map->header.text_off[i])); - for (i = 0; i < MAX(1, map->data_cnt); i++) - fprintf(stderr, " DATA %d @ 0x%08x [0x%x] off 0x%x\n", i, - swap32(map->header.data_addr[i]), - swap32(map->header.data_size[i]), - swap32(map->header.data_off[i])); - if (swap32(map->header.bss_addr) && swap32(map->header.bss_size)) - fprintf(stderr, " BSS @ 0x%08x [0x%x]\n", swap32(map->header.bss_addr), - swap32(map->header.bss_size)); - fprintf(stderr, " Entry: 0x%08x\n", swap32(map->header.entry)); - fprintf(stderr, "Writing DOL header...\n"); - } - - // Write DOL header with aligned text and data section sizes - DOL_hdr aligned_header = map->header; - for (i = 0; i < ARRAY_COUNT(aligned_header.text_size); i++) - aligned_header.text_size[i] = - swap32(DOL_ALIGN(swap32(aligned_header.text_size[i]))); - for (i = 0; i < ARRAY_COUNT(aligned_header.data_size); i++) - aligned_header.data_size[i] = - swap32(DOL_ALIGN(swap32(aligned_header.data_size[i]))); - written = fwrite(&aligned_header, sizeof(DOL_hdr), 1, dolf); - if (written != 1) - ferrordie(dolf, "writing DOL header"); - - for (i = 0; i < map->text_cnt; i++) { - uint32_t size = swap32(map->header.text_size[i]); - uint32_t padded_size = DOL_ALIGN(size); - if (verbosity >= 2) - fprintf(stderr, "Writing TEXT segment %d...\n", i); - fcpy(dolf, map->elf, swap32(map->header.text_off[i]), map->text_elf_off[i], - size); - if (padded_size > size) - fpad(dolf, swap32(map->header.text_off[i]) + size, padded_size - size); - } - for (i = 0; i < map->data_cnt; i++) { - uint32_t size = swap32(map->header.data_size[i]); - uint32_t padded_size = DOL_ALIGN(size); - if (verbosity >= 2) - fprintf(stderr, "Writing DATA segment %d...\n", i); - fcpy(dolf, map->elf, swap32(map->header.data_off[i]), map->data_elf_off[i], - size); - if (padded_size > size) - fpad(dolf, swap32(map->header.data_off[i]) + size, padded_size - size); - } - - if (verbosity >= 2) - fprintf(stderr, "All done!\n"); - - fclose(map->elf); - fclose(dolf); -} - -int main(int argc, char** argv) { - char** arg; - - if (argc < 2) { - usage(argv[0]); - return 1; - } - arg = &argv[1]; - argc--; - - while (argc && *arg[0] == '-') { - if (!strcmp(*arg, "-h")) { - usage(argv[0]); - return 1; - } else if (!strcmp(*arg, "-v")) { - verbosity++; - } else if (!strcmp(*arg, "--")) { - arg++; - argc--; - break; - } else { - fprintf(stderr, "Unrecognized option %s\n", *arg); - usage(argv[0]); - return 1; - } - arg++; - argc--; - } - if (argc < 2) { - usage(argv[0]); - exit(1); - } - - const char* elf_file = arg[0]; - const char* dol_file = arg[1]; - - DOL_map map; - - memset(&map, 0, sizeof(map)); - - read_elf_segments(&map, elf_file); - map_dol(&map); - write_dol(&map, dol_file); - - return 0; -} diff --git a/tools/metroidbuildinfo.c b/tools/metroidbuildinfo.c deleted file mode 100644 index 4fce9e4b..00000000 --- a/tools/metroidbuildinfo.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include - -#define VERSION_MAX_LEN (size_t)(35) -#define METROID_BUILD_INFO_TAG "!#$MetroidBuildInfo!#$" - -void* memmem(const void* l, size_t l_len, const void* s, size_t s_len) { - register char *cur, *last; - const char* cl = (const char*)l; - const char* cs = (const char*)s; - - /* we need something to compare */ - if (l_len == 0 || s_len == 0) - return NULL; - - /* "s" must be smaller or equal to "l" */ - if (l_len < s_len) - return NULL; - - /* special case where s_len == 1 */ - if (s_len == 1) - return memchr(l, (int)*cs, l_len); - - /* the last position where its possible to find "s" in "l" */ - last = (char*)cl + l_len - s_len; - - for (cur = (char*)cl; cur <= last; cur++) - if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0) - return cur; - - return NULL; -} - -int main(int argc, const char* argv[]) { - if (argc < 3) { - fprintf(stdout, "\t--- METROID BUILD INFO ---\n" - "\tWritten by Phillip \"Antidote\" Stephens\n" - "\tReleased under the MIT License\n\n" - "\tSets the MetroidBuildInfo tag value in a given binary\n" - "\tThe version string can be a maximum of 35 characters,\n" - "\texcluding null terminator\n" - "\t--------------------------\n" - - ); - fprintf(stdout, "Usage:\n" - "\tmetroidbuildinfo \n"); - return -1; - } - - /* Let's try to get the source binary */ - FILE* source = fopen(argv[1], "rb"); - if (!source) { - fprintf(stderr, "Unable to open '%s'\nPlease ensure the file exists!\n", argv[1]); - return -2; - } - - char build_string[36] = {0}; - FILE* build = fopen(argv[2], "rb"); - - if (!build) { - fprintf(stderr, "Unable to open '%s'\nPlease ensure the file exists!\n", argv[2]); - return -3; - } - size_t read_len = fread(build_string, 1, 35, build); - fclose(build); - - if (read_len <= 0) { - fprintf(stderr, "Empty file %s specified for build version!\n", argv[2]); - return -4; - } - - build_string[strcspn(build_string, "\r\n")] = '\0'; - - /* Get source length */ - fseek(source, 0, SEEK_END); - size_t source_len = ftell(source); - rewind(source); - void* source_buf = malloc(source_len); - if (source_buf == NULL) { - fprintf(stderr, "Unable to allocate buffer of size %zubytes!\n", source_len); - return -5; - } - fread(source_buf, 1, source_len, source); - fclose(source); - - /* Find the build info tag so we can stuff our version info in the binary */ - void* ptr = - memmem(source_buf, source_len, METROID_BUILD_INFO_TAG, strlen(METROID_BUILD_INFO_TAG)); - if (ptr == NULL) { - fprintf(stderr, "Unable to find build info tag in source!\n"); - return -6; - } - - /* Lets actually copy over the build string */ - strcpy(ptr + strlen(METROID_BUILD_INFO_TAG), build_string); - - /* Now attempt to open the target file */ - FILE* target = fopen(argv[1], "wb"); - - if (!target) { - fprintf(stderr, "Unable to open '%s'\nPlease ensure you have write permissions!\n", argv[1]); - return -7; - } - - /* Finally write the buffer to the target file */ - fwrite(source_buf, 1, source_len, target); - fclose(target); - - /* Don't leak */ - free(source_buf); - - return 0; -}