Files
echoes/configure.py

409 lines
11 KiB
Python

#!/usr/bin/env python3
###
# Generates build files for the project.
# This file also includes the project configuration,
# such as compiler flags and the object matching status.
#
# Usage:
# python3 configure.py
# ninja
#
# Append --help to see available options.
###
import argparse
import sys
from pathlib import Path
from typing import Any, Dict, List
from tools.project import *
# Game versions
DEFAULT_VERSION = 0
VERSIONS = [
"G2ME01", # 0
"G2MJ01", # 1
"G2MP01", # 2
"R32J01", # 3
"R3ME01", # 4
"R3MP01", # 5
]
parser = argparse.ArgumentParser()
parser.add_argument(
"mode",
choices=["configure", "progress"],
default="configure",
help="script mode (default: configure)",
nargs="?",
)
parser.add_argument(
"-v",
"--version",
choices=VERSIONS,
type=str.upper,
default=VERSIONS[DEFAULT_VERSION],
help="version to build",
)
parser.add_argument(
"--build-dir",
metavar="DIR",
type=Path,
default=Path("build"),
help="base build directory (default: build)",
)
parser.add_argument(
"--binutils",
metavar="BINARY",
type=Path,
help="path to binutils (optional)",
)
parser.add_argument(
"--compilers",
metavar="DIR",
type=Path,
help="path to compilers (optional)",
)
parser.add_argument(
"--map",
action="store_true",
help="generate map file(s)",
)
parser.add_argument(
"--debug",
action="store_true",
help="build with debug info (non-matching)",
)
if not is_windows():
parser.add_argument(
"--wrapper",
metavar="BINARY",
type=Path,
help="path to wibo or wine (optional)",
)
parser.add_argument(
"--dtk",
metavar="BINARY | DIR",
type=Path,
help="path to decomp-toolkit binary or source (optional)",
)
parser.add_argument(
"--objdiff",
metavar="BINARY | DIR",
type=Path,
help="path to objdiff-cli binary or source (optional)",
)
parser.add_argument(
"--sjiswrap",
metavar="EXE",
type=Path,
help="path to sjiswrap.exe (optional)",
)
parser.add_argument(
"--verbose",
action="store_true",
help="print verbose output",
)
parser.add_argument(
"--non-matching",
dest="non_matching",
action="store_true",
help="builds equivalent (but non-matching) or modded objects",
)
args = parser.parse_args()
config = ProjectConfig()
config.version = str(args.version)
version_num = VERSIONS.index(config.version)
# Apply arguments
config.build_dir = args.build_dir
config.dtk_path = args.dtk
config.objdiff_path = args.objdiff
config.binutils_path = args.binutils
config.compilers_path = args.compilers
config.debug = args.debug
config.generate_map = args.map
config.non_matching = args.non_matching
config.sjiswrap_path = args.sjiswrap
if not is_windows():
config.wrapper = args.wrapper
# Don't build asm unless we're --non-matching
if not config.non_matching:
config.asm_dir = None
# Tool versions
config.binutils_tag = "2.42-1"
config.compilers_tag = "20240706"
config.dtk_tag = "v0.9.5"
config.objdiff_tag = "v2.0.0-beta.5"
config.sjiswrap_tag = "v1.1.1"
config.wibo_tag = "0.6.11"
# Project
config.config_path = Path("config") / config.version / "config.yml"
config.check_sha_path = Path("config") / config.version / "build.sha1"
config.asflags = [
"-mgekko",
"--strip-local-absolute",
"-I include",
f"-I build/{config.version}/include",
f"--defsym version={version_num}",
]
config.ldflags = [
"-fp hardware",
"-nodefaults",
"-listclosure",
]
# Use for any additional files that should cause a re-configure when modified
config.reconfig_deps = []
# Base flags, common to most GC/Wii games.
# Generally leave untouched, with overrides added below.
cflags_base = [
"-nodefaults",
"-proc gekko",
"-align powerpc",
"-enum int",
"-fp hardware",
"-Cpp_exceptions off",
# "-W all",
"-O4,p",
"-inline auto",
'-pragma "cats off"',
'-pragma "warn_notinlined off"',
"-maxerrors 1",
"-nosyspath",
"-RTTI off",
"-fp_contract on",
"-str reuse",
"-i include",
"-i libc",
f"-i build/{config.version}/include",
f"-DVERSION={version_num}",
]
# GC 3.0 and above require -enc SJIS instead of -multibyte
if version_num >= 3:
cflags_base.append("-enc SJIS")
else:
cflags_base.append("-multibyte")
# Debug flags
if config.debug:
cflags_base.extend(["-sym on", "-DDEBUG=1"])
else:
cflags_base.append("-DNDEBUG=1")
# Metrowerks library flags
cflags_runtime = [
*cflags_base,
"-use_lmw_stmw on",
"-str reuse,pool,readonly",
"-gccinc",
"-common off",
# "-inline auto",
]
# Retro flags
cflags_retro = [
*cflags_base,
"-use_lmw_stmw on",
"-str reuse,pool,readonly",
"-gccinc",
"-inline deferred,noauto",
"-common on",
]
# REL flags
cflags_rel = [
*cflags_retro,
"-sdata 0",
"-sdata2 0",
]
if version_num >= 3:
cflags_runtime.append("-inline auto")
config.linker_version = "GC/3.0a5"
else:
cflags_runtime.append("-inline deferred,auto")
config.linker_version = "GC/2.7"
if version_num > 0:
# RELs not yet set up for non-USA versions
config.build_rels = False
# Helper function for Dolphin libraries
def DolphinLib(lib_name: str, objects: List[Object]) -> Dict[str, Any]:
return {
"lib": lib_name,
"mw_version": "GC/1.2.5n",
"cflags": cflags_base,
"host": False,
"objects": objects,
}
# Helper function for REL script objects
def Rel(lib_name: str, objects: List[Object]) -> Dict[str, Any]:
return {
"lib": lib_name,
"mw_version": "GC/1.3.2",
"cflags": cflags_rel,
"host": True,
"objects": objects,
}
Matching = True # Object matches and should be linked
NonMatching = False # Object does not match and should not be linked
Equivalent = config.non_matching # Object should be linked when configured with --non-matching
config.warn_missing_config = True
config.warn_missing_source = False
config.libs = [
{
"lib": "MetroidPrime",
"cflags": cflags_retro,
"mw_version": "GC/2.7",
"host": True,
"objects": [
Object(NonMatching, "MetroidPrime/main.cpp"),
Object(NonMatching, "MetroidPrime/CStateManager.cpp"),
Object(NonMatching, "MetroidPrime/CEntity.cpp"),
Object(NonMatching, "MetroidPrime/Player/CPlayerState.cpp"),
Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptPickup.cpp"),
Object(Matching, "MetroidPrime/HUD/CHUDMemoParms.cpp"),
Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptHUDMemo.cpp"),
Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptAreaProperties.cpp"),
Object(NonMatching, "MetroidPrime/CHealthInfo.cpp"),
Object(NonMatching, "MetroidPrime/Player/CGameOptions.cpp"),
Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptSequenceTimer.cpp"),
Object(NonMatching, "MetroidPrime/ScriptLoaderRel.cpp"),
Object(NonMatching, "MetroidPrime/ScriptLoader.cpp"),
Object(NonMatching, "MetroidPrime/CAxisAngle.cpp"),
Object(NonMatching, "MetroidPrime/CEulerAngles.cpp"),
],
},
{
"lib": "Kyoto_CW",
"mw_version": "GC/2.7",
"cflags": cflags_retro,
"host": True,
"objects": [
Object(Matching, "Kyoto/Basics/CStopwatch.cpp"),
Object(Matching, "Kyoto/Basics/RAssertDolphin.cpp"),
Object(NonMatching, "Kyoto/Graphics/CGX.cpp"),
Object(NonMatching, "Kyoto/Math/CTransform4f.cpp"),
Object(Matching, "Kyoto/Math/CVector2f.cpp"),
Object(Matching, "Kyoto/Math/CVector2i.cpp"),
Object(Matching, "Kyoto/Math/CVector3d.cpp"),
Object(Matching, "Kyoto/Math/CVector3f.cpp"),
Object(Matching, "Kyoto/Math/CVector3i.cpp"),
Object(Matching, "Kyoto/Input/DolphinIController.cpp"),
Object(Matching, "Kyoto/Input/CDolphinController.cpp"),
Object(NonMatching, "Kyoto/Math/CMayaSpline.cpp"),
],
},
{
"lib": "Runtime.PPCEABI.H",
"mw_version": config.linker_version,
"cflags": cflags_runtime,
"host": False,
"objects": [
Object(Matching, "Runtime/global_destructor_chain.c"),
Object(Matching, "Runtime/__init_cpp_exceptions.cpp"),
# TODO: need to implement all
Object(NonMatching, "Runtime/Gecko_ExceptionPPC.cp"),
],
},
DolphinLib(
"ar",
[
Object(Matching, "Dolphin/ar/ar.c"),
Object(Matching, "Dolphin/ar/arq.c"),
],
),
DolphinLib(
"card",
[
Object(Matching, "Dolphin/card/CARDBios.c"),
Object(Matching, "Dolphin/card/CARDUnlock.c"),
Object(Matching, "Dolphin/card/CARDRdwr.c"),
Object(Matching, "Dolphin/card/CARDBlock.c"),
Object(Matching, "Dolphin/card/CARDDir.c"),
Object(Matching, "Dolphin/card/CARDCheck.c"),
Object(Matching, "Dolphin/card/CARDMount.c"),
Object(Matching, "Dolphin/card/CARDFormat.c"),
Object(Matching, "Dolphin/card/CARDOpen.c"),
Object(Matching, "Dolphin/card/CARDCreate.c"),
Object(Matching, "Dolphin/card/CARDRead.c"),
Object(Matching, "Dolphin/card/CARDWrite.c"),
Object(Matching, "Dolphin/card/CARDDelete.c"),
Object(Matching, "Dolphin/card/CARDStat.c"),
Object(Matching, "Dolphin/card/CARDRename.c"),
Object(Matching, "Dolphin/card/CARDStatEx.c"),
Object(Matching, "Dolphin/card/CARDRaw.c"),
Object(Matching, "Dolphin/card/CARDNet.c"),
Object(Matching, "Dolphin/card/CARDErase.c"),
Object(Matching, "Dolphin/card/CARDProgram.c"),
],
),
DolphinLib(
"base",
[
Object(Matching, "Dolphin/PPCArch.c"),
],
),
DolphinLib(
"os",
[
Object(Matching, "Dolphin/os/OSCache.c"),
Object(Matching, "Dolphin/os/OSContext.c"),
Object(Matching, "Dolphin/os/OSError.c"),
Object(Matching, "Dolphin/os/OSInterrupt.c"),
],
),
# Begin RELs
{
"lib": "REL",
"mw_version": "GC/2.7",
"cflags": cflags_rel,
"host": False,
"objects": [
Object(Matching, "REL/REL_Setup.cpp"),
Object(
Matching,
"REL/global_destructor_chain.c",
source="Runtime/global_destructor_chain.c",
),
],
},
Rel(
"ForgottenObject",
[
Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptForgottenObject.cpp"),
],
),
Rel(
"ScriptCannonBall",
[
Object(NonMatching, "MetroidPrime/ScriptObjects/CScriptCannonBall.cpp"),
],
),
]
# Optional extra categories for progress tracking
config.progress_categories = []
config.progress_each_module = args.verbose
if args.mode == "configure":
# Write build.ninja and objdiff.json
generate_build(config)
elif args.mode == "progress":
# Print progress and write progress.json
calculate_progress(config)
else:
sys.exit("Unknown mode: " + args.mode)