nix flake for tm-ce dev on nixos
0
flake.nix
1{
2 description = "TrainingMode-CommunityEdition build";
3
4 inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
5
6 outputs = {
7 self,
8 nixpkgs,
9 }: let
10 system = "x86_64-linux";
11 pkgs = nixpkgs.legacyPackages.${system};
12
13 tmce-tools = pkgs.stdenv.mkDerivation {
14 pname = "tmce-tools";
15 version = "1.0";
16 src = ./bin;
17 nativeBuildInputs = [pkgs.autoPatchelfHook];
18 buildInputs = [pkgs.stdenv.cc.cc.lib];
19 installPhase = ''
20 mkdir -p $out/bin
21 cp gc_fst hgecko hmex $out/bin/
22 chmod +x $out/bin/*
23 '';
24 };
25
26 devkitPPC = let
27 image = pkgs.dockerTools.pullImage {
28 imageName = "devkitpro/devkitppc";
29 imageDigest = "sha256:4c919aa26151dd43d88ca28c922d1fe2409579a8ba60ef56517baf1abdfb1a48";
30 sha256 = "TjTZiI1EaqU9Hr5e5RmBL2g9gvuY4Ea225zxtkh8DU8=";
31 finalImageName = "devkitpro/devkitppc";
32 finalImageTag = "20260503";
33 };
34 in
35 pkgs.stdenv.mkDerivation {
36 pname = "devkitPPC";
37 version = "20260503";
38 src = image;
39 nativeBuildInputs = [pkgs.autoPatchelfHook];
40 buildInputs = [pkgs.stdenv.cc.cc.lib pkgs.ncurses];
41 unpackPhase = ''
42 mkdir merged
43 ${pkgs.python3}/bin/python3 -c "
44 import tarfile, json, io
45 with tarfile.open('$src', 'r') as outer:
46 manifest = json.load(outer.extractfile('manifest.json'))
47 for layer_name in manifest[0]['Layers']:
48 layer_data = outer.extractfile(layer_name).read()
49 with tarfile.open(fileobj=io.BytesIO(layer_data)) as layer:
50 for member in layer:
51 if member.name.startswith('opt/devkitpro/devkitPPC') or \
52 member.name.startswith('opt/devkitpro/libogc') or \
53 member.name.startswith('opt/devkitpro/portlibs'):
54 layer.extract(member, 'merged', filter='data')
55 "
56 '';
57 buildPhase = "true";
58 installPhase = ''
59 mkdir -p $out
60 cp -r merged/opt/devkitpro/devkitPPC $out/
61 cp -r merged/opt/devkitpro/libogc $out/
62 cp -r merged/opt/devkitpro/portlibs $out/
63 '';
64 };
65
66 hmex = "${tmce-tools}/bin/hmex";
67 hgecko = "${tmce-tools}/bin/hgecko";
68 DEVKITPPC = "${devkitPPC}/devkitPPC";
69 hmexFlags = "-Wall -Wextra -Wno-char-subscripts -Wno-builtin-declaration-mismatch -Wno-unused-parameter -DTM_DEBUG";
70 hmexLink = "MexTK/melee.link";
71
72 compileEvent = {
73 name,
74 sym,
75 srcFiles,
76 datFile ? null,
77 }:
78 pkgs.stdenv.mkDerivation {
79 pname = name;
80 version = "1.0";
81 src = ./.;
82 nativeBuildInputs = [tmce-tools];
83 DEVKITPPC = "${devkitPPC}/devkitPPC";
84 buildPhase = ''
85 mkdir -p build
86 ${hmex} -q \
87 -l ${hmexLink} \
88 -f "${hmexFlags}" \
89 -s ${sym} \
90 -t MexTK/${sym}.txt \
91 -o "build/${name}.dat" \
92 -i ${srcFiles} \
93 ${pkgs.lib.optionalString (datFile != null) "-dat ${datFile}"}
94 '';
95 installPhase = ''
96 mkdir -p $out
97 cp build/${name}.dat $out/
98 '';
99 };
100
101 events = {
102 eventMenu = compileEvent {
103 name = "eventMenu";
104 sym = "tmFunction";
105 srcFiles = "src/events.c src/menu.c src/osds.c src/savestate_v1.c";
106 datFile = "dats/eventMenu.dat";
107 };
108 labCSS = compileEvent {
109 name = "labCSS";
110 sym = "cssFunction";
111 srcFiles = "src/lab_css.c";
112 datFile = "dats/labCSS.dat";
113 };
114 lab = compileEvent {
115 name = "lab";
116 sym = "evFunction";
117 srcFiles = "src/lab.c";
118 datFile = "dats/lab.dat";
119 };
120 lcancel = compileEvent {
121 name = "lcancel";
122 sym = "evFunction";
123 srcFiles = "src/lcancel.c";
124 datFile = "dats/lcancel.dat";
125 };
126 ledgedash = compileEvent {
127 name = "ledgedash";
128 sym = "evFunction";
129 srcFiles = "src/ledgedash.c";
130 datFile = "dats/ledgedash.dat";
131 };
132 wavedash = compileEvent {
133 name = "wavedash";
134 sym = "evFunction";
135 srcFiles = "src/wavedash.c";
136 datFile = "dats/wavedash.dat";
137 };
138 powershield = compileEvent {
139 name = "powershield";
140 sym = "evFunction";
141 srcFiles = "src/powershield.c";
142 };
143 edgeguard = compileEvent {
144 name = "edgeguard";
145 sym = "evFunction";
146 srcFiles = "src/edgeguard.c";
147 };
148 fc = compileEvent {
149 name = "fc";
150 sym = "evFunction";
151 srcFiles = "src/fc.c";
152 datFile = "dats/ledgedash.dat";
153 };
154 sweetspot = compileEvent {
155 name = "sweetspot";
156 sym = "evFunction";
157 srcFiles = "src/sweetspot.c";
158 };
159 eggs = compileEvent {
160 name = "eggs";
161 sym = "evFunction";
162 srcFiles = "src/eggs.c";
163 };
164 techchase = compileEvent {
165 name = "techchase";
166 sym = "evFunction";
167 srcFiles = "src/techchase.c";
168 };
169 slalom = compileEvent {
170 name = "slalom";
171 sym = "evFunction";
172 srcFiles = "src/slalom.c";
173 datFile = "dats/wavedash.dat";
174 };
175 };
176
177 codesGct = pkgs.stdenv.mkDerivation {
178 pname = "codes";
179 version = "1.0";
180 src = ./ASM;
181 nativeBuildInputs = [tmce-tools];
182 DEVKITPPC = "${devkitPPC}/devkitPPC";
183 buildPhase = ''
184 mkdir -p $out
185 ${hgecko} -q . "$out/codes.gct"
186 '';
187 installPhase = "true";
188 };
189
190 allDat = pkgs.symlinkJoin {
191 name = "tmce-all-dat";
192 paths = pkgs.lib.attrValues events;
193 };
194
195 mkISOScript = pkgs.writeShellApplication {
196 name = "mkTM-ISO";
197 runtimeInputs = [tmce-tools pkgs.xdelta];
198 text = ''
199 set -e
200 ISO="''${1:-}"
201 OUT="''${2:-TM-CE.iso}"
202 if [ -z "$ISO" ] || [ ! -f "$ISO" ]; then
203 echo "Usage: nix run .#mkISO -- /path/to/vanilla/melee.iso [output.iso]"
204 exit 1
205 fi
206
207 HEADER=$(gc_fst get-header "$ISO")
208 case "$HEADER" in
209 GALE01) PATCH="${self}/Build TM Start.dol/patch.xdelta" ;;
210 GALJ01) PATCH="${self}/Build TM Start.dol/patch_jp.xdelta" ;;
211 *) echo "Error: Unknown game ID '$HEADER' (expected GALE01 or GALJ01)"; exit 1 ;;
212 esac
213
214 TMP=$(mktemp -d)
215 trap 'rm -rf "$TMP"' EXIT
216
217 gc_fst read "$ISO" Start.dol "$TMP/ISOStart.dol"
218 xdelta3 -dfs "$TMP/ISOStart.dol" "$PATCH" "$TMP/Start.dol"
219
220 cp "$ISO" "$OUT"
221
222 gc_fst fs "$OUT" \
223 delete MvHowto.mth \
224 delete MvOmake15.mth \
225 delete MvOpen.mth \
226 insert TM/eventMenu.dat "${allDat}/eventMenu.dat" \
227 insert TM/lab.dat "${allDat}/lab.dat" \
228 insert TM/labCSS.dat "${allDat}/labCSS.dat" \
229 insert TM/lcancel.dat "${allDat}/lcancel.dat" \
230 insert TM/ledgedash.dat "${allDat}/ledgedash.dat" \
231 insert TM/wavedash.dat "${allDat}/wavedash.dat" \
232 insert TM/powershield.dat "${allDat}/powershield.dat" \
233 insert TM/edgeguard.dat "${allDat}/edgeguard.dat" \
234 insert TM/fc.dat "${allDat}/fc.dat" \
235 insert TM/sweetspot.dat "${allDat}/sweetspot.dat" \
236 insert TM/eggs.dat "${allDat}/eggs.dat" \
237 insert TM/techchase.dat "${allDat}/techchase.dat" \
238 insert TM/slalom.dat "${allDat}/slalom.dat" \
239 insert codes.gct "${codesGct}/codes.gct" \
240 insert Start.dol "$TMP/Start.dol" \
241 insert opening.bnr "${self}/opening.bnr"
242
243 gc_fst set-header "$OUT" "GTME01" "Training Mode Community Edition"
244
245 echo "Built $OUT"
246 '';
247 };
248 in {
249 packages.${system} =
250 {
251 inherit tmce-tools devkitPPC;
252 codes = codesGct;
253 inherit allDat;
254 }
255 // events;
256
257 apps.${system} = {
258 mkISO = {
259 type = "app";
260 program = "${mkISOScript}/bin/mkTM-ISO";
261 };
262 };
263
264 checks.${system} = let
265 eventsCheck =
266 pkgs.linkFarm "check-events"
267 (pkgs.lib.mapAttrsToList (name: pkg: {
268 inherit name;
269 path = pkg;
270 })
271 events);
272 in
273 events
274 // {
275 all-events = eventsCheck;
276 codes = codesGct;
277 };
278
279 formatter.${system} = pkgs.alejandra;
280
281 devShells.${system}.default = pkgs.mkShell {
282 buildInputs = with pkgs; [
283 xdelta
284 tmce-tools
285 devkitPPC
286 ];
287 DEVKITPPC = "${devkitPPC}/devkitPPC";
288 };
289 };
290}