From db2b97b479b5051e353de3c2a8827ac01f00800e Mon Sep 17 00:00:00 2001 From: jamie Date: Sat, 29 Aug 2026 01:11:29 +0100 Subject: [PATCH 1/3] feat(nix): add flake for pure native Linux build --- .gitignore | 4 + flake.lock | 27 +++++ flake.nix | 165 ++++++++++++++++++++++++++ nix/deps.nix | 98 ++++++++++++++++ nix/extract.nix | 33 ++++++ nix/game.nix | 211 ++++++++++++++++++++++++++++++++++ nix/rr-hash.nix | 9 ++ nix/rr-pack.nix | 68 +++++++++++ nix/translate.nix | 108 +++++++++++++++++ nix/translator.nix | 24 ++++ nix/translator/nuget-deps.nix | 7 ++ nix/wrapper.nix | 55 +++++++++ 12 files changed, 809 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 nix/deps.nix create mode 100644 nix/extract.nix create mode 100644 nix/game.nix create mode 100644 nix/rr-hash.nix create mode 100644 nix/rr-pack.nix create mode 100644 nix/translate.nix create mode 100644 nix/translator.nix create mode 100644 nix/translator/nuget-deps.nix create mode 100644 nix/wrapper.nix diff --git a/.gitignore b/.gitignore index 1c69c0a9a..649eedef7 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,10 @@ Code.pul /build-*/ /dist/ /out/ + +# Nix build results +/result +/result-* [Bb]in/ [Oo]bj/ *.o diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..c50829616 --- /dev/null +++ b/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1787736819, + "narHash": "sha256-cV5xEJJK3BvhU8rEd4mC9UsmDi5qscv/kzGPhBRC5WA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "9fbb54b33e91ee4ca368e35a78e0613c720600b3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..b0be787f3 --- /dev/null +++ b/flake.nix @@ -0,0 +1,165 @@ +{ + description = "WiiCompiled: native Mario Kart Wii port (Linux, Nix-first)"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; + + outputs = { + self, + nixpkgs, + }: let + systems = [ + "x86_64-linux" + ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); + lib' = nixpkgs.lib; + + # Retro Rewind is opt-in: pin the pack's recursive sha256 in nix/rr-hash.nix + # (see the instructions in that file) to enable the Retro Rewind product. + rrHash = import ./nix/rr-hash.nix; + + mkOutputs = pkgs: let + lib = pkgs.lib; + repoSrc = lib.cleanSource self; + + # The disc image and the extracted tree derive from the user's own + # game dump, which the project may not redistribute, so they eval in + # an allow-unfree scope. Everything else in the flake stays free. + unfreePkgs = import pkgs.path { + inherit (pkgs) system; + config.allowUnfree = true; + }; + discImage = unfreePkgs.requireFile { + name = "RMCP01.rvz"; + hashMode = "recursive"; + hash = "sha256-cP/ZT1wpezNbHYZFgtvCAFnWQwKkupe7/46uFPlFbJU="; + message = '' + WiiCompiled needs your legally dumped clean PAL RMCP01 image + (RVZ/ISO/GCM/GCZ/CISO/WBFS/WIA). Nothing is downloaded; add your + own dump to the Nix store with: + + nix-store --add-fixed sha256 --recursive /path/to/RMCP01.rvz + ''; + }; + dataTree = (unfreePkgs.callPackage ./nix/extract.nix { + nodtool = pkgs.nodtool; + }) {inherit discImage;}; + + retroRewindPack = + if rrHash.hash == "" + then null + else + (pkgs.callPackage ./nix/rr-pack.nix {}) { + retroRewindPack = pkgs.requireFile { + name = "RetroRewind6.zip"; + hashMode = "recursive"; + inherit (rrHash) hash; + message = '' + The Retro Rewind product needs the Retro Rewind distribution + zip. Add it to the Nix store with: + + nix-store --add-fixed sha256 --recursive /path/to/RetroRewind6.zip + + then pin the printed hash in nix/rr-hash.nix. + ''; + }; + }; + + # The Retro-WFC shared payload the mod translation bakes in; fetched + # from the same endpoint the Windows build uses. + retroWfcPayload = pkgs.fetchurl { + url = "http://nas.play.rwfc.net/payload?g=RMCPD00"; + hash = "sha256-/Y8m1q8m8aDPrs0eRy/nRKJddfUTaRBTO3x14+yi8dI="; + }; + + translator = pkgs.callPackage ./nix/translator.nix {}; + + translation = + (pkgs.callPackage ./nix/translate.nix { + inherit repoSrc translator; + }) { + inherit dataTree retroRewindPack retroWfcPayload; + }; + + deps = pkgs.callPackage ./nix/deps.nix {}; + game = (pkgs.callPackage ./nix/game.nix { + inherit repoSrc deps; + llvmPackages = pkgs.llvmPackages; + }) {inherit translation;}; + + wrapper = pkgs.callPackage ./nix/wrapper.nix {}; + in rec { + nodtool = pkgs.nodtool; + datatree = dataTree; + inherit translation game; + + wiicompiled = wrapper { + inherit game dataTree; + }; + + retro-rewind = lib.mapNullable (pack: + wrapper { + inherit game dataTree; + retroRewindRoot = "${pack}/RetroRewind6"; + }) + retroRewindPack; + + default = wiicompiled; + }; + in { + devShells = forAllSystems (pkgs: { + default = pkgs.mkShell { + packages = with pkgs; [ + dotnet-sdk_8 + llvmPackages.clang + cmake + ninja + pkg-config + sdl3 + vulkan-headers + vulkan-loader + libx11 + libxcursor + libxi + libxrandr + wayland + wayland-protocols + libxkbcommon + ]; + }; + }); + + packages = forAllSystems ( + pkgs: + lib'.filterAttrs (_: value: value != null) (mkOutputs pkgs) + ); + + # The pinned FetchContent sources are a nested set, so they live outside + # the packages output (whose values must be derivations). Handy for + # inspecting a single dep: nix build .#legacyPackages.x86_64-linux.deps.fmt + legacyPackages = forAllSystems (pkgs: { + inherit (mkOutputs pkgs) deps; + }); + + apps = forAllSystems ( + pkgs: let + outputs = mkOutputs pkgs; + in + { + wiicompiled = { + type = "app"; + program = "${outputs.wiicompiled}/bin/wiicompiled"; + meta.description = "Mario Kart Wii (WiiCompiled base product)"; + }; + } + // (pkgs.lib.optionalAttrs (outputs.retro-rewind != null) { + retro-rewind = { + type = "app"; + program = "${outputs.retro-rewind}/bin/retro-rewind"; + meta.description = "Mario Kart Wii Retro Rewind (WiiCompiled)"; + }; + }) + ); + }; +} diff --git a/nix/deps.nix b/nix/deps.nix new file mode 100644 index 000000000..7bfe27cf0 --- /dev/null +++ b/nix/deps.nix @@ -0,0 +1,98 @@ +# Pinned sources for every FetchContent dependency aurora-main declares. These +# are injected into the configure line as -DFETCHCONTENT_SOURCE_DIR_ under +# FETCHCONTENT_FULLY_DISCONNECTED=ON, mirroring how the Windows toolkit feeds +# the same trees from Dependencies/ (see Launcher/NativeBuildFlags.ps1). The +# URLs, versions and source subdirectories match +# aurora-main/extern/CMakeLists.txt, cmake/AuroraSDL3Provider.cmake and +# cmake/AuroraDawnProvider.cmake exactly. +{ + fetchurl, + runCommand, + unzip, +}: let + # FetchContent finds the CMakeLists below the top directory of every + # tarball here; --strip-components=1 reproduces that layout. + unpackTar = name: src: + runCommand "aurora-fc-${name}" {} '' + mkdir -p $out + tar -xf ${src} -C $out --strip-components=1 + ''; + unpackZip = name: src: + runCommand "aurora-fc-${name}" + { + nativeBuildInputs = [unzip]; + } + '' + mkdir -p $out + unzip -q ${src} -d $out + # Some amalgamation zips nest everything one level down; FetchContent + # consumers expect sqlite3.c at the source root either way. + first=$(find $out -mindepth 1 -maxdepth 1 -type d | head -n 1) + if [ -n "$first" ] && [ ! -f $out/sqlite3.c ]; then + find "$first" -mindepth 1 -maxdepth 1 -exec mv -t $out {} + + rmdir "$first" 2>/dev/null || true + fi + ''; +in { + abseil-cpp = unpackTar "abseil-cpp" (fetchurl { + url = "https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz"; + hash = "sha256-9Q5awxGoE4Laf6dblzEOS5AGR0+VYKxG9UqZZ/B9SuM="; + }); + + xxhash = unpackTar "xxhash" (fetchurl { + url = "https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz"; + hash = "sha256-quYI3+ghPf0F2QmldxjvgvMHIsOSNEWD0/OQUMfymoA="; + }); + + fmt = unpackTar "fmt" (fetchurl { + url = "https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz"; + hash = "sha256-rDZre0wunw3eY6WbP+te5Ztnl0sU7l3J6orXiqLB7h4="; + }); + + zlib = unpackTar "zlib" (fetchurl { + url = "https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz"; + hash = "sha256-uzKaCizQJ00FUZ1hxmfAYuBpkNcuEl7i36jeZPARnRY="; + }); + + png = unpackTar "png" (fetchurl { + url = "https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.58.tar.gz"; + hash = "sha256-qdTfRj02puX5wpvW9JZzEtF+mWwYVPNRH4M5JOsZk88="; + }); + + freetype = unpackTar "freetype" (fetchurl { + url = "https://files.twilitrealm.dev/freetype-2.14.3.tar.gz"; + hash = "sha256-5hsxqyY1i5Rudn7X639LsuUH2hz+/reohhrOf9XImaE="; + }); + + imgui = unpackTar "imgui" (fetchurl { + url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz"; + hash = "sha256-Rm/e+bGN4V8LtuKI49AP+j2CIA7EWM5eT3JKFh2VKKU="; + }); + + sqlite3 = unpackZip "sqlite3" (fetchurl { + url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip"; + hash = "sha256-rLHm9dgySEv20ytoHoWMOK3Ysqzf1CrF3yS4r7RlUrQ="; + }); + + zstd = unpackTar "zstd" (fetchurl { + url = "https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz"; + hash = "sha256-6zPlH0mhXgI5UM14Jcp0pKK0Pbg1SCWsJPwbfuCeb6M="; + }); + + tracy = unpackTar "tracy" (fetchurl { + url = "https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz"; + hash = "sha256-JNNCtRJ9f2Wdw8+U8kNHs0jMc28lsXppH9f2lUGTdlg="; + }); + + sdl = unpackTar "sdl" (fetchurl { + url = "https://github.com/libsdl-org/SDL/releases/download/release-3.4.4/SDL3-3.4.4.tar.gz"; + hash = "sha256-7nEtvmqJuxQLv8LOcjWPte5cwiQKvqvVSFUBLbMLOGQ="; + }); + + # Prebuilt Dawn install tree (DawnConfig.cmake, DAWN_ENABLE_INSTALL=ON), + # consumed via aurora's AURORA_DAWN_PROVIDER=package. + dawn-prebuilt = unpackTar "dawn-prebuilt" (fetchurl { + url = "https://github.com/encounter/dawn-build/releases/download/v20260603.191052/dawn-linux-x86_64.tar.gz"; + hash = "sha256-+eEdQYVI4pFP1bL280VipZIKa52dVS6/K/zHR6XfmuE="; + }); +} diff --git a/nix/extract.nix b/nix/extract.nix new file mode 100644 index 000000000..db7ad53b6 --- /dev/null +++ b/nix/extract.nix @@ -0,0 +1,33 @@ +# Disc extraction derivation: consumes the user-supplied RMCP01 image (via +# pkgs.requireFile or an override) and produces the extracted filesystem tree +# the runtime serves as dvd_root and the translator feeds on. Validation mirrors +# the Windows installer (Launcher/WiiCompiled.Setup/InstallerEngine.cs): the +# required files must exist and main.dol / StaticR.rel must hash to the pins +# recorded in projects/mkwii/recomp.yml. +{ + lib, + stdenvNoCC, + nodtool, + dolSha256 ? "80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05", + relSha256 ? "16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d", +}: {discImage}: +stdenvNoCC.mkDerivation { + name = "wiicompiled-datatree"; + nativeBuildInputs = [nodtool]; + + buildCommand = '' + mkdir -p $out + nodtool extract ${discImage} $out + test -d $out/files + test -f $out/sys/fst.bin + test -f $out/sys/main.dol + test -f $out/files/rel/StaticR.rel + test "$(sha256sum $out/sys/main.dol | cut -d' ' -f1)" = "${dolSha256}" + test "$(sha256sum $out/files/rel/StaticR.rel | cut -d' ' -f1)" = "${relSha256}" + ''; + + meta = { + description = "Extracted RMCP01 disc filesystem for WiiCompiled"; + license = lib.licenses.unfree; + }; +} diff --git a/nix/game.nix b/nix/game.nix new file mode 100644 index 000000000..90d90110c --- /dev/null +++ b/nix/game.nix @@ -0,0 +1,211 @@ +# Native build derivation: configures runtime/ CMake against the translated +# shard graph and builds both products (WiiCompiled, RetroRewind) in one build +# so every profile-neutral shard object is shared between them (the same +# decision LocalBuild.ps1 makes with -Profile both). +# +# shards.cmake embeds absolute paths pointing at the translation workspace, so +# this derivation recreates that exact layout at /build/workspace: the repo +# sources plus the translation outputs in their original positions. Configure +# flags mirror Launcher/NativeBuildFlags.ps1 (Get-MkwNativeFixedConfigureFlags) +# with the FetchContent trees supplied from nix/deps.nix. +{ + lib, + stdenv, + cmake, + ninja, + pkg-config, + patchelf, + wayland-scanner, + llvmPackages, + vulkan-loader, + wayland, + wayland-protocols, + libxkbcommon, + alsa-lib, + libpulseaudio, + pipewire, + libffi, + libGL, + mesa, + libx11, + libxcb, + libxext, + libxrender, + libxcursor, + libxi, + libxrandr, + libxscrnsaver, + libxfixes, + libxtst, + libxv, + deps, + repoSrc, +}: {translation}: +# Clang is the tested toolchain for this project (GCC untested upstream); +# the clang stdenv is what makes CMake pick clang over the default gcc. +llvmPackages.stdenv.mkDerivation { + name = "wiicompiled-native"; + pname = "wiicompiled"; + version = "0-unstable"; + + src = repoSrc; + + nativeBuildInputs = [ + cmake + ninja + pkg-config + patchelf + # SDL's CheckWayland runs find_program(WAYLAND_SCANNER) and silently + # drops the Wayland video driver when it is missing; nixpkgs ships the + # scanner as a standalone package, not in wayland's outputs. + wayland-scanner + ]; + buildInputs = [ + vulkan-loader + wayland + wayland-protocols + libxkbcommon + libGL + mesa + alsa-lib + libpulseaudio + pipewire + # wayland-client.pc Requires: libffi; without it SDL's pkg-config check + # fails and the Wayland video driver silently drops out of the build. + libffi + libx11 + libxcb + libxext + libxrender + libxcursor + libxi + libxrandr + libxscrnsaver + libxfixes + libxtst + libxv + ]; + + # The clang wrapper's default hardening (pie, fortify, relro) is harmless + # here, but the fixed guest address space needs no extra hints from us. + hardeningDisable = ["format"]; + dontFixCmake = true; + + unpackPhase = '' + runHook preUnpack + cp -r $src workspace + chmod -R u+w workspace + mkdir -p workspace/generated workspace/build + cp -r ${translation}/generated/. workspace/generated/ + cp -r ${translation}/build/. workspace/build/ + chmod -R u+w workspace/generated workspace/build + cd workspace + runHook postUnpack + ''; + + configurePhase = '' + runHook preConfigure + # SDL builds backends in dlopen mode by default (SDL_*_SHARED=ON), and + # this stdenv's fixupPhase shrinks every RPATH entry not needed for + # DT_NEEDED resolution - which deletes exactly the dlopen'd libraries. + # Force direct linking so the normal buildInputs rpath survives. + cmake -S runtime -B native-build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SYSTEM_PROCESSOR=x86_64 \ + -DAURORA_DAWN_PROVIDER=package \ + -DAURORA_SDL3_PROVIDER=vendor \ + -DSDL_X11_SHARED=OFF \ + -DSDL_WAYLAND_SHARED=OFF \ + -DSDL_WAYLAND_LIBDECOR_SHARED=OFF \ + -DSDL_ALSA_SHARED=OFF \ + -DSDL_PIPEWIRE_SHARED=OFF \ + -DSDL_PULSEAUDIO_SHARED=OFF \ + -DCMAKE_POLICY_DEFAULT_CMP0168=NEW \ + -DFETCHCONTENT_FULLY_DISCONNECTED=ON \ + -DAWK:FILEPATH= \ + -DFETCHCONTENT_SOURCE_DIR_ABSEIL-CPP=${deps.abseil-cpp} \ + -DFETCHCONTENT_SOURCE_DIR_XXHASH=${deps.xxhash} \ + -DFETCHCONTENT_SOURCE_DIR_FMT=${deps.fmt} \ + -DFETCHCONTENT_SOURCE_DIR_ZLIB=${deps.zlib} \ + -DFETCHCONTENT_SOURCE_DIR_PNG=${deps.png} \ + -DFETCHCONTENT_SOURCE_DIR_FREETYPE=${deps.freetype} \ + -DFETCHCONTENT_SOURCE_DIR_IMGUI=${deps.imgui} \ + -DFETCHCONTENT_SOURCE_DIR_SQLITE3=${deps.sqlite3} \ + -DFETCHCONTENT_SOURCE_DIR_ZSTD=${deps.zstd} \ + -DFETCHCONTENT_SOURCE_DIR_TRACY=${deps.tracy} \ + -DFETCHCONTENT_SOURCE_DIR_SDL=${deps.sdl} \ + -DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${deps.dawn-prebuilt} \ + -DMKW_TRANSLATED_COMPILE_JOBS=$((NIX_BUILD_CORES / 2)) + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + # mkw_release covers both products when the shard graph includes Retro + # Rewind, and just WiiCompiled for a base-only translation. + cmake --build native-build --target mkw_release + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin $out/lib + install -m755 native-build/WiiCompiled $out/bin/ + if [ -f native-build/RetroRewind ]; then + install -m755 native-build/RetroRewind $out/bin/ + fi + # The vendored libpng/zlib are built shared and the CMake build-tree + # RPATH points back into /build, which Nix refuses. Install those two + # libraries beside the product and rewrite the RPATH to the remaining + # (store-path) entries plus $out/lib. Everything else — SDL3, Dawn, + # Crypto++ — is statically linked, and the backends SDL dlopens upstream + # (X11, Wayland, audio) are DT_NEEDED here, so the normal buildInputs + # rpath covers them; Vulkan is supplied by the launcher wrapper. + foundAny=0 + for lib in native-build/_deps/*/libpng16.so* native-build/_deps/*/libz.so*; do + if [ -e "$lib" ]; then + cp -L "$lib" $out/lib/ + foundAny=1 + fi + done + # The vendored libs' own RPATHs also point into /build (their _deps + # build directory); everything they need lives in $out/lib now. + for so in $out/lib/*.so*; do + patchelf --set-rpath "$out/lib" "$so" + done + for exe in $out/bin/*; do + [ -f "$exe" ] || continue + oldRpath="$(patchelf --print-rpath "$exe")" + kept="$(echo "$oldRpath" | tr ':' '\n' | grep -v '^/build/' | paste -sd:)" + if [ -n "$kept" ]; then + patchelf --set-rpath "$out/lib:$kept" "$exe" + else + patchelf --set-rpath "$out/lib" "$exe" + fi + done + # First-run assets the CMake build stages beside each product + # (runtime/cmake/PublicProducts.cmake); the DSP ROM is a hard startup + # requirement (runtime/src/hle/audio/ax_mix.cpp). Installed after the + # RPATH pass above, which must only touch ELF executables. + for asset in dsp_coef.bin initial_pipeline_cache.db; do + if [ -f "native-build/$asset" ]; then + install -m644 "native-build/$asset" "$out/bin/" + fi + done + for d in wii_bootstrap config; do + if [ -d native-build/$d ]; then + cp -r native-build/$d $out/bin/ + fi + done + # Static Dawn and SDL3 link directly into the products; nothing else to + # carry beside the per-product assets staged above. + runHook postInstall + ''; + + meta = { + description = "WiiCompiled native runtime (Mario Kart Wii static recompilation), base and Retro Rewind products"; + license = lib.licenses.gpl3Only; + platforms = ["x86_64-linux"]; + mainProgram = "WiiCompiled"; + }; +} diff --git a/nix/rr-hash.nix b/nix/rr-hash.nix new file mode 100644 index 000000000..a330bf3c3 --- /dev/null +++ b/nix/rr-hash.nix @@ -0,0 +1,9 @@ +# Retro Rewind pack hash. Add the distribution zip to the store with: +# +# nix-store --add-fixed sha256 --recursive RetroRewind6.zip +# +# then paste the printed hash below to enable the Retro Rewind product. An +# empty string keeps the flake building the base product only. +{ + hash = ""; +} diff --git a/nix/rr-pack.nix b/nix/rr-pack.nix new file mode 100644 index 000000000..587f00968 --- /dev/null +++ b/nix/rr-pack.nix @@ -0,0 +1,68 @@ +# Normalizes the Retro Rewind distribution into the layout the translator and +# the runtime expect: +# +# RetroRewind6/ +# Binaries/Code.pul (translator: recomp.yml profile code_pul) +# xml/RetroRewind6.xml (translator: profile riivolution pin) +# riivolution/RetroRewind6.xml (runtime: overlay discovery) +# files/... (runtime: overlay game files, when shipped) +# +# The upstream zip spells these differently (RetroRewind6/ + Riivolution/*.xml), +# so this derivation locates the pieces and re-lays them out. If a future pack +# moves things around, this is the one place to adjust. +{ + lib, + stdenvNoCC, + unzip, +}: {retroRewindPack}: +stdenvNoCC.mkDerivation { + name = "retro-rewind-pack"; + nativeBuildInputs = [unzip]; + + buildCommand = '' + mkdir src + cd src + case "${retroRewindPack}" in + *.zip|*.7z) + unzip -q ${retroRewindPack} -d . + ;; + *) + # A store path already laid out by a custom override. + cp -r ${retroRewindPack}/. . + ;; + esac + + findDir() { + find . -type d -name "$1" | head -n 1 + } + + rrDir=$(findDir 'RetroRewind6' || true) + if [ -z "$rrDir" ]; then + # Some distributions nest one level; take the only top-level directory. + rrDir=$(find . -mindepth 2 -maxdepth 2 -type d -name 'RetroRewind6' | head -n 1) + fi + pul=$(find . -iname 'Code.pul' -type f | head -n 1) + xml=$(find . -iname 'RetroRewind6.xml' -type f | head -n 1) + + test -n "$pul" || { echo "Retro Rewind pack contains no Code.pul" >&2; exit 1; } + test -n "$xml" || { echo "Retro Rewind pack contains no Riivolution XML" >&2; exit 1; } + + mkdir -p $out/RetroRewind6/Binaries $out/RetroRewind6/xml $out/RetroRewind6/riivolution + cp "$pul" $out/RetroRewind6/Binaries/Code.pul + cp "$xml" $out/RetroRewind6/xml/RetroRewind6.xml + cp "$xml" $out/RetroRewind6/riivolution/RetroRewind6.xml + + # Carry the mod's game-file overlay (whatever the pack shipped under a + # files/ directory) so the runtime can serve it as the Riivolution root. + if [ -d "$rrDir/files" ]; then + cp -r "$rrDir/files" $out/RetroRewind6/files + elif [ -d ./files ]; then + cp -r ./files $out/RetroRewind6/files + fi + ''; + + meta = { + description = "Normalized Retro Rewind pack for WiiCompiled"; + license = lib.licenses.unfree; + }; +} diff --git a/nix/translate.nix b/nix/translate.nix new file mode 100644 index 000000000..64aa50fa2 --- /dev/null +++ b/nix/translate.nix @@ -0,0 +1,108 @@ +# Translation derivation: runs the Translator.Cli over the extracted main.dol / +# StaticR.rel (base) and the Retro Rewind Code.pul (retro-rewind profile), then +# emits the data initializer and the shard manifest the native build consumes. +# +# The command sequence mirrors Launcher/LocalBuild.ps1 exactly: +# 1. stage the RR Code.pul at the profile's mod_root (workspace-relative) +# 2. translate-recursive -> generated/functions + base metadata +# 3. emit-base-manifest -> build/base/mkwii_base_manifest.json +# 4. translate-mod -> build/mods/retro_rewind_full_cpp +# 5. generate-data-init -> generated/{RuntimeConfig.h,data_sections_init*} +# 6. emit-build-shards -> generated/build_shards/shards.cmake +# +# shards.cmake embeds absolute paths (Translator.Cli GetFullPath's its inputs), +# so the native build (nix/game.nix) must place the repo and these outputs at +# the same /build/workspace paths this derivation used. +{ + lib, + stdenvNoCC, + translator, + repoSrc, +}: { + dataTree, + retroRewindPack, + retroWfcPayload ? null, +}: +stdenvNoCC.mkDerivation { + name = "wiicompiled-translation"; + + nativeBuildInputs = [translator]; + + unpackPhase = '' + runHook preUnpack + cp -r ${repoSrc} workspace + chmod -R u+w workspace + cd workspace + runHook postUnpack + ''; + + buildPhase = '' + runHook preBuild + mkdir -p Assets generated build/base + cp ${dataTree}/sys/main.dol Assets/main.dol + cp ${dataTree}/files/rel/StaticR.rel Assets/StaticR.rel + + THREADS=$([ "$NIX_BUILD_CORES" -gt 16 ] && echo 16 || echo "$NIX_BUILD_CORES") + + Translator.Cli translate-recursive 0x800060A4 --project projects/mkwii/recomp.yml \ + --outdir generated/functions --output-metadata generated/base_translation_output.json \ + --production-source-bundle generated/base_translation_sources.bin \ + --no-function-files --prune-stale --threads "$THREADS" + + Translator.Cli emit-base-manifest --project projects/mkwii/recomp.yml \ + --out build/base --functions-dir generated/functions \ + --translation-output-metadata generated/base_translation_output.json --region P + + ${lib.optionalString (retroRewindPack != null) '' + # Stage the profile's Code.pul at the mod_root recomp.yml pins so the + # base translation becomes retro-aware (leaf inlining blocks at patch + # addresses) before the mod leg runs. + mkdir -p PulsarPacks/completed/RetroRewind + cp -r ${retroRewindPack}/RetroRewind6 PulsarPacks/completed/RetroRewind/RetroRewind6 + chmod -R u+w PulsarPacks + + ${lib.optionalString (retroWfcPayload != null) '' + WFC_ARGS="--retro-wfc-payload ${retroWfcPayload}" + ''} + ${lib.optionalString (retroWfcPayload == null) '' + WFC_ARGS="--skip-retro-wfc" + ''} + + Translator.Cli translate-mod --project projects/mkwii/recomp.yml --profile retro-rewind \ + --base-manifest build/base/mkwii_base_manifest.json \ + --base-translation-output-metadata generated/base_translation_output.json \ + --code-pul PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul \ + --mod-root PulsarPacks/completed/RetroRewind/RetroRewind6 \ + --mod-name "Retro Rewind" --region P --out build/mods/retro_rewind_full_cpp \ + --prefer-cached-inputs --emit-cpp --threads "$THREADS" $WFC_ARGS + ''} + + Translator.Cli generate-data-init --project projects/mkwii/recomp.yml + + SHARD_ARGS="" + ${lib.optionalString (retroRewindPack != null) '' + SHARD_ARGS="--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json --retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp" + ''} + + Translator.Cli emit-build-shards --project projects/mkwii/recomp.yml \ + --base-metadata generated/base_translation_output.json \ + --base-functions-dir generated/functions --native-source-dir runtime/src \ + --out generated/build_shards $SHARD_ARGS + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir $out + cp -r generated build $out/ + # shards.cmake embeds absolute paths pointing at /build/workspace; the + # native build (nix/game.nix) recreates that exact layout from repoSrc + # plus these outputs, so no workspace copy is shipped here. + runHook postInstall + ''; + + meta = { + description = "Translated WiiCompiled build graph (base + Retro Rewind)"; + license = lib.licenses.gpl3Only; + }; +} diff --git a/nix/translator.nix b/nix/translator.nix new file mode 100644 index 000000000..e9936fb8c --- /dev/null +++ b/nix/translator.nix @@ -0,0 +1,24 @@ +{ + lib, + buildDotnetModule, + dotnet-sdk_8, +}: +buildDotnetModule rec { + pname = "wiicompiled-translator"; + version = "0-unstable"; + src = ../translator; + projectFile = "src/Translator.Cli/Translator.Cli.csproj"; + nugetDeps = ./translator/nuget-deps.nix; + dotnet-sdk = dotnet-sdk_8; + executables = ["Translator.Cli"]; + + passthru.fetch-deps = buildDotnetModule.fetchDeps { + inherit pname version src projectFile; + }; + + meta = { + description = "WiiCompiled static recompiler: DOL/REL to C++ translator"; + license = lib.licenses.gpl3Only; + mainProgram = "Translator.Cli"; + }; +} diff --git a/nix/translator/nuget-deps.nix b/nix/translator/nuget-deps.nix new file mode 100644 index 000000000..6989e8a68 --- /dev/null +++ b/nix/translator/nuget-deps.nix @@ -0,0 +1,7 @@ +{fetchNuGet}: [ + (fetchNuGet { + pname = "YamlDotNet"; + version = "15.1.2"; + hash = "sha256-yrvQG/QMIEMOMCv4mtrzWJ5zdFSYEsSRHlkqzSShzts="; + }) +] diff --git a/nix/wrapper.nix b/nix/wrapper.nix new file mode 100644 index 000000000..c27904b41 --- /dev/null +++ b/nix/wrapper.nix @@ -0,0 +1,55 @@ +# Launcher wrappers: seed the user's Config.toml on first run (the game owns it +# afterwards, exactly like the Windows installer's install-time write) and exec +# the product binary. dvd_root points at the store's extracted disc tree; the +# Retro Rewind wrapper additionally pins retro_rewind_root at the normalized +# pack. User state (Config.toml, NAND, Cache, Logs) lives in +# $XDG_DATA_HOME/WiiCompiled, mirroring runtime_config.h's +# ApplicationDataDirectory(). +# +# LD_LIBRARY_PATH carries vulkan-loader because Dawn loads libvulkan.so.1 via +# dlopen, and RPATH entries for dlopen'd libraries do not survive this +# stdenv's fixupPhase RPATH shrink. +{ + lib, + makeWrapper, + runCommand, + vulkan-loader, +}: { + game, + dataTree, + retroRewindRoot ? null, +}: let + product = + if retroRewindRoot == null + then { + binary = "WiiCompiled"; + command = "wiicompiled"; + extraPaths = ""; + } + else { + binary = "RetroRewind"; + command = "retro-rewind"; + extraPaths = "retro_rewind_root = \"${retroRewindRoot}\""; + }; +in + runCommand "wiicompiled-launcher-${product.command}" + { + nativeBuildInputs = [makeWrapper]; + passthru = {inherit game dataTree;}; + } + '' + mkdir -p $out/bin + makeWrapper ${game}/bin/${product.binary} $out/bin/${product.command} \ + --prefix LD_LIBRARY_PATH : ${vulkan-loader}/lib \ + --run ' + data_dir="''${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled" + mkdir -p "$data_dir" + if [ ! -f "$data_dir/Config.toml" ]; then + { + echo "[paths]" + echo "dvd_root = \"${dataTree}\"" + echo "${product.extraPaths}" + } > "$data_dir/Config.toml" + fi + ' + '' From 3605ecccf4337f4172722b348a87aeaa192b843f Mon Sep 17 00:00:00 2001 From: jamie Date: Sat, 29 Aug 2026 12:13:39 +0100 Subject: [PATCH 2/3] refactor(nix): consolidate pipeline into build.nix, harden launcher --- flake.nix | 66 +-- nix/build.nix | 446 ++++++++++++++++++ nix/extract.nix | 33 -- nix/game.nix | 211 --------- nix/retro-rewind.nix | 79 ++++ nix/rr-hash.nix | 9 - nix/rr-pack.nix | 68 --- nix/translate.nix | 108 ----- .../default.nix} | 4 +- nix/wrapper.nix | 55 --- 10 files changed, 564 insertions(+), 515 deletions(-) create mode 100644 nix/build.nix delete mode 100644 nix/extract.nix delete mode 100644 nix/game.nix create mode 100644 nix/retro-rewind.nix delete mode 100644 nix/rr-hash.nix delete mode 100644 nix/rr-pack.nix delete mode 100644 nix/translate.nix rename nix/{translator.nix => translator/default.nix} (88%) delete mode 100644 nix/wrapper.nix diff --git a/flake.nix b/flake.nix index b0be787f3..ce735c965 100644 --- a/flake.nix +++ b/flake.nix @@ -15,13 +15,23 @@ forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); lib' = nixpkgs.lib; - # Retro Rewind is opt-in: pin the pack's recursive sha256 in nix/rr-hash.nix - # (see the instructions in that file) to enable the Retro Rewind product. - rrHash = import ./nix/rr-hash.nix; + # Retro Rewind is opt-in: pin the pack's recursive sha256 in + # nix/retro-rewind.nix (see the instructions in that file) to enable the + # Retro Rewind product. + rr = import ./nix/retro-rewind.nix; mkOutputs = pkgs: let lib = pkgs.lib; - repoSrc = lib.cleanSource self; + # The build consumes projects/, runtime/ and aurora-main/ only; keeping + # the flake's own nix/ files out means flake-only edits do not + # re-trigger the translation and native build derivations. + repoSrc = lib.cleanSourceWith { + src = lib.cleanSource self; + filter = path: type: + !(type == "directory" && baseNameOf path == "nix") + && baseNameOf path != "flake.nix" + && baseNameOf path != "flake.lock"; + }; # The disc image and the extracted tree derive from the user's own # game dump, which the project may not redistribute, so they eval in @@ -42,26 +52,35 @@ nix-store --add-fixed sha256 --recursive /path/to/RMCP01.rvz ''; }; - dataTree = (unfreePkgs.callPackage ./nix/extract.nix { - nodtool = pkgs.nodtool; - }) {inherit discImage;}; + + # The datatree carries the extracted game data (unfree), so the whole + # pipeline is built in an allow-unfree scope; the tools it consumes are + # the same nixpkgs ones either way. + build = unfreePkgs.callPackage ./nix/build.nix { + inherit repoSrc; + translator = pkgs.callPackage ./nix/translator {}; + deps = pkgs.callPackage ./nix/deps.nix {}; + llvmPackages = pkgs.llvmPackages; + }; + + dataTree = build.extractDisc {} {inherit discImage;}; retroRewindPack = - if rrHash.hash == "" + if rr.hash == "" then null else - (pkgs.callPackage ./nix/rr-pack.nix {}) { - retroRewindPack = pkgs.requireFile { + (unfreePkgs.callPackage rr.normalize {}) { + retroRewindPack = unfreePkgs.requireFile { name = "RetroRewind6.zip"; hashMode = "recursive"; - inherit (rrHash) hash; + inherit (rr) hash; message = '' The Retro Rewind product needs the Retro Rewind distribution zip. Add it to the Nix store with: nix-store --add-fixed sha256 --recursive /path/to/RetroRewind6.zip - then pin the printed hash in nix/rr-hash.nix. + then pin the printed hash in nix/retro-rewind.nix. ''; }; }; @@ -73,33 +92,22 @@ hash = "sha256-/Y8m1q8m8aDPrs0eRy/nRKJddfUTaRBTO3x14+yi8dI="; }; - translator = pkgs.callPackage ./nix/translator.nix {}; - - translation = - (pkgs.callPackage ./nix/translate.nix { - inherit repoSrc translator; - }) { - inherit dataTree retroRewindPack retroWfcPayload; - }; - - deps = pkgs.callPackage ./nix/deps.nix {}; - game = (pkgs.callPackage ./nix/game.nix { - inherit repoSrc deps; - llvmPackages = pkgs.llvmPackages; - }) {inherit translation;}; + translation = build.translate { + inherit dataTree retroRewindPack retroWfcPayload; + }; - wrapper = pkgs.callPackage ./nix/wrapper.nix {}; + game = build.buildNative {inherit translation;}; in rec { nodtool = pkgs.nodtool; datatree = dataTree; inherit translation game; - wiicompiled = wrapper { + wiicompiled = build.launcher { inherit game dataTree; }; retro-rewind = lib.mapNullable (pack: - wrapper { + build.launcher { inherit game dataTree; retroRewindRoot = "${pack}/RetroRewind6"; }) diff --git a/nix/build.nix b/nix/build.nix new file mode 100644 index 000000000..cfe5e9298 --- /dev/null +++ b/nix/build.nix @@ -0,0 +1,446 @@ +# WiiCompiled build pipeline, top to bottom. flake.nix supplies the +# user-owned inputs (disc dump, optional Retro Rewind pack) and wires the +# stages together: +# +# extractDisc user-supplied RMCP01 image -> validated nodtool extraction +# (the extracted tree is both the translator's input and the +# runtime's dvd_root) +# translate Translator.Cli over main.dol/StaticR.rel (and the opt-in +# Retro Rewind Code.pul) -> emitted C++ shard build graph +# buildNative cmake/ninja/clang over runtime/ + the shard graph -> the +# WiiCompiled and RetroRewind executables +# launcher wrapper binaries: user state seeding, RADV workaround +# +# Supporting files: nix/deps.nix (pinned FetchContent tarballs feeding +# buildNative) and nix/translator (dotnet build of Translator.Cli feeding +# translate). +{ + lib, + stdenvNoCC, + nodtool, + translator, + repoSrc, + deps, + cmake, + ninja, + pkg-config, + patchelf, + wayland-scanner, + llvmPackages, + vulkan-loader, + wayland, + wayland-protocols, + libxkbcommon, + alsa-lib, + libpulseaudio, + pipewire, + libffi, + libGL, + mesa, + libx11, + libxcb, + libxext, + libxrender, + libxcursor, + libxi, + libxrandr, + libxscrnsaver, + libxfixes, + libxtst, + libxv, + makeWrapper, + runCommand, +}: { + # Disc extraction: consumes the user-supplied RMCP01 image (via + # pkgs.requireFile or an override) and produces the extracted filesystem + # tree. Validation mirrors the Windows installer: the required files must + # exist and main.dol / StaticR.rel must hash to the pins recorded in + # projects/mkwii/recomp.yml. + extractDisc = { + dolSha256 ? "80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05", + relSha256 ? "16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d", + }: {discImage}: + stdenvNoCC.mkDerivation { + name = "wiicompiled-datatree"; + nativeBuildInputs = [nodtool]; + + buildCommand = '' + mkdir -p $out + nodtool extract ${discImage} $out + test -d $out/files + test -f $out/sys/fst.bin + test -f $out/sys/main.dol + test -f $out/files/rel/StaticR.rel + test "$(sha256sum $out/sys/main.dol | cut -d' ' -f1)" = "${dolSha256}" + test "$(sha256sum $out/files/rel/StaticR.rel | cut -d' ' -f1)" = "${relSha256}" + ''; + + meta = { + description = "Extracted RMCP01 disc filesystem for WiiCompiled"; + license = lib.licenses.unfree; + }; + }; + + # Translation: runs the Translator.Cli over the extracted main.dol / + # StaticR.rel (base) and the Retro Rewind Code.pul (retro-rewind profile), + # then emits the data initializer and the shard manifest the native build + # consumes. + # + # The command sequence mirrors Launcher/local-build.sh exactly: + # 1. stage the RR Code.pul at the profile's mod_root (workspace-relative) + # 2. translate-recursive -> generated/functions + base metadata + # 3. emit-base-manifest -> build/base/mkwii_base_manifest.json + # 4. translate-mod -> build/mods/retro_rewind_full_cpp + # 5. generate-data-init -> generated/{RuntimeConfig.h,data_sections_init*} + # 6. emit-build-shards -> generated/build_shards/shards.cmake + # + # shards.cmake embeds absolute paths (Translator.Cli GetFullPath's its + # inputs), so the native build (buildNative) must place the repo and these + # outputs at the same /build/workspace paths this derivation used. + translate = { + dataTree, + retroRewindPack, + retroWfcPayload ? null, + }: + stdenvNoCC.mkDerivation { + name = "wiicompiled-translation"; + + nativeBuildInputs = [translator]; + + unpackPhase = '' + runHook preUnpack + cp -r ${repoSrc} workspace + chmod -R u+w workspace + cd workspace + runHook postUnpack + ''; + + buildPhase = '' + runHook preBuild + mkdir -p Assets generated build/base + cp ${dataTree}/sys/main.dol Assets/main.dol + cp ${dataTree}/files/rel/StaticR.rel Assets/StaticR.rel + + THREADS=$([ "$NIX_BUILD_CORES" -gt 16 ] && echo 16 || echo "$NIX_BUILD_CORES") + + Translator.Cli translate-recursive 0x800060A4 --project projects/mkwii/recomp.yml \ + --outdir generated/functions --output-metadata generated/base_translation_output.json \ + --production-source-bundle generated/base_translation_sources.bin \ + --no-function-files --prune-stale --threads "$THREADS" + + Translator.Cli emit-base-manifest --project projects/mkwii/recomp.yml \ + --out build/base --functions-dir generated/functions \ + --translation-output-metadata generated/base_translation_output.json --region P + + ${lib.optionalString (retroRewindPack != null) '' + # Stage the profile's Code.pul at the mod_root recomp.yml pins so the + # base translation becomes retro-aware (leaf inlining blocks at patch + # addresses) before the mod leg runs. + mkdir -p PulsarPacks/completed/RetroRewind + cp -r ${retroRewindPack}/RetroRewind6 PulsarPacks/completed/RetroRewind/RetroRewind6 + chmod -R u+w PulsarPacks + + ${lib.optionalString (retroWfcPayload != null) '' + WFC_ARGS="--retro-wfc-payload ${retroWfcPayload}" + ''} + ${lib.optionalString (retroWfcPayload == null) '' + WFC_ARGS="--skip-retro-wfc" + ''} + + Translator.Cli translate-mod --project projects/mkwii/recomp.yml --profile retro-rewind \ + --base-manifest build/base/mkwii_base_manifest.json \ + --base-translation-output-metadata generated/base_translation_output.json \ + --code-pul PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul \ + --mod-root PulsarPacks/completed/RetroRewind/RetroRewind6 \ + --mod-name "Retro Rewind" --region P --out build/mods/retro_rewind_full_cpp \ + --prefer-cached-inputs --emit-cpp --threads "$THREADS" $WFC_ARGS + ''} + + Translator.Cli generate-data-init --project projects/mkwii/recomp.yml + + SHARD_ARGS="" + ${lib.optionalString (retroRewindPack != null) '' + SHARD_ARGS="--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json --retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp" + ''} + + Translator.Cli emit-build-shards --project projects/mkwii/recomp.yml \ + --base-metadata generated/base_translation_output.json \ + --base-functions-dir generated/functions --native-source-dir runtime/src \ + --out generated/build_shards $SHARD_ARGS + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir $out + cp -r generated build $out/ + # shards.cmake embeds absolute paths pointing at /build/workspace; the + # native build (buildNative) recreates that exact layout from repoSrc + # plus these outputs, so no workspace copy is shipped here. + runHook postInstall + ''; + + meta = { + description = "Translated WiiCompiled build graph (base + Retro Rewind)"; + license = lib.licenses.gpl3Only; + }; + }; + + # Native build: configures runtime/ CMake against the translated shard + # graph and builds both products (WiiCompiled, RetroRewind) in one build so + # every profile-neutral shard object is shared between them (the same + # decision LocalBuild.ps1 makes with -Profile both). + # + # shards.cmake embeds absolute paths pointing at the translation workspace, + # so this derivation recreates that exact layout at /build/workspace: the + # repo sources plus the translation outputs in their original positions. + # Configure flags mirror Launcher/NativeBuildFlags.ps1 with the FetchContent + # trees supplied from nix/deps.nix. + buildNative = {translation}: + # Clang is the tested toolchain for this project (GCC untested upstream); + # the clang stdenv is what makes CMake pick clang over the default gcc. + llvmPackages.stdenv.mkDerivation { + name = "wiicompiled-native"; + pname = "wiicompiled"; + version = "0-unstable"; + + src = repoSrc; + + nativeBuildInputs = [ + cmake + ninja + pkg-config + patchelf + # SDL's CheckWayland runs find_program(WAYLAND_SCANNER) and silently + # drops the Wayland video driver when it is missing; nixpkgs ships the + # scanner as a standalone package, not in wayland's outputs. + wayland-scanner + ]; + buildInputs = [ + vulkan-loader + wayland + wayland-protocols + libxkbcommon + libGL + mesa + alsa-lib + libpulseaudio + pipewire + # wayland-client.pc Requires: libffi; without it SDL's pkg-config + # check fails and the Wayland video driver silently drops out. + libffi + libx11 + libxcb + libxext + libxrender + libxcursor + libxi + libxrandr + libxscrnsaver + libxfixes + libxtst + libxv + ]; + + # The clang wrapper's default hardening (pie, fortify, relro) is + # harmless here, but the fixed guest address space needs no extra + # hints from us. + hardeningDisable = ["format"]; + dontFixCmake = true; + + unpackPhase = '' + runHook preUnpack + cp -r $src workspace + chmod -R u+w workspace + mkdir -p workspace/generated workspace/build + cp -r ${translation}/generated/. workspace/generated/ + cp -r ${translation}/build/. workspace/build/ + chmod -R u+w workspace/generated workspace/build + cd workspace + runHook postUnpack + ''; + + configurePhase = '' + runHook preConfigure + # SDL builds backends in dlopen mode by default (SDL_*_SHARED=ON), + # and this stdenv's fixupPhase shrinks every RPATH entry not needed + # for DT_NEEDED resolution - which deletes exactly the dlopen'd + # libraries. Force direct linking so the normal buildInputs rpath + # survives. + cmake -S runtime -B native-build -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_SYSTEM_PROCESSOR=x86_64 \ + -DAURORA_DAWN_PROVIDER=package \ + -DAURORA_SDL3_PROVIDER=vendor \ + -DSDL_X11_SHARED=OFF \ + -DSDL_WAYLAND_SHARED=OFF \ + -DSDL_WAYLAND_LIBDECOR_SHARED=OFF \ + -DSDL_ALSA_SHARED=OFF \ + -DSDL_PIPEWIRE_SHARED=OFF \ + -DSDL_PULSEAUDIO_SHARED=OFF \ + -DCMAKE_POLICY_DEFAULT_CMP0168=NEW \ + -DFETCHCONTENT_FULLY_DISCONNECTED=ON \ + -DAWK:FILEPATH= \ + -DFETCHCONTENT_SOURCE_DIR_ABSEIL-CPP=${deps.abseil-cpp} \ + -DFETCHCONTENT_SOURCE_DIR_XXHASH=${deps.xxhash} \ + -DFETCHCONTENT_SOURCE_DIR_FMT=${deps.fmt} \ + -DFETCHCONTENT_SOURCE_DIR_ZLIB=${deps.zlib} \ + -DFETCHCONTENT_SOURCE_DIR_PNG=${deps.png} \ + -DFETCHCONTENT_SOURCE_DIR_FREETYPE=${deps.freetype} \ + -DFETCHCONTENT_SOURCE_DIR_IMGUI=${deps.imgui} \ + -DFETCHCONTENT_SOURCE_DIR_SQLITE3=${deps.sqlite3} \ + -DFETCHCONTENT_SOURCE_DIR_ZSTD=${deps.zstd} \ + -DFETCHCONTENT_SOURCE_DIR_TRACY=${deps.tracy} \ + -DFETCHCONTENT_SOURCE_DIR_SDL=${deps.sdl} \ + -DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${deps.dawn-prebuilt} \ + -DMKW_TRANSLATED_COMPILE_JOBS=$((NIX_BUILD_CORES / 2)) + runHook postConfigure + ''; + + buildPhase = '' + runHook preBuild + # mkw_release covers both products when the shard graph includes Retro + # Rewind, and just WiiCompiled for a base-only translation. + cmake --build native-build --target mkw_release + runHook postBuild + ''; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin $out/lib + install -m755 native-build/WiiCompiled $out/bin/ + if [ -f native-build/RetroRewind ]; then + install -m755 native-build/RetroRewind $out/bin/ + fi + # The vendored libpng/zlib are built shared and the CMake build-tree + # RPATH points back into /build, which Nix refuses. Install those two + # libraries beside the product and rewrite the RPATH to the remaining + # (store-path) entries plus $out/lib. Everything else — SDL3, Dawn, + # Crypto++ — is statically linked, and the backends SDL dlopens + # upstream (X11, Wayland, audio) are DT_NEEDED here, so the normal + # buildInputs rpath covers them; Vulkan is supplied by the launcher. + for lib in native-build/_deps/*/libpng16.so* native-build/_deps/*/libz.so*; do + if [ -e "$lib" ]; then + cp -L "$lib" $out/lib/ + fi + done + # The vendored libs' own RPATHs also point into /build (their _deps + # build directory); everything they need lives in $out/lib now. + for so in $out/lib/*.so*; do + patchelf --set-rpath "$out/lib" "$so" + done + for exe in $out/bin/*; do + [ -f "$exe" ] || continue + oldRpath="$(patchelf --print-rpath "$exe")" + kept="$(echo "$oldRpath" | tr ':' '\n' | grep -v '^/build/' | paste -sd:)" + if [ -n "$kept" ]; then + patchelf --set-rpath "$out/lib:$kept" "$exe" + else + patchelf --set-rpath "$out/lib" "$exe" + fi + done + # First-run assets the CMake build stages beside each product + # (runtime/cmake/PublicProducts.cmake); the DSP ROM is a hard startup + # requirement (runtime/src/hle/audio/ax_mix.cpp). Installed after the + # RPATH pass above, which must only touch ELF executables. + for asset in dsp_coef.bin initial_pipeline_cache.db; do + if [ -f "native-build/$asset" ]; then + install -m644 "native-build/$asset" "$out/bin/" + fi + done + for d in wii_bootstrap config; do + if [ -d native-build/$d ]; then + cp -r native-build/$d $out/bin/ + fi + done + # Static Dawn and SDL3 link directly into the products; nothing else + # to carry beside the per-product assets staged above. + runHook postInstall + ''; + + meta = { + description = "WiiCompiled native runtime (Mario Kart Wii static recompilation), base and Retro Rewind products"; + license = lib.licenses.gpl3Only; + platforms = ["x86_64-linux"]; + mainProgram = "WiiCompiled"; + }; + }; + + # Launcher wrappers: seed the user's Config.toml on first run and keep + # [paths] dvd_root pointing at this build's extracted disc tree (the game + # owns every other setting afterwards). dvd_root is deterministic per + # flake, but the runtime's own default Config.toml template ships the line + # commented out, so an existing file is healed in place rather than + # clobbered. User state (Config.toml, NAND, Cache, Logs) lives in + # $XDG_DATA_HOME/WiiCompiled, mirroring runtime_config.h's + # ApplicationDataDirectory(). + # + # The wrapper also scopes a Mesa drirc file to this game capping RADV at + # Vulkan 1.3: Dawn emits SPIR-V 1.4 (with the Vulkan memory model) when the + # driver reports API 1.4 and then validates it under 1.0 semantics, so + # pipeline creation dies with "Produced invalid SPIRV" on RDNA4-era RADV. + # Other drivers ignore the radv device match. Remove once upstream Dawn + # validates against a matching environment. + launcher = { + game, + dataTree, + retroRewindRoot ? null, + }: let + product = + if retroRewindRoot == null + then { + binary = "WiiCompiled"; + command = "wiicompiled"; + extraPaths = ""; + } + else { + binary = "RetroRewind"; + command = "retro-rewind"; + extraPaths = "retro_rewind_root = \"${retroRewindRoot}\""; + }; + in + runCommand "wiicompiled-launcher-${product.command}" { + nativeBuildInputs = [makeWrapper]; + passthru = {inherit game dataTree;}; + } '' + mkdir -p $out/bin $out/etc + cat > $out/etc/drirc <<'DRIRC' + + + + + + + + + DRIRC + makeWrapper ${game}/bin/${product.binary} $out/bin/${product.command} \ + --prefix LD_LIBRARY_PATH : ${vulkan-loader}/lib \ + --run ' + data_dir="''${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled" + mkdir -p "$data_dir" + + # Scope the RADV Vulkan-1.3 cap to this game via Mesa drirc; the + # XDG_CONFIG_HOME redirection applies to the game process only. + export XDG_CONFIG_HOME="$data_dir/xdg-config" + mkdir -p "$XDG_CONFIG_HOME" + if [ ! -f "$XDG_CONFIG_HOME/drirc" ]; then + cp ${placeholder "out"}/etc/drirc "$XDG_CONFIG_HOME/drirc" + fi + + if [ -f "$data_dir/Config.toml" ]; then + if grep -q "^[[:space:]]*dvd_root" "$data_dir/Config.toml"; then + sed -i "s|^[[:space:]]*dvd_root = .*|dvd_root = \"${dataTree}\"|" "$data_dir/Config.toml" + elif grep -q "^\[paths\]" "$data_dir/Config.toml"; then + sed -i "/^\[paths\]/a dvd_root = \"${dataTree}\"" "$data_dir/Config.toml" + else + printf "\n[paths]\ndvd_root = \"%s\"\n" "${dataTree}" >> "$data_dir/Config.toml" + fi + else + printf "[paths]\ndvd_root = \"%s\"\n" "${dataTree}" > "$data_dir/Config.toml" + fi + ' + ''; +} diff --git a/nix/extract.nix b/nix/extract.nix deleted file mode 100644 index db7ad53b6..000000000 --- a/nix/extract.nix +++ /dev/null @@ -1,33 +0,0 @@ -# Disc extraction derivation: consumes the user-supplied RMCP01 image (via -# pkgs.requireFile or an override) and produces the extracted filesystem tree -# the runtime serves as dvd_root and the translator feeds on. Validation mirrors -# the Windows installer (Launcher/WiiCompiled.Setup/InstallerEngine.cs): the -# required files must exist and main.dol / StaticR.rel must hash to the pins -# recorded in projects/mkwii/recomp.yml. -{ - lib, - stdenvNoCC, - nodtool, - dolSha256 ? "80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05", - relSha256 ? "16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d", -}: {discImage}: -stdenvNoCC.mkDerivation { - name = "wiicompiled-datatree"; - nativeBuildInputs = [nodtool]; - - buildCommand = '' - mkdir -p $out - nodtool extract ${discImage} $out - test -d $out/files - test -f $out/sys/fst.bin - test -f $out/sys/main.dol - test -f $out/files/rel/StaticR.rel - test "$(sha256sum $out/sys/main.dol | cut -d' ' -f1)" = "${dolSha256}" - test "$(sha256sum $out/files/rel/StaticR.rel | cut -d' ' -f1)" = "${relSha256}" - ''; - - meta = { - description = "Extracted RMCP01 disc filesystem for WiiCompiled"; - license = lib.licenses.unfree; - }; -} diff --git a/nix/game.nix b/nix/game.nix deleted file mode 100644 index 90d90110c..000000000 --- a/nix/game.nix +++ /dev/null @@ -1,211 +0,0 @@ -# Native build derivation: configures runtime/ CMake against the translated -# shard graph and builds both products (WiiCompiled, RetroRewind) in one build -# so every profile-neutral shard object is shared between them (the same -# decision LocalBuild.ps1 makes with -Profile both). -# -# shards.cmake embeds absolute paths pointing at the translation workspace, so -# this derivation recreates that exact layout at /build/workspace: the repo -# sources plus the translation outputs in their original positions. Configure -# flags mirror Launcher/NativeBuildFlags.ps1 (Get-MkwNativeFixedConfigureFlags) -# with the FetchContent trees supplied from nix/deps.nix. -{ - lib, - stdenv, - cmake, - ninja, - pkg-config, - patchelf, - wayland-scanner, - llvmPackages, - vulkan-loader, - wayland, - wayland-protocols, - libxkbcommon, - alsa-lib, - libpulseaudio, - pipewire, - libffi, - libGL, - mesa, - libx11, - libxcb, - libxext, - libxrender, - libxcursor, - libxi, - libxrandr, - libxscrnsaver, - libxfixes, - libxtst, - libxv, - deps, - repoSrc, -}: {translation}: -# Clang is the tested toolchain for this project (GCC untested upstream); -# the clang stdenv is what makes CMake pick clang over the default gcc. -llvmPackages.stdenv.mkDerivation { - name = "wiicompiled-native"; - pname = "wiicompiled"; - version = "0-unstable"; - - src = repoSrc; - - nativeBuildInputs = [ - cmake - ninja - pkg-config - patchelf - # SDL's CheckWayland runs find_program(WAYLAND_SCANNER) and silently - # drops the Wayland video driver when it is missing; nixpkgs ships the - # scanner as a standalone package, not in wayland's outputs. - wayland-scanner - ]; - buildInputs = [ - vulkan-loader - wayland - wayland-protocols - libxkbcommon - libGL - mesa - alsa-lib - libpulseaudio - pipewire - # wayland-client.pc Requires: libffi; without it SDL's pkg-config check - # fails and the Wayland video driver silently drops out of the build. - libffi - libx11 - libxcb - libxext - libxrender - libxcursor - libxi - libxrandr - libxscrnsaver - libxfixes - libxtst - libxv - ]; - - # The clang wrapper's default hardening (pie, fortify, relro) is harmless - # here, but the fixed guest address space needs no extra hints from us. - hardeningDisable = ["format"]; - dontFixCmake = true; - - unpackPhase = '' - runHook preUnpack - cp -r $src workspace - chmod -R u+w workspace - mkdir -p workspace/generated workspace/build - cp -r ${translation}/generated/. workspace/generated/ - cp -r ${translation}/build/. workspace/build/ - chmod -R u+w workspace/generated workspace/build - cd workspace - runHook postUnpack - ''; - - configurePhase = '' - runHook preConfigure - # SDL builds backends in dlopen mode by default (SDL_*_SHARED=ON), and - # this stdenv's fixupPhase shrinks every RPATH entry not needed for - # DT_NEEDED resolution - which deletes exactly the dlopen'd libraries. - # Force direct linking so the normal buildInputs rpath survives. - cmake -S runtime -B native-build -G Ninja \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_SYSTEM_PROCESSOR=x86_64 \ - -DAURORA_DAWN_PROVIDER=package \ - -DAURORA_SDL3_PROVIDER=vendor \ - -DSDL_X11_SHARED=OFF \ - -DSDL_WAYLAND_SHARED=OFF \ - -DSDL_WAYLAND_LIBDECOR_SHARED=OFF \ - -DSDL_ALSA_SHARED=OFF \ - -DSDL_PIPEWIRE_SHARED=OFF \ - -DSDL_PULSEAUDIO_SHARED=OFF \ - -DCMAKE_POLICY_DEFAULT_CMP0168=NEW \ - -DFETCHCONTENT_FULLY_DISCONNECTED=ON \ - -DAWK:FILEPATH= \ - -DFETCHCONTENT_SOURCE_DIR_ABSEIL-CPP=${deps.abseil-cpp} \ - -DFETCHCONTENT_SOURCE_DIR_XXHASH=${deps.xxhash} \ - -DFETCHCONTENT_SOURCE_DIR_FMT=${deps.fmt} \ - -DFETCHCONTENT_SOURCE_DIR_ZLIB=${deps.zlib} \ - -DFETCHCONTENT_SOURCE_DIR_PNG=${deps.png} \ - -DFETCHCONTENT_SOURCE_DIR_FREETYPE=${deps.freetype} \ - -DFETCHCONTENT_SOURCE_DIR_IMGUI=${deps.imgui} \ - -DFETCHCONTENT_SOURCE_DIR_SQLITE3=${deps.sqlite3} \ - -DFETCHCONTENT_SOURCE_DIR_ZSTD=${deps.zstd} \ - -DFETCHCONTENT_SOURCE_DIR_TRACY=${deps.tracy} \ - -DFETCHCONTENT_SOURCE_DIR_SDL=${deps.sdl} \ - -DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${deps.dawn-prebuilt} \ - -DMKW_TRANSLATED_COMPILE_JOBS=$((NIX_BUILD_CORES / 2)) - runHook postConfigure - ''; - - buildPhase = '' - runHook preBuild - # mkw_release covers both products when the shard graph includes Retro - # Rewind, and just WiiCompiled for a base-only translation. - cmake --build native-build --target mkw_release - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - mkdir -p $out/bin $out/lib - install -m755 native-build/WiiCompiled $out/bin/ - if [ -f native-build/RetroRewind ]; then - install -m755 native-build/RetroRewind $out/bin/ - fi - # The vendored libpng/zlib are built shared and the CMake build-tree - # RPATH points back into /build, which Nix refuses. Install those two - # libraries beside the product and rewrite the RPATH to the remaining - # (store-path) entries plus $out/lib. Everything else — SDL3, Dawn, - # Crypto++ — is statically linked, and the backends SDL dlopens upstream - # (X11, Wayland, audio) are DT_NEEDED here, so the normal buildInputs - # rpath covers them; Vulkan is supplied by the launcher wrapper. - foundAny=0 - for lib in native-build/_deps/*/libpng16.so* native-build/_deps/*/libz.so*; do - if [ -e "$lib" ]; then - cp -L "$lib" $out/lib/ - foundAny=1 - fi - done - # The vendored libs' own RPATHs also point into /build (their _deps - # build directory); everything they need lives in $out/lib now. - for so in $out/lib/*.so*; do - patchelf --set-rpath "$out/lib" "$so" - done - for exe in $out/bin/*; do - [ -f "$exe" ] || continue - oldRpath="$(patchelf --print-rpath "$exe")" - kept="$(echo "$oldRpath" | tr ':' '\n' | grep -v '^/build/' | paste -sd:)" - if [ -n "$kept" ]; then - patchelf --set-rpath "$out/lib:$kept" "$exe" - else - patchelf --set-rpath "$out/lib" "$exe" - fi - done - # First-run assets the CMake build stages beside each product - # (runtime/cmake/PublicProducts.cmake); the DSP ROM is a hard startup - # requirement (runtime/src/hle/audio/ax_mix.cpp). Installed after the - # RPATH pass above, which must only touch ELF executables. - for asset in dsp_coef.bin initial_pipeline_cache.db; do - if [ -f "native-build/$asset" ]; then - install -m644 "native-build/$asset" "$out/bin/" - fi - done - for d in wii_bootstrap config; do - if [ -d native-build/$d ]; then - cp -r native-build/$d $out/bin/ - fi - done - # Static Dawn and SDL3 link directly into the products; nothing else to - # carry beside the per-product assets staged above. - runHook postInstall - ''; - - meta = { - description = "WiiCompiled native runtime (Mario Kart Wii static recompilation), base and Retro Rewind products"; - license = lib.licenses.gpl3Only; - platforms = ["x86_64-linux"]; - mainProgram = "WiiCompiled"; - }; -} diff --git a/nix/retro-rewind.nix b/nix/retro-rewind.nix new file mode 100644 index 000000000..b576a7971 --- /dev/null +++ b/nix/retro-rewind.nix @@ -0,0 +1,79 @@ +# Retro Rewind is opt-in. Add the distribution zip to the Nix store with: +# +# nix-store --add-fixed sha256 --recursive /path/to/RetroRewind6.zip +# +# then paste the printed hash below. An empty string keeps the flake +# building the base product only. +{ + hash = ""; # <- paste your hash here ("": base product only) + + # Normalizes the Retro Rewind distribution into the layout the translator + # and the runtime expect: + # + # RetroRewind6/ + # Binaries/Code.pul (translator: recomp.yml profile code_pul) + # xml/RetroRewind6.xml (translator: profile riivolution pin) + # riivolution/RetroRewind6.xml (runtime: overlay discovery) + # files/... (runtime: overlay game files, when shipped) + # + # The upstream zip spells these differently (RetroRewind6/ + + # Riivolution/*.xml), so this derivation locates the pieces and re-lays + # them out. If a future pack moves things around, this is the one place to + # adjust. + normalize = { + lib, + stdenvNoCC, + unzip, + }: {retroRewindPack}: + stdenvNoCC.mkDerivation { + name = "retro-rewind-pack"; + nativeBuildInputs = [unzip]; + + buildCommand = '' + mkdir src + cd src + case "${retroRewindPack}" in + *.zip|*.7z) + unzip -q ${retroRewindPack} -d . + ;; + *) + # A store path already laid out by a custom override. + cp -r ${retroRewindPack}/. . + ;; + esac + + findDir() { + find . -type d -name "$1" | head -n 1 + } + + rrDir=$(findDir 'RetroRewind6' || true) + if [ -z "$rrDir" ]; then + # Some distributions nest one level; take the only top-level directory. + rrDir=$(find . -mindepth 2 -maxdepth 2 -type d -name 'RetroRewind6' | head -n 1) + fi + pul=$(find . -iname 'Code.pul' -type f | head -n 1) + xml=$(find . -iname 'RetroRewind6.xml' -type f | head -n 1) + + test -n "$pul" || { echo "Retro Rewind pack contains no Code.pul" >&2; exit 1; } + test -n "$xml" || { echo "Retro Rewind pack contains no Riivolution XML" >&2; exit 1; } + + mkdir -p $out/RetroRewind6/Binaries $out/RetroRewind6/xml $out/RetroRewind6/riivolution + cp "$pul" $out/RetroRewind6/Binaries/Code.pul + cp "$xml" $out/RetroRewind6/xml/RetroRewind6.xml + cp "$xml" $out/RetroRewind6/riivolution/RetroRewind6.xml + + # Carry the mod's game-file overlay (whatever the pack shipped under a + # files/ directory) so the runtime can serve it as the Riivolution root. + if [ -d "$rrDir/files" ]; then + cp -r "$rrDir/files" $out/RetroRewind6/files + elif [ -d ./files ]; then + cp -r ./files $out/RetroRewind6/files + fi + ''; + + meta = { + description = "Normalized Retro Rewind pack for WiiCompiled"; + license = lib.licenses.unfree; + }; + }; +} diff --git a/nix/rr-hash.nix b/nix/rr-hash.nix deleted file mode 100644 index a330bf3c3..000000000 --- a/nix/rr-hash.nix +++ /dev/null @@ -1,9 +0,0 @@ -# Retro Rewind pack hash. Add the distribution zip to the store with: -# -# nix-store --add-fixed sha256 --recursive RetroRewind6.zip -# -# then paste the printed hash below to enable the Retro Rewind product. An -# empty string keeps the flake building the base product only. -{ - hash = ""; -} diff --git a/nix/rr-pack.nix b/nix/rr-pack.nix deleted file mode 100644 index 587f00968..000000000 --- a/nix/rr-pack.nix +++ /dev/null @@ -1,68 +0,0 @@ -# Normalizes the Retro Rewind distribution into the layout the translator and -# the runtime expect: -# -# RetroRewind6/ -# Binaries/Code.pul (translator: recomp.yml profile code_pul) -# xml/RetroRewind6.xml (translator: profile riivolution pin) -# riivolution/RetroRewind6.xml (runtime: overlay discovery) -# files/... (runtime: overlay game files, when shipped) -# -# The upstream zip spells these differently (RetroRewind6/ + Riivolution/*.xml), -# so this derivation locates the pieces and re-lays them out. If a future pack -# moves things around, this is the one place to adjust. -{ - lib, - stdenvNoCC, - unzip, -}: {retroRewindPack}: -stdenvNoCC.mkDerivation { - name = "retro-rewind-pack"; - nativeBuildInputs = [unzip]; - - buildCommand = '' - mkdir src - cd src - case "${retroRewindPack}" in - *.zip|*.7z) - unzip -q ${retroRewindPack} -d . - ;; - *) - # A store path already laid out by a custom override. - cp -r ${retroRewindPack}/. . - ;; - esac - - findDir() { - find . -type d -name "$1" | head -n 1 - } - - rrDir=$(findDir 'RetroRewind6' || true) - if [ -z "$rrDir" ]; then - # Some distributions nest one level; take the only top-level directory. - rrDir=$(find . -mindepth 2 -maxdepth 2 -type d -name 'RetroRewind6' | head -n 1) - fi - pul=$(find . -iname 'Code.pul' -type f | head -n 1) - xml=$(find . -iname 'RetroRewind6.xml' -type f | head -n 1) - - test -n "$pul" || { echo "Retro Rewind pack contains no Code.pul" >&2; exit 1; } - test -n "$xml" || { echo "Retro Rewind pack contains no Riivolution XML" >&2; exit 1; } - - mkdir -p $out/RetroRewind6/Binaries $out/RetroRewind6/xml $out/RetroRewind6/riivolution - cp "$pul" $out/RetroRewind6/Binaries/Code.pul - cp "$xml" $out/RetroRewind6/xml/RetroRewind6.xml - cp "$xml" $out/RetroRewind6/riivolution/RetroRewind6.xml - - # Carry the mod's game-file overlay (whatever the pack shipped under a - # files/ directory) so the runtime can serve it as the Riivolution root. - if [ -d "$rrDir/files" ]; then - cp -r "$rrDir/files" $out/RetroRewind6/files - elif [ -d ./files ]; then - cp -r ./files $out/RetroRewind6/files - fi - ''; - - meta = { - description = "Normalized Retro Rewind pack for WiiCompiled"; - license = lib.licenses.unfree; - }; -} diff --git a/nix/translate.nix b/nix/translate.nix deleted file mode 100644 index 64aa50fa2..000000000 --- a/nix/translate.nix +++ /dev/null @@ -1,108 +0,0 @@ -# Translation derivation: runs the Translator.Cli over the extracted main.dol / -# StaticR.rel (base) and the Retro Rewind Code.pul (retro-rewind profile), then -# emits the data initializer and the shard manifest the native build consumes. -# -# The command sequence mirrors Launcher/LocalBuild.ps1 exactly: -# 1. stage the RR Code.pul at the profile's mod_root (workspace-relative) -# 2. translate-recursive -> generated/functions + base metadata -# 3. emit-base-manifest -> build/base/mkwii_base_manifest.json -# 4. translate-mod -> build/mods/retro_rewind_full_cpp -# 5. generate-data-init -> generated/{RuntimeConfig.h,data_sections_init*} -# 6. emit-build-shards -> generated/build_shards/shards.cmake -# -# shards.cmake embeds absolute paths (Translator.Cli GetFullPath's its inputs), -# so the native build (nix/game.nix) must place the repo and these outputs at -# the same /build/workspace paths this derivation used. -{ - lib, - stdenvNoCC, - translator, - repoSrc, -}: { - dataTree, - retroRewindPack, - retroWfcPayload ? null, -}: -stdenvNoCC.mkDerivation { - name = "wiicompiled-translation"; - - nativeBuildInputs = [translator]; - - unpackPhase = '' - runHook preUnpack - cp -r ${repoSrc} workspace - chmod -R u+w workspace - cd workspace - runHook postUnpack - ''; - - buildPhase = '' - runHook preBuild - mkdir -p Assets generated build/base - cp ${dataTree}/sys/main.dol Assets/main.dol - cp ${dataTree}/files/rel/StaticR.rel Assets/StaticR.rel - - THREADS=$([ "$NIX_BUILD_CORES" -gt 16 ] && echo 16 || echo "$NIX_BUILD_CORES") - - Translator.Cli translate-recursive 0x800060A4 --project projects/mkwii/recomp.yml \ - --outdir generated/functions --output-metadata generated/base_translation_output.json \ - --production-source-bundle generated/base_translation_sources.bin \ - --no-function-files --prune-stale --threads "$THREADS" - - Translator.Cli emit-base-manifest --project projects/mkwii/recomp.yml \ - --out build/base --functions-dir generated/functions \ - --translation-output-metadata generated/base_translation_output.json --region P - - ${lib.optionalString (retroRewindPack != null) '' - # Stage the profile's Code.pul at the mod_root recomp.yml pins so the - # base translation becomes retro-aware (leaf inlining blocks at patch - # addresses) before the mod leg runs. - mkdir -p PulsarPacks/completed/RetroRewind - cp -r ${retroRewindPack}/RetroRewind6 PulsarPacks/completed/RetroRewind/RetroRewind6 - chmod -R u+w PulsarPacks - - ${lib.optionalString (retroWfcPayload != null) '' - WFC_ARGS="--retro-wfc-payload ${retroWfcPayload}" - ''} - ${lib.optionalString (retroWfcPayload == null) '' - WFC_ARGS="--skip-retro-wfc" - ''} - - Translator.Cli translate-mod --project projects/mkwii/recomp.yml --profile retro-rewind \ - --base-manifest build/base/mkwii_base_manifest.json \ - --base-translation-output-metadata generated/base_translation_output.json \ - --code-pul PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul \ - --mod-root PulsarPacks/completed/RetroRewind/RetroRewind6 \ - --mod-name "Retro Rewind" --region P --out build/mods/retro_rewind_full_cpp \ - --prefer-cached-inputs --emit-cpp --threads "$THREADS" $WFC_ARGS - ''} - - Translator.Cli generate-data-init --project projects/mkwii/recomp.yml - - SHARD_ARGS="" - ${lib.optionalString (retroRewindPack != null) '' - SHARD_ARGS="--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json --retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp" - ''} - - Translator.Cli emit-build-shards --project projects/mkwii/recomp.yml \ - --base-metadata generated/base_translation_output.json \ - --base-functions-dir generated/functions --native-source-dir runtime/src \ - --out generated/build_shards $SHARD_ARGS - runHook postBuild - ''; - - installPhase = '' - runHook preInstall - mkdir $out - cp -r generated build $out/ - # shards.cmake embeds absolute paths pointing at /build/workspace; the - # native build (nix/game.nix) recreates that exact layout from repoSrc - # plus these outputs, so no workspace copy is shipped here. - runHook postInstall - ''; - - meta = { - description = "Translated WiiCompiled build graph (base + Retro Rewind)"; - license = lib.licenses.gpl3Only; - }; -} diff --git a/nix/translator.nix b/nix/translator/default.nix similarity index 88% rename from nix/translator.nix rename to nix/translator/default.nix index e9936fb8c..e144c77e4 100644 --- a/nix/translator.nix +++ b/nix/translator/default.nix @@ -6,9 +6,9 @@ buildDotnetModule rec { pname = "wiicompiled-translator"; version = "0-unstable"; - src = ../translator; + src = ../../translator; projectFile = "src/Translator.Cli/Translator.Cli.csproj"; - nugetDeps = ./translator/nuget-deps.nix; + nugetDeps = ./nuget-deps.nix; dotnet-sdk = dotnet-sdk_8; executables = ["Translator.Cli"]; diff --git a/nix/wrapper.nix b/nix/wrapper.nix deleted file mode 100644 index c27904b41..000000000 --- a/nix/wrapper.nix +++ /dev/null @@ -1,55 +0,0 @@ -# Launcher wrappers: seed the user's Config.toml on first run (the game owns it -# afterwards, exactly like the Windows installer's install-time write) and exec -# the product binary. dvd_root points at the store's extracted disc tree; the -# Retro Rewind wrapper additionally pins retro_rewind_root at the normalized -# pack. User state (Config.toml, NAND, Cache, Logs) lives in -# $XDG_DATA_HOME/WiiCompiled, mirroring runtime_config.h's -# ApplicationDataDirectory(). -# -# LD_LIBRARY_PATH carries vulkan-loader because Dawn loads libvulkan.so.1 via -# dlopen, and RPATH entries for dlopen'd libraries do not survive this -# stdenv's fixupPhase RPATH shrink. -{ - lib, - makeWrapper, - runCommand, - vulkan-loader, -}: { - game, - dataTree, - retroRewindRoot ? null, -}: let - product = - if retroRewindRoot == null - then { - binary = "WiiCompiled"; - command = "wiicompiled"; - extraPaths = ""; - } - else { - binary = "RetroRewind"; - command = "retro-rewind"; - extraPaths = "retro_rewind_root = \"${retroRewindRoot}\""; - }; -in - runCommand "wiicompiled-launcher-${product.command}" - { - nativeBuildInputs = [makeWrapper]; - passthru = {inherit game dataTree;}; - } - '' - mkdir -p $out/bin - makeWrapper ${game}/bin/${product.binary} $out/bin/${product.command} \ - --prefix LD_LIBRARY_PATH : ${vulkan-loader}/lib \ - --run ' - data_dir="''${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled" - mkdir -p "$data_dir" - if [ ! -f "$data_dir/Config.toml" ]; then - { - echo "[paths]" - echo "dvd_root = \"${dataTree}\"" - echo "${product.extraPaths}" - } > "$data_dir/Config.toml" - fi - ' - '' From 07bcd6d6a26f2aef29a2f0d9ce1a31ac96d54215 Mon Sep 17 00:00:00 2001 From: jamie Date: Sat, 29 Aug 2026 13:26:26 +0100 Subject: [PATCH 3/3] feat(nix): impure Retro Rewind lifecycle, drop store-pinned RR product The pack updates far too often to pin in Nix, so RR moves out of the store entirely: nix/retro-rewind.sh in charge of install, update, rebuild and launch in the user's workspace (upstream AppImage parity), wrapped as the retro-rewind app. --- flake.nix | 144 +++++++++++++++----------- nix/build.nix | 107 ++++++------------- nix/retro-rewind-app.nix | 61 +++++++++++ nix/retro-rewind.nix | 79 -------------- nix/retro-rewind.sh | 216 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 395 insertions(+), 212 deletions(-) create mode 100644 nix/retro-rewind-app.nix delete mode 100644 nix/retro-rewind.nix create mode 100644 nix/retro-rewind.sh diff --git a/flake.nix b/flake.nix index ce735c965..c47e8faaa 100644 --- a/flake.nix +++ b/flake.nix @@ -1,5 +1,5 @@ { - description = "WiiCompiled: native Mario Kart Wii port (Linux, Nix-first)"; + description = "Wiicompiled: native Mario Kart Wii recompilation"; inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; @@ -13,12 +13,6 @@ "x86_64-linux" ]; forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system}); - lib' = nixpkgs.lib; - - # Retro Rewind is opt-in: pin the pack's recursive sha256 in - # nix/retro-rewind.nix (see the instructions in that file) to enable the - # Retro Rewind product. - rr = import ./nix/retro-rewind.nix; mkOutputs = pkgs: let lib = pkgs.lib; @@ -65,38 +59,29 @@ dataTree = build.extractDisc {} {inherit discImage;}; - retroRewindPack = - if rr.hash == "" - then null - else - (unfreePkgs.callPackage rr.normalize {}) { - retroRewindPack = unfreePkgs.requireFile { - name = "RetroRewind6.zip"; - hashMode = "recursive"; - inherit (rr) hash; - message = '' - The Retro Rewind product needs the Retro Rewind distribution - zip. Add it to the Nix store with: - - nix-store --add-fixed sha256 --recursive /path/to/RetroRewind6.zip - - then pin the printed hash in nix/retro-rewind.nix. - ''; - }; - }; - - # The Retro-WFC shared payload the mod translation bakes in; fetched - # from the same endpoint the Windows build uses. + # The Retro-WFC shared payload the workspace Retro Rewind build bakes + # in; fetched from the same endpoint the Windows build uses. Wrapped in + # the binary/ layout local-build.sh's --retro-wfc-offline-dir expects. retroWfcPayload = pkgs.fetchurl { url = "http://nas.play.rwfc.net/payload?g=RMCPD00"; hash = "sha256-/Y8m1q8m8aDPrs0eRy/nRKJddfUTaRBTO3x14+yi8dI="; }; + retroWfcOffline = pkgs.runCommand "retro-wfc-payload-offline" {} '' + mkdir -p $out/binary + cp ${retroWfcPayload} $out/binary/payload.RMCPD00.bin + ''; - translation = build.translate { - inherit dataTree retroRewindPack retroWfcPayload; - }; + translation = build.translate {inherit dataTree;}; game = build.buildNative {inherit translation;}; + + retroRewindApp = pkgs.callPackage ./nix/retro-rewind-app.nix { + inherit repoSrc; + datatree = dataTree; + translator = pkgs.callPackage ./nix/translator {}; + wfcOffline = retroWfcOffline; + launcher = build.launcher {inherit game dataTree;}; + }; in rec { nodtool = pkgs.nodtool; datatree = dataTree; @@ -106,12 +91,7 @@ inherit game dataTree; }; - retro-rewind = lib.mapNullable (pack: - build.launcher { - inherit game dataTree; - retroRewindRoot = "${pack}/RetroRewind6"; - }) - retroRewindPack; + retro-rewind = retroRewindApp; default = wiicompiled; }; @@ -136,12 +116,65 @@ libxkbcommon ]; }; + + # Environment for the impure workspace Retro Rewind build the + # retro-rewind launcher drives via `nix develop .#retro-rewind-build`. + # The cc wrapper puts every package's lib dir on the binary's rpath, so + # the resulting executable runs on NixOS without further fixups; the + # dlopen-via-rpath details match nix/build.nix's findings. + retro-rewind-build = let + buildDeps = with pkgs; [ + vulkan-headers + vulkan-loader + wayland + wayland-protocols + libxkbcommon + libffi + libGL + mesa + alsa-lib + libpulseaudio + pipewire + libx11 + libxcb + libxext + libxrender + libxcursor + libxi + libxrandr + libxscrnsaver + libxfixes + libxtst + libxv + ]; + in + pkgs.mkShell { + packages = + [ + pkgs.llvmPackages.clang + pkgs.cmake + pkgs.ninja + pkgs.pkg-config + # SDL's CheckWayland drops the Wayland video driver when the + # scanner is missing (see nix/build.nix). + pkgs."wayland-scanner" + ] + ++ buildDeps; + # The cmake setup hook only populates CMAKE_PREFIX_PATH during + # real builds, not in interactive shells; feed the dependencies to + # find_package manually (dev output for headers, lib for libs). + # NIX_LDFLAGS rpath entries likewise: the workspace build has no + # fixupPhase to repair rpaths, and SDL dlopens X11/Wayland/audio + # by default, so the linked binary must carry the store lib dirs + # itself. + shellHook = '' + export CMAKE_PREFIX_PATH="${pkgs.lib.concatMapStringsSep ":" (p: "${pkgs.lib.getDev p}:${pkgs.lib.getLib p}") buildDeps}" + export NIX_LDFLAGS="$NIX_LDFLAGS ${pkgs.lib.concatMapStringsSep " " (p: "-rpath ${pkgs.lib.getLib p}/lib") buildDeps}" + ''; + }; }); - packages = forAllSystems ( - pkgs: - lib'.filterAttrs (_: value: value != null) (mkOutputs pkgs) - ); + packages = forAllSystems mkOutputs; # The pinned FetchContent sources are a nested set, so they live outside # the packages output (whose values must be derivations). Handy for @@ -153,21 +186,18 @@ apps = forAllSystems ( pkgs: let outputs = mkOutputs pkgs; - in - { - wiicompiled = { - type = "app"; - program = "${outputs.wiicompiled}/bin/wiicompiled"; - meta.description = "Mario Kart Wii (WiiCompiled base product)"; - }; - } - // (pkgs.lib.optionalAttrs (outputs.retro-rewind != null) { - retro-rewind = { - type = "app"; - program = "${outputs.retro-rewind}/bin/retro-rewind"; - meta.description = "Mario Kart Wii Retro Rewind (WiiCompiled)"; - }; - }) + in { + wiicompiled = { + type = "app"; + program = "${outputs.wiicompiled}/bin/wiicompiled"; + meta.description = "Mario Kart Wii (WiiCompiled base product)"; + }; + retro-rewind = { + type = "app"; + program = "${outputs.retro-rewind}/bin/retro-rewind"; + meta.description = "Mario Kart Wii Retro Rewind (WiiCompiled), impure install/update/launch"; + }; + } ); }; } diff --git a/nix/build.nix b/nix/build.nix index cfe5e9298..5b48b969e 100644 --- a/nix/build.nix +++ b/nix/build.nix @@ -1,16 +1,19 @@ -# WiiCompiled build pipeline, top to bottom. flake.nix supplies the -# user-owned inputs (disc dump, optional Retro Rewind pack) and wires the -# stages together: +# WiiCompiled base-game pipeline, top to bottom. flake.nix supplies the +# user-owned disc dump and wires the stages together: # # extractDisc user-supplied RMCP01 image -> validated nodtool extraction # (the extracted tree is both the translator's input and the # runtime's dvd_root) -# translate Translator.Cli over main.dol/StaticR.rel (and the opt-in -# Retro Rewind Code.pul) -> emitted C++ shard build graph +# translate Translator.Cli over main.dol/StaticR.rel -> emitted C++ +# shard build graph # buildNative cmake/ninja/clang over runtime/ + the shard graph -> the -# WiiCompiled and RetroRewind executables +# WiiCompiled executable # launcher wrapper binaries: user state seeding, RADV workaround # +# Retro Rewind is deliberately not a store product: the pack updates too +# often to pin, so nix/retro-rewind-app.nix builds it in the user's +# workspace (upstream parity) with the tools and sources from this flake. +# # Supporting files: nix/deps.nix (pinned FetchContent tarballs feeding # buildNative) and nix/translator (dotnet build of Translator.Cli feeding # translate). @@ -82,26 +85,23 @@ }; # Translation: runs the Translator.Cli over the extracted main.dol / - # StaticR.rel (base) and the Retro Rewind Code.pul (retro-rewind profile), - # then emits the data initializer and the shard manifest the native build - # consumes. + # StaticR.rel, then emits the data initializer and the shard manifest the + # native build consumes. + # + # The command sequence mirrors Launcher/local-build.sh's base profile: + # 1. translate-recursive -> generated/functions + base metadata + # 2. emit-base-manifest -> build/base/mkwii_base_manifest.json + # 3. generate-data-init -> generated/{RuntimeConfig.h,data_sections_init*} + # 4. emit-build-shards -> generated/build_shards/shards.cmake # - # The command sequence mirrors Launcher/local-build.sh exactly: - # 1. stage the RR Code.pul at the profile's mod_root (workspace-relative) - # 2. translate-recursive -> generated/functions + base metadata - # 3. emit-base-manifest -> build/base/mkwii_base_manifest.json - # 4. translate-mod -> build/mods/retro_rewind_full_cpp - # 5. generate-data-init -> generated/{RuntimeConfig.h,data_sections_init*} - # 6. emit-build-shards -> generated/build_shards/shards.cmake + # Retro Rewind is not built here: it is translated and compiled in the + # user's workspace by the impure retro-rewind launcher (upstream parity), + # because the pack updates far too often for store pinning. # # shards.cmake embeds absolute paths (Translator.Cli GetFullPath's its # inputs), so the native build (buildNative) must place the repo and these # outputs at the same /build/workspace paths this derivation used. - translate = { - dataTree, - retroRewindPack, - retroWfcPayload ? null, - }: + translate = {dataTree}: stdenvNoCC.mkDerivation { name = "wiicompiled-translation"; @@ -132,41 +132,12 @@ --out build/base --functions-dir generated/functions \ --translation-output-metadata generated/base_translation_output.json --region P - ${lib.optionalString (retroRewindPack != null) '' - # Stage the profile's Code.pul at the mod_root recomp.yml pins so the - # base translation becomes retro-aware (leaf inlining blocks at patch - # addresses) before the mod leg runs. - mkdir -p PulsarPacks/completed/RetroRewind - cp -r ${retroRewindPack}/RetroRewind6 PulsarPacks/completed/RetroRewind/RetroRewind6 - chmod -R u+w PulsarPacks - - ${lib.optionalString (retroWfcPayload != null) '' - WFC_ARGS="--retro-wfc-payload ${retroWfcPayload}" - ''} - ${lib.optionalString (retroWfcPayload == null) '' - WFC_ARGS="--skip-retro-wfc" - ''} - - Translator.Cli translate-mod --project projects/mkwii/recomp.yml --profile retro-rewind \ - --base-manifest build/base/mkwii_base_manifest.json \ - --base-translation-output-metadata generated/base_translation_output.json \ - --code-pul PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul \ - --mod-root PulsarPacks/completed/RetroRewind/RetroRewind6 \ - --mod-name "Retro Rewind" --region P --out build/mods/retro_rewind_full_cpp \ - --prefer-cached-inputs --emit-cpp --threads "$THREADS" $WFC_ARGS - ''} - Translator.Cli generate-data-init --project projects/mkwii/recomp.yml - SHARD_ARGS="" - ${lib.optionalString (retroRewindPack != null) '' - SHARD_ARGS="--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json --retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp" - ''} - Translator.Cli emit-build-shards --project projects/mkwii/recomp.yml \ --base-metadata generated/base_translation_output.json \ --base-functions-dir generated/functions --native-source-dir runtime/src \ - --out generated/build_shards $SHARD_ARGS + --out generated/build_shards runHook postBuild ''; @@ -187,9 +158,7 @@ }; # Native build: configures runtime/ CMake against the translated shard - # graph and builds both products (WiiCompiled, RetroRewind) in one build so - # every profile-neutral shard object is shared between them (the same - # decision LocalBuild.ps1 makes with -Profile both). + # graph and builds the base product. # # shards.cmake embeds absolute paths pointing at the translation workspace, # so this derivation recreates that exact layout at /build/workspace: the @@ -299,8 +268,7 @@ buildPhase = '' runHook preBuild - # mkw_release covers both products when the shard graph includes Retro - # Rewind, and just WiiCompiled for a base-only translation. + # Base-only shard graph: mkw_release builds just WiiCompiled. cmake --build native-build --target mkw_release runHook postBuild ''; @@ -359,14 +327,14 @@ ''; meta = { - description = "WiiCompiled native runtime (Mario Kart Wii static recompilation), base and Retro Rewind products"; + description = "WiiCompiled native runtime (Mario Kart Wii static recompilation), base product"; license = lib.licenses.gpl3Only; platforms = ["x86_64-linux"]; mainProgram = "WiiCompiled"; }; }; - # Launcher wrappers: seed the user's Config.toml on first run and keep + # Launcher wrapper: seed the user's Config.toml on first run and keep # [paths] dvd_root pointing at this build's extracted disc tree (the game # owns every other setting afterwards). dvd_root is deterministic per # flake, but the runtime's own default Config.toml template ships the line @@ -380,26 +348,13 @@ # driver reports API 1.4 and then validates it under 1.0 semantics, so # pipeline creation dies with "Produced invalid SPIRV" on RDNA4-era RADV. # Other drivers ignore the radv device match. Remove once upstream Dawn - # validates against a matching environment. + # validates against a matching environment. The impure retro-rewind + # launcher reuses the same drirc file for the workspace-built binary. launcher = { game, dataTree, - retroRewindRoot ? null, - }: let - product = - if retroRewindRoot == null - then { - binary = "WiiCompiled"; - command = "wiicompiled"; - extraPaths = ""; - } - else { - binary = "RetroRewind"; - command = "retro-rewind"; - extraPaths = "retro_rewind_root = \"${retroRewindRoot}\""; - }; - in - runCommand "wiicompiled-launcher-${product.command}" { + }: + runCommand "wiicompiled-launcher-wiicompiled" { nativeBuildInputs = [makeWrapper]; passthru = {inherit game dataTree;}; } '' @@ -416,7 +371,7 @@ DRIRC - makeWrapper ${game}/bin/${product.binary} $out/bin/${product.command} \ + makeWrapper ${game}/bin/WiiCompiled $out/bin/wiicompiled \ --prefix LD_LIBRARY_PATH : ${vulkan-loader}/lib \ --run ' data_dir="''${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled" diff --git a/nix/retro-rewind-app.nix b/nix/retro-rewind-app.nix new file mode 100644 index 000000000..bdc0fa5f4 --- /dev/null +++ b/nix/retro-rewind-app.nix @@ -0,0 +1,61 @@ +# The impure Retro Rewind launcher: install/update/build/launch orchestration +# running in the user's workspace (see nix/retro-rewind.sh). Everything the +# script needs at runtime is either on its wrapped PATH or baked in as a store +# path; the workspace build itself happens inside the retro-rewind-build +# devShell (flake.nix) so the compiled binary carries store rpaths and runs +# on NixOS unmodified. +{ + lib, + runCommand, + makeWrapper, + replaceVars, + bash, + coreutils, + curl, + unzip, + findutils, + gnugrep, + gnused, + gawk, + nix, + vulkan-loader, + repoSrc, + datatree, + translator, + wfcOffline, + launcher, +}: let + script = replaceVars ./retro-rewind.sh { + inherit + bash + datatree + translator + wfcOffline + launcher + repoSrc + vulkan-loader + ; + }; +in + runCommand "wiicompiled-retro-rewind" { + nativeBuildInputs = [makeWrapper]; + meta = { + description = "Retro Rewind install/update/build/launch orchestration for WiiCompiled"; + mainProgram = "retro-rewind"; + platforms = ["x86_64-linux"]; + }; + } '' + install -Dm755 ${script} $out/bin/retro-rewind + wrapProgram $out/bin/retro-rewind \ + --prefix PATH : ${lib.makeBinPath [ + bash + coreutils + curl + unzip + findutils + gnugrep + gnused + gawk + nix + ]} + '' diff --git a/nix/retro-rewind.nix b/nix/retro-rewind.nix deleted file mode 100644 index b576a7971..000000000 --- a/nix/retro-rewind.nix +++ /dev/null @@ -1,79 +0,0 @@ -# Retro Rewind is opt-in. Add the distribution zip to the Nix store with: -# -# nix-store --add-fixed sha256 --recursive /path/to/RetroRewind6.zip -# -# then paste the printed hash below. An empty string keeps the flake -# building the base product only. -{ - hash = ""; # <- paste your hash here ("": base product only) - - # Normalizes the Retro Rewind distribution into the layout the translator - # and the runtime expect: - # - # RetroRewind6/ - # Binaries/Code.pul (translator: recomp.yml profile code_pul) - # xml/RetroRewind6.xml (translator: profile riivolution pin) - # riivolution/RetroRewind6.xml (runtime: overlay discovery) - # files/... (runtime: overlay game files, when shipped) - # - # The upstream zip spells these differently (RetroRewind6/ + - # Riivolution/*.xml), so this derivation locates the pieces and re-lays - # them out. If a future pack moves things around, this is the one place to - # adjust. - normalize = { - lib, - stdenvNoCC, - unzip, - }: {retroRewindPack}: - stdenvNoCC.mkDerivation { - name = "retro-rewind-pack"; - nativeBuildInputs = [unzip]; - - buildCommand = '' - mkdir src - cd src - case "${retroRewindPack}" in - *.zip|*.7z) - unzip -q ${retroRewindPack} -d . - ;; - *) - # A store path already laid out by a custom override. - cp -r ${retroRewindPack}/. . - ;; - esac - - findDir() { - find . -type d -name "$1" | head -n 1 - } - - rrDir=$(findDir 'RetroRewind6' || true) - if [ -z "$rrDir" ]; then - # Some distributions nest one level; take the only top-level directory. - rrDir=$(find . -mindepth 2 -maxdepth 2 -type d -name 'RetroRewind6' | head -n 1) - fi - pul=$(find . -iname 'Code.pul' -type f | head -n 1) - xml=$(find . -iname 'RetroRewind6.xml' -type f | head -n 1) - - test -n "$pul" || { echo "Retro Rewind pack contains no Code.pul" >&2; exit 1; } - test -n "$xml" || { echo "Retro Rewind pack contains no Riivolution XML" >&2; exit 1; } - - mkdir -p $out/RetroRewind6/Binaries $out/RetroRewind6/xml $out/RetroRewind6/riivolution - cp "$pul" $out/RetroRewind6/Binaries/Code.pul - cp "$xml" $out/RetroRewind6/xml/RetroRewind6.xml - cp "$xml" $out/RetroRewind6/riivolution/RetroRewind6.xml - - # Carry the mod's game-file overlay (whatever the pack shipped under a - # files/ directory) so the runtime can serve it as the Riivolution root. - if [ -d "$rrDir/files" ]; then - cp -r "$rrDir/files" $out/RetroRewind6/files - elif [ -d ./files ]; then - cp -r ./files $out/RetroRewind6/files - fi - ''; - - meta = { - description = "Normalized Retro Rewind pack for WiiCompiled"; - license = lib.licenses.unfree; - }; - }; -} diff --git a/nix/retro-rewind.sh b/nix/retro-rewind.sh new file mode 100644 index 000000000..e394498af --- /dev/null +++ b/nix/retro-rewind.sh @@ -0,0 +1,216 @@ +#!@bash@/bin/bash +# Impure Retro Rewind lifecycle for the WiiCompiled flake: install, update, +# build and launch in the user's workspace, mirroring the upstream AppImage +# flow. The pack updates too often to pin in Nix, so everything after the +# base-game build happens here with store-provided tools. +# +# retro-rewind ensure installed (auto-install on first run), launch +# retro-rewind update check the CDN, apply the newest pack, rebuild, launch +# retro-rewind check report only; never builds or downloads +set -euo pipefail + +readonly DATA_TREE="@datatree@" +readonly TRANSLATOR="@translator@/bin/Translator.Cli" +readonly WFC_OFFLINE="@wfcOffline@" +readonly DRIRC_SRC="@launcher@/etc/drirc" +readonly REPO_SRC="@repoSrc@" +readonly VULKAN_LIB="@vulkan-loader@/lib" + +readonly WS_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled" +readonly WORKSPACE="$WS_ROOT/workspace" +readonly PACK_DIR="$WORKSPACE/PulsarPacks/completed/RetroRewind/RetroRewind6" +readonly INSTALL_DIR="$WS_ROOT/Install/RetroRewind" +readonly RR_VERSION_URL="https://update.rwfc.net/RetroRewind/RetroRewindVersion.txt" +readonly RR_INSTALL_URL_FILE="https://update.rwfc.net/RetroRewind/RetroRewindInstall.txt" + +log() { printf 'retro-rewind: %s\n' "$*"; } +die() { printf 'retro-rewind: error: %s\n' "$*" >&2; exit 1; } + +readonly FLAKE_DIR="$PWD" +[ -f "$FLAKE_DIR/flake.nix" ] || die "run from the WiiCompiled flake checkout (./flake.nix is needed for the build shell)" + +installed_version() { + if [ -f "$PACK_DIR/version.txt" ]; then + cat "$PACK_DIR/version.txt" + else + echo "" + fi +} + +latest_version() { + curl -fsSL --max-time 5 "$RR_VERSION_URL" | awk 'NF {v=$1} END {print v}' +} + +latest_install_url() { + curl -fsSL --max-time 5 "$RR_INSTALL_URL_FILE" | tr -d '[:space:]' +} + +ensure_workspace() { + mkdir -p "$WS_ROOT" + stamp="$WORKSPACE/.bundle-version" + if [ ! -f "$stamp" ] || [ "$(cat "$stamp")" != "$REPO_SRC" ]; then + log "Syncing workspace sources from the flake snapshot" + mkdir -p "$WORKSPACE/Launcher" + rm -rf "$WORKSPACE/runtime" "$WORKSPACE/aurora-main" "$WORKSPACE/projects" + cp -r "$REPO_SRC/runtime" "$REPO_SRC/aurora-main" "$REPO_SRC/projects" "$WORKSPACE/" + cp "$REPO_SRC/Launcher/local-build.sh" "$WORKSPACE/Launcher/" + chmod -R u+w "$WORKSPACE" + printf '%s' "$REPO_SRC" > "$stamp" + fi + + # The workspace translation needs the user-owned disc data; copy it out of + # the store tree so local-build.sh's fingerprint checks see real files. + mkdir -p "$WORKSPACE/Assets" + cp -f "$DATA_TREE/sys/main.dol" "$WORKSPACE/Assets/main.dol" + cp -f "$DATA_TREE/files/rel/StaticR.rel" "$WORKSPACE/Assets/StaticR.rel" +} + +install_rr() { + local version url tmp exdir rrdir xml rizip + version=$(latest_version) || die "cannot reach the Retro Rewind CDN" + [ -n "$version" ] || die "CDN returned no version" + url=$(latest_install_url) || die "cannot resolve the full-pack URL" + [ -n "$url" ] || die "empty full-pack URL" + + log "Downloading Retro Rewind $version" + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + curl -fL --progress-bar "$url" -o "$tmp/rr.zip" + + log "Extracting" + exdir="$tmp/extracted" + mkdir -p "$exdir" + unzip -q "$tmp/rr.zip" -d "$exdir" + + # Normalize into the virtual-SD layout the pack's Riivolution xml and + # projects/mkwii/recomp.yml both assume: + # + # completed/RetroRewind/ <- virtual SD root + # ├── RetroRewind6/ <- pack folder (overlayRoot) + # │ ├── Binaries/Code.pul (translator: profile code_pul) + # │ ├── xml/RetroRewind6.xml (profile riivolution pin) + # │ └── Tracks/ Assets/ ... (content the xml externals reference + # │ as /RetroRewind6/...) + # └── riivolution/ (xml + save data, referenced as + # /riivolution/...) + rrdir=$(find "$exdir" -type d -name RetroRewind6 | head -n 1) + [ -n "$rrdir" ] || die "no RetroRewind6 directory in the pack zip" + xml=$(find "$exdir" -iname 'RetroRewind6.xml' -type f | head -n 1) + [ -n "$xml" ] || die "no RetroRewind6.xml in the pack zip" + rizip=$(find "$exdir" -type d -iname 'riivolution' | head -n 1) + + pack_parent="$WORKSPACE/PulsarPacks/completed/RetroRewind" + mkdir -p "$pack_parent" + rm -rf "$PACK_DIR" "$pack_parent/riivolution" + cp -r "$rrdir" "$PACK_DIR" + mkdir -p "$PACK_DIR/xml" + cp "$xml" "$PACK_DIR/xml/RetroRewind6.xml" + if [ -n "$rizip" ]; then + cp -r "$rizip" "$pack_parent/riivolution" + fi + printf '%s' "$version" > "$PACK_DIR/version.txt" + chmod -R u+w "$WORKSPACE/PulsarPacks" +} + +build_rr() { + log "Translating and compiling (this can take a while; upstream caching applies)" + (cd "$FLAKE_DIR" && nix develop .#retro-rewind-build -c bash "$WORKSPACE/Launcher/local-build.sh" \ + --workspace "$WORKSPACE" \ + --profile retro-rewind \ + --output-dir "$INSTALL_DIR" \ + --retro-rewind-package-dir "$PACK_DIR" \ + --retro-wfc-offline-dir "$WFC_OFFLINE" \ + --translator-bin "$TRANSLATOR") +} + +# $1 = key, $2 = value. Heals an uncommented key under [paths] without +# clobbering any other user settings, mirroring the pure launcher. +ensure_path_key() { + local key=$1 value=$2 file="$WS_ROOT/Config.toml" + if [ -f "$file" ] && grep -q "^[[:space:]]*$key" "$file"; then + sed -i "s|^[[:space:]]*$key = .*|$key = \"$value\"|" "$file" + elif [ -f "$file" ] && grep -q '^\[paths\]' "$file"; then + sed -i "/^\[paths\]/a $key = \"$value\"" "$file" + elif [ -f "$file" ]; then + printf '\n[paths]\n%s = "%s"\n' "$key" "$value" >> "$file" + else + printf '[paths]\n%s = "%s"\n' "$key" "$value" > "$file" + fi +} + +launch_rr() { + local exe="$INSTALL_DIR/RetroRewind" + [ -f "$exe" ] || die "Retro Rewind is not installed; run 'retro-rewind update'" + + ensure_path_key dvd_root "$DATA_TREE" + ensure_path_key retro_rewind_root "$PACK_DIR" + + export LD_LIBRARY_PATH="$VULKAN_LIB${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" + export XDG_CONFIG_HOME="$WS_ROOT/xdg-config" + mkdir -p "$XDG_CONFIG_HOME" + if [ ! -f "$XDG_CONFIG_HOME/drirc" ]; then + cp "$DRIRC_SRC" "$XDG_CONFIG_HOME/drirc" + fi + exec "$exe" +} + +maybe_hint_update() { + local latest current + latest=$(latest_version 2>/dev/null) || return 0 + current=$(installed_version) + [ -n "$current" ] || return 0 + if [ "$latest" != "$current" ]; then + log "Retro Rewind $latest is available ($current installed); run 'retro-rewind update'" + fi +} + +case "${1:-launch}" in + check) + latest=$(latest_version) || die "cannot reach the Retro Rewind CDN" + current=$(installed_version) + if [ -z "$current" ]; then + log "not installed; latest on the CDN is $latest (run 'retro-rewind update' to install)" + elif [ "$latest" = "$current" ]; then + log "up to date ($current)" + else + log "update available: $current -> $latest (run 'retro-rewind update')" + fi + ;; + update) + ensure_workspace + latest=$(latest_version) || die "cannot reach the Retro Rewind CDN" + current=$(installed_version) + if [ "$latest" != "$current" ]; then + install_rr + else + log "pack already latest ($current)" + fi + # Always rebuild: incremental (cmake config + no-op ninja when + # nothing changed), and it picks up toolchain/flake changes that a + # version match alone would hide. + build_rr + log "Retro Rewind $(installed_version) ready" + launch_rr + ;; + reinstall) + ensure_workspace + install_rr + build_rr + log "reinstalled Retro Rewind $(installed_version)" + launch_rr + ;; + launch) + if [ -z "$(installed_version)" ] || [ ! -f "$INSTALL_DIR/RetroRewind" ]; then + log "no Retro Rewind install found; installing" + ensure_workspace + install_rr + build_rr + log "installed Retro Rewind $(installed_version)" + fi + maybe_hint_update + launch_rr + ;; + *) + die "usage: retro-rewind [launch|update|reinstall|check]" + ;; +esac