diff --git a/README.md b/README.md index 17ecc6c2..cf10180e 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,9 @@ The Legend of Zelda: Skyward Sword [Build Status]: https://github.com/zeldaret/ss/actions/workflows/build.yml/badge.svg [actions]: https://github.com/zeldaret/ss/actions/workflows/build.yml -[Progress]: https://img.shields.io/endpoint?label=Code&url=https%3A%2F%2Fprogress.decomp.club%2Fdata%2Fss%2FSOUE01%2Fall%2F%3Fmode%3Dshield%26measure%3Dcode -[DOL Progress]: https://img.shields.io/endpoint?label=DOL&url=https%3A%2F%2Fprogress.decomp.club%2Fdata%2Fss%2FSOUE01%2Fdol%2F%3Fmode%3Dshield%26measure%3Dcode -[RELs Progress]: https://img.shields.io/endpoint?label=RELs&url=https%3A%2F%2Fprogress.decomp.club%2Fdata%2Fss%2FSOUE01%2Fmodules%2F%3Fmode%3Dshield%26measure%3Dcode +[Progress]: https://decomp.dev/zeldaret/ss.svg?mode=shield&measure=code&label=Code +[DOL Progress]: https://decomp.dev/zeldaret/ss.svg?mode=shield&measure=code&category=dol&label=DOL +[RELs Progress]: https://decomp.dev/zeldaret/ss.svg?mode=shield&measure=code&category=modules&label=RELs [Discord Badge]: https://img.shields.io/discord/688807550715560050?color=%237289DA&logo=discord&logoColor=%23FFFFFF [discord]: https://discord.zelda64.dev diff --git a/configure.py b/configure.py index 48bd489a..2e5c4fd6 100644 --- a/configure.py +++ b/configure.py @@ -15,6 +15,7 @@ import argparse import sys from pathlib import Path +from typing import Any, Dict, List from tools.project import ( Object, @@ -124,6 +125,8 @@ 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 @@ -156,6 +159,8 @@ config.asflags = [ "-I include", f"-I build/{config.version}/include", f"--defsym version={version_num}", + f"--defsym BUILD_VERSION={version_num}", + f"--defsym VERSION_{config.version}", ] config.linker_version = "Wii/1.5" config.ldflags = [ @@ -198,6 +203,8 @@ cflags_base = [ "-enc SJIS", "-i include", f"-i build/{config.version}/include", + f"-DBUILD_VERSION={version_num}", + f"-DVERSION_{config.version}", "-i src", "-i src/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common/Include", "-i src/PowerPC_EABI_Support/MSL/MSL_C/MSL_Common_Embedded/Math/Include", @@ -205,7 +212,6 @@ cflags_base = [ "-i src/PowerPC_EABI_Support/MSL/MSL_C++/MSL_Common/Include", "-i src/PowerPC_EABI_Support/Runtime/Inc", "-i src/PowerPC_EABI_Support/MetroTRK", - f"-DGAME_VERSION={version_num}", ] # Debug flags @@ -1661,9 +1667,7 @@ config.libs = [ Rel(NonMatching, "d_a_obj_tornado", "REL/d/a/obj/d_a_obj_tornado.cpp"), Rel(NonMatching, "d_a_obj_tower_bomb", "REL/d/a/obj/d_a_obj_tower_bomb.cpp"), Rel(NonMatching, "d_a_obj_tower_D101", "REL/d/a/obj/d_a_obj_tower_D101.cpp"), - Rel( - Matching, "d_a_obj_tower_gearD101", "REL/d/a/obj/d_a_obj_tower_gearD101.cpp" - ), + Rel(Matching, "d_a_obj_tower_gearD101", "REL/d/a/obj/d_a_obj_tower_gearD101.cpp"), Rel( NonMatching, "d_a_obj_tower_hand_D101", @@ -1877,6 +1881,24 @@ config.libs = [ Rel(NonMatching, "d_t_tumble_weed", "REL/d/t/d_t_tumble_weed.cpp"), ] + +# Optional callback to adjust link order. This can be used to add, remove, or reorder objects. +# This is called once per module, with the module ID and the current link order. +# +# For example, this adds "dummy.c" to the end of the DOL link order if configured with --non-matching. +# "dummy.c" *must* be configured as a Matching (or Equivalent) object in order to be linked. +def link_order_callback(module_id: int, objects: List[str]) -> List[str]: + # Don't modify the link order for matching builds + if not config.non_matching: + return objects + if module_id == 0: # DOL + return objects + ["dummy.c"] + return objects + + +# Uncomment to enable the link order callback. +# config.link_order_callback = link_order_callback + # Optional extra categories for progress tracking config.progress_categories = [ ProgressCategory("core", "Core Game Engine"), diff --git a/tools/download_tool.py b/tools/download_tool.py index f4512d01..b2634880 100644 --- a/tools/download_tool.py +++ b/tools/download_tool.py @@ -92,6 +92,23 @@ TOOLS: Dict[str, Callable[[str], str]] = { } +def download(url, response, output) -> None: + if url.endswith(".zip"): + data = io.BytesIO(response.read()) + with zipfile.ZipFile(data) as f: + f.extractall(output) + # Make all files executable + for root, _, files in os.walk(output): + for name in files: + os.chmod(os.path.join(root, name), 0o755) + output.touch(mode=0o755) # Update dir modtime + else: + with open(output, "wb") as f: + shutil.copyfileobj(response, f) + st = os.stat(output) + os.chmod(output, st.st_mode | stat.S_IEXEC) + + def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("tool", help="Tool name") @@ -104,21 +121,25 @@ def main() -> None: print(f"Downloading {url} to {output}") req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) - with urllib.request.urlopen(req) as response: - if url.endswith(".zip"): - data = io.BytesIO(response.read()) - with zipfile.ZipFile(data) as f: - f.extractall(output) - # Make all files executable - for root, _, files in os.walk(output): - for name in files: - os.chmod(os.path.join(root, name), 0o755) - output.touch(mode=0o755) # Update dir modtime - else: - with open(output, "wb") as f: - shutil.copyfileobj(response, f) - st = os.stat(output) - os.chmod(output, st.st_mode | stat.S_IEXEC) + try: + with urllib.request.urlopen(req) as response: + download(url, response, output) + except urllib.error.URLError as e: + if str(e).find("CERTIFICATE_VERIFY_FAILED") == -1: + raise e + try: + import certifi + import ssl + except: + print( + '"certifi" module not found. Please install it using "python -m pip install certifi".' + ) + return + + with urllib.request.urlopen( + req, context=ssl.create_default_context(cafile=certifi.where()) + ) as response: + download(url, response, output) if __name__ == "__main__": diff --git a/tools/project.py b/tools/project.py index f794b8c6..bb282674 100644 --- a/tools/project.py +++ b/tools/project.py @@ -17,7 +17,20 @@ import os import platform import sys from pathlib import Path -from typing import IO, Any, Dict, Iterable, List, Optional, Set, Tuple, Union, cast +from typing import ( + Any, + Callable, + cast, + Dict, + IO, + Iterable, + List, + Optional, + Set, + Tuple, + TypedDict, + Union, +) from . import ninja_syntax from .ninja_syntax import serialize_path @@ -179,6 +192,9 @@ class ProjectConfig: self.scratch_preset_id: Optional[int] = ( None # Default decomp.me preset ID for scratches ) + self.link_order_callback: Optional[Callable[[int, List[str]], List[str]]] = ( + None # Callback to add/remove/reorder units within a module + ) # Progress output, progress.json and report.json config self.progress = True # Enable report.json generation and CLI progress output @@ -294,10 +310,38 @@ def make_flags_str(flags: Optional[List[str]]) -> str: return " ".join(flags) +# Unit configuration +class BuildConfigUnit(TypedDict): + object: Optional[str] + name: str + autogenerated: bool + + +# Module configuration +class BuildConfigModule(TypedDict): + name: str + module_id: int + ldscript: str + entry: str + units: List[BuildConfigUnit] + + +# Module link configuration +class BuildConfigLink(TypedDict): + modules: List[str] + + +# Build configuration generated by decomp-toolkit +class BuildConfig(BuildConfigModule): + version: str + modules: List[BuildConfigModule] + links: List[BuildConfigLink] + + # Load decomp-toolkit generated config.json def load_build_config( config: ProjectConfig, build_config_path: Path -) -> Optional[Dict[str, Any]]: +) -> Optional[BuildConfig]: if not build_config_path.is_file(): return None @@ -305,7 +349,7 @@ def load_build_config( return tuple(map(int, (v.split(".")))) f = open(build_config_path, "r", encoding="utf-8") - build_config: Dict[str, Any] = json.load(f) + build_config: BuildConfig = json.load(f) config_version = build_config.get("version") if config_version is None: print("Invalid config.json, regenerating...") @@ -321,6 +365,24 @@ def load_build_config( return None f.close() + + # Apply link order callback + if config.link_order_callback: + modules: List[BuildConfigModule] = [build_config, *build_config["modules"]] + for module in modules: + unit_names = list(map(lambda u: u["name"], module["units"])) + unit_names = config.link_order_callback(module["module_id"], unit_names) + units: List[BuildConfigUnit] = [] + for unit_name in unit_names: + units.append( + # Find existing unit or create a new one + next( + (u for u in module["units"] if u["name"] == unit_name), + {"object": None, "name": unit_name, "autogenerated": False}, + ) + ) + module["units"] = units + return build_config @@ -338,7 +400,7 @@ def generate_build(config: ProjectConfig) -> None: def generate_build_ninja( config: ProjectConfig, objects: Dict[str, Object], - build_config: Optional[Dict[str, Any]], + build_config: Optional[BuildConfig], ) -> None: out = io.StringIO() n = ninja_syntax.Writer(out) @@ -699,9 +761,9 @@ def generate_build_ninja( return path.parent / (path.name + ".MAP") class LinkStep: - def __init__(self, config: Dict[str, Any]) -> None: - self.name: str = config["name"] - self.module_id: int = config["module_id"] + def __init__(self, config: BuildConfigModule) -> None: + self.name = config["name"] + self.module_id = config["module_id"] self.ldscript: Optional[Path] = Path(config["ldscript"]) self.entry = config["entry"] self.inputs: List[str] = [] @@ -907,13 +969,14 @@ def generate_build_ninja( return obj_path - def add_unit(build_obj, link_step: LinkStep): + def add_unit(build_obj: BuildConfigUnit, link_step: LinkStep): obj_path, obj_name = build_obj["object"], build_obj["name"] obj = objects.get(obj_name) if obj is None: if config.warn_missing_config and not build_obj["autogenerated"]: print(f"Missing configuration for {obj_name}") - link_step.add(obj_path) + if obj_path is not None: + link_step.add(Path(obj_path)) return link_built_obj = obj.completed @@ -942,12 +1005,7 @@ def generate_build_ninja( link_step.add(built_obj_path) elif obj_path is not None: # Use the original (extracted) object - link_step.add(obj_path) - else: - lib_name = obj.options["lib"] - sys.exit( - f"Missing object for {obj_name}: {obj.src_path} {lib_name} {obj}" - ) + link_step.add(Path(obj_path)) # Add DOL link step link_step = LinkStep(build_config) @@ -1268,7 +1326,7 @@ def generate_build_ninja( def generate_objdiff_config( config: ProjectConfig, objects: Dict[str, Object], - build_config: Optional[Dict[str, Any]], + build_config: Optional[BuildConfig], ) -> None: if build_config is None: return @@ -1333,7 +1391,7 @@ def generate_objdiff_config( } def add_unit( - build_obj: Dict[str, Any], module_name: str, progress_categories: List[str] + build_obj: BuildConfigUnit, module_name: str, progress_categories: List[str] ) -> None: obj_path, obj_name = build_obj["object"], build_obj["name"] base_object = Path(obj_name).with_suffix("") @@ -1481,7 +1539,7 @@ def generate_objdiff_config( def generate_compile_commands( config: ProjectConfig, objects: Dict[str, Object], - build_config: Optional[Dict[str, Any]], + build_config: Optional[BuildConfig], ) -> None: if build_config is None or not config.generate_compile_commands: return @@ -1570,7 +1628,7 @@ def generate_compile_commands( clangd_config = [] - def add_unit(build_obj: Dict[str, Any]) -> None: + def add_unit(build_obj: BuildConfigUnit) -> None: obj = objects.get(build_obj["name"]) if obj is None: return