{ description = "TrainingMode-CommunityEdition build"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; outputs = { self, nixpkgs, }: let system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; tmce-tools = pkgs.stdenv.mkDerivation { pname = "tmce-tools"; version = "1.0"; src = ./bin; nativeBuildInputs = [pkgs.autoPatchelfHook]; buildInputs = [pkgs.stdenv.cc.cc.lib]; installPhase = '' mkdir -p $out/bin cp gc_fst hgecko hmex $out/bin/ chmod +x $out/bin/* ''; }; devkitPPC = let image = pkgs.dockerTools.pullImage { imageName = "devkitpro/devkitppc"; imageDigest = "sha256:4c919aa26151dd43d88ca28c922d1fe2409579a8ba60ef56517baf1abdfb1a48"; sha256 = "TjTZiI1EaqU9Hr5e5RmBL2g9gvuY4Ea225zxtkh8DU8="; finalImageName = "devkitpro/devkitppc"; finalImageTag = "20260503"; }; in pkgs.stdenv.mkDerivation { pname = "devkitPPC"; version = "20260503"; src = image; nativeBuildInputs = [pkgs.autoPatchelfHook]; buildInputs = [pkgs.stdenv.cc.cc.lib pkgs.ncurses]; unpackPhase = '' mkdir merged ${pkgs.python3}/bin/python3 -c " import tarfile, json, io with tarfile.open('$src', 'r') as outer: manifest = json.load(outer.extractfile('manifest.json')) for layer_name in manifest[0]['Layers']: layer_data = outer.extractfile(layer_name).read() with tarfile.open(fileobj=io.BytesIO(layer_data)) as layer: for member in layer: if member.name.startswith('opt/devkitpro/devkitPPC') or \ member.name.startswith('opt/devkitpro/libogc') or \ member.name.startswith('opt/devkitpro/portlibs'): layer.extract(member, 'merged', filter='data') " ''; buildPhase = "true"; installPhase = '' mkdir -p $out cp -r merged/opt/devkitpro/devkitPPC $out/ cp -r merged/opt/devkitpro/libogc $out/ cp -r merged/opt/devkitpro/portlibs $out/ ''; }; hmex = "${tmce-tools}/bin/hmex"; hgecko = "${tmce-tools}/bin/hgecko"; DEVKITPPC = "${devkitPPC}/devkitPPC"; hmexFlags = "-Wall -Wextra -Wno-char-subscripts -Wno-builtin-declaration-mismatch -Wno-unused-parameter -DTM_DEBUG"; hmexLink = "MexTK/melee.link"; compileEvent = { name, sym, srcFiles, datFile ? null, }: pkgs.stdenv.mkDerivation { pname = name; version = "1.0"; src = ./.; nativeBuildInputs = [tmce-tools]; DEVKITPPC = "${devkitPPC}/devkitPPC"; buildPhase = '' mkdir -p build ${hmex} -q \ -l ${hmexLink} \ -f "${hmexFlags}" \ -s ${sym} \ -t MexTK/${sym}.txt \ -o "build/${name}.dat" \ -i ${srcFiles} \ ${pkgs.lib.optionalString (datFile != null) "-dat ${datFile}"} ''; installPhase = '' mkdir -p $out cp build/${name}.dat $out/ ''; }; events = { eventMenu = compileEvent { name = "eventMenu"; sym = "tmFunction"; srcFiles = "src/events.c src/menu.c src/osds.c src/savestate_v1.c"; datFile = "dats/eventMenu.dat"; }; labCSS = compileEvent { name = "labCSS"; sym = "cssFunction"; srcFiles = "src/lab_css.c"; datFile = "dats/labCSS.dat"; }; lab = compileEvent { name = "lab"; sym = "evFunction"; srcFiles = "src/lab.c"; datFile = "dats/lab.dat"; }; lcancel = compileEvent { name = "lcancel"; sym = "evFunction"; srcFiles = "src/lcancel.c"; datFile = "dats/lcancel.dat"; }; ledgedash = compileEvent { name = "ledgedash"; sym = "evFunction"; srcFiles = "src/ledgedash.c"; datFile = "dats/ledgedash.dat"; }; wavedash = compileEvent { name = "wavedash"; sym = "evFunction"; srcFiles = "src/wavedash.c"; datFile = "dats/wavedash.dat"; }; powershield = compileEvent { name = "powershield"; sym = "evFunction"; srcFiles = "src/powershield.c"; }; edgeguard = compileEvent { name = "edgeguard"; sym = "evFunction"; srcFiles = "src/edgeguard.c"; }; fc = compileEvent { name = "fc"; sym = "evFunction"; srcFiles = "src/fc.c"; datFile = "dats/ledgedash.dat"; }; sweetspot = compileEvent { name = "sweetspot"; sym = "evFunction"; srcFiles = "src/sweetspot.c"; }; eggs = compileEvent { name = "eggs"; sym = "evFunction"; srcFiles = "src/eggs.c"; }; techchase = compileEvent { name = "techchase"; sym = "evFunction"; srcFiles = "src/techchase.c"; }; slalom = compileEvent { name = "slalom"; sym = "evFunction"; srcFiles = "src/slalom.c"; datFile = "dats/wavedash.dat"; }; }; codesGct = pkgs.stdenv.mkDerivation { pname = "codes"; version = "1.0"; src = ./ASM; nativeBuildInputs = [tmce-tools]; DEVKITPPC = "${devkitPPC}/devkitPPC"; buildPhase = '' mkdir -p $out ${hgecko} -q . "$out/codes.gct" ''; installPhase = "true"; }; allDat = pkgs.symlinkJoin { name = "tmce-all-dat"; paths = pkgs.lib.attrValues events; }; mkISOScript = pkgs.writeShellApplication { name = "mkTM-ISO"; runtimeInputs = [tmce-tools pkgs.xdelta]; text = '' set -e ISO="''${1:-}" OUT="''${2:-TM-CE.iso}" if [ -z "$ISO" ] || [ ! -f "$ISO" ]; then echo "Usage: nix run .#mkISO -- /path/to/vanilla/melee.iso [output.iso]" exit 1 fi HEADER=$(gc_fst get-header "$ISO") case "$HEADER" in GALE01) PATCH="${self}/Build TM Start.dol/patch.xdelta" ;; GALJ01) PATCH="${self}/Build TM Start.dol/patch_jp.xdelta" ;; *) echo "Error: Unknown game ID '$HEADER' (expected GALE01 or GALJ01)"; exit 1 ;; esac TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT gc_fst read "$ISO" Start.dol "$TMP/ISOStart.dol" xdelta3 -dfs "$TMP/ISOStart.dol" "$PATCH" "$TMP/Start.dol" cp "$ISO" "$OUT" gc_fst fs "$OUT" \ delete MvHowto.mth \ delete MvOmake15.mth \ delete MvOpen.mth \ insert TM/eventMenu.dat "${allDat}/eventMenu.dat" \ insert TM/lab.dat "${allDat}/lab.dat" \ insert TM/labCSS.dat "${allDat}/labCSS.dat" \ insert TM/lcancel.dat "${allDat}/lcancel.dat" \ insert TM/ledgedash.dat "${allDat}/ledgedash.dat" \ insert TM/wavedash.dat "${allDat}/wavedash.dat" \ insert TM/powershield.dat "${allDat}/powershield.dat" \ insert TM/edgeguard.dat "${allDat}/edgeguard.dat" \ insert TM/fc.dat "${allDat}/fc.dat" \ insert TM/sweetspot.dat "${allDat}/sweetspot.dat" \ insert TM/eggs.dat "${allDat}/eggs.dat" \ insert TM/techchase.dat "${allDat}/techchase.dat" \ insert TM/slalom.dat "${allDat}/slalom.dat" \ insert codes.gct "${codesGct}/codes.gct" \ insert Start.dol "$TMP/Start.dol" \ insert opening.bnr "${self}/opening.bnr" gc_fst set-header "$OUT" "GTME01" "Training Mode Community Edition" echo "Built $OUT" ''; }; in { packages.${system} = { inherit tmce-tools devkitPPC; codes = codesGct; inherit allDat; } // events; apps.${system} = { mkISO = { type = "app"; program = "${mkISOScript}/bin/mkTM-ISO"; }; }; checks.${system} = let eventsCheck = pkgs.linkFarm "check-events" (pkgs.lib.mapAttrsToList (name: pkg: { inherit name; path = pkg; }) events); in events // { all-events = eventsCheck; codes = codesGct; }; formatter.${system} = pkgs.alejandra; devShells.${system}.default = pkgs.mkShell { buildInputs = with pkgs; [ xdelta tmce-tools devkitPPC ]; DEVKITPPC = "${devkitPPC}/devkitPPC"; }; }; }