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="; + }) +]