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..c47e8faaa
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,203 @@
+{
+ description = "Wiicompiled: native Mario Kart Wii recompilation";
+
+ 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});
+
+ mkOutputs = pkgs: let
+ lib = pkgs.lib;
+ # 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
+ # 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
+ '';
+ };
+
+ # 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;};
+
+ # 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;};
+
+ 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;
+ inherit translation game;
+
+ wiicompiled = build.launcher {
+ inherit game dataTree;
+ };
+
+ retro-rewind = retroRewindApp;
+
+ 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
+ ];
+ };
+
+ # 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 mkOutputs;
+
+ # 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)";
+ };
+ 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
new file mode 100644
index 000000000..5b48b969e
--- /dev/null
+++ b/nix/build.nix
@@ -0,0 +1,401 @@
+# 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 -> emitted C++
+# shard build graph
+# buildNative cmake/ninja/clang over runtime/ + the shard graph -> the
+# 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).
+{
+ 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, 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
+ #
+ # 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}:
+ 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
+
+ Translator.Cli generate-data-init --project projects/mkwii/recomp.yml
+
+ 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
+ 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 the base product.
+ #
+ # 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
+ # Base-only shard graph: mkw_release builds just WiiCompiled.
+ 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 product";
+ license = lib.licenses.gpl3Only;
+ platforms = ["x86_64-linux"];
+ mainProgram = "WiiCompiled";
+ };
+ };
+
+ # 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
+ # 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. The impure retro-rewind
+ # launcher reuses the same drirc file for the workspace-built binary.
+ launcher = {
+ game,
+ dataTree,
+ }:
+ runCommand "wiicompiled-launcher-wiicompiled" {
+ nativeBuildInputs = [makeWrapper];
+ passthru = {inherit game dataTree;};
+ } ''
+ mkdir -p $out/bin $out/etc
+ cat > $out/etc/drirc <<'DRIRC'
+
+
+
+
+
+
+
+
+
+
+ DRIRC
+ makeWrapper ${game}/bin/WiiCompiled $out/bin/wiicompiled \
+ --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/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/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.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
diff --git a/nix/translator/default.nix b/nix/translator/default.nix
new file mode 100644
index 000000000..e144c77e4
--- /dev/null
+++ b/nix/translator/default.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 = ./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=";
+ })
+]