Unverified Commit c7655776 authored by Audrey Dutcher's avatar Audrey Dutcher Committed by GitHub
Browse files

celestegame: init at 1.4.0.0{,+everest.5806} (#371078)

parents 3e5aa7df 422a6e0a
Loading
Loading
Loading
Loading
+171 −0
Original line number Diff line number Diff line
{
  lib,
  stdenvNoCC,
  requireFile,
  makeWrapper,
  copyDesktopItems,
  makeDesktopItem,
  unzip,
  yq,
  dotnet-runtime_8,

  executableName ? "Celeste",
  desktopItems ? null,
  everest ? null,
  overrideSrc ? null,
  writableDir ? null,
  launchFlags ? "",
  launchEnv ? "",
  # If we leave it to be the default (log.txt),
  # Everest will try to delete log.txt when it starts,
  # which doesn't work because the file system is read-only.
  # https://github.com/EverestAPI/Everest/blob/050b4a1b4a7918b22d3d5140224f9c0472e1655a/Celeste.Mod.mm/Patches/Celeste.cs#L140-L155
  everestLogFilename ? "everest-log.txt",
}:

# TODO: It appears that it is possible to package Celeste for aarch devices:
# https://github.com/pixelomer/Celeste-ARM64
# However, I don't have an aarch device to do that.
# The ARM support doesn't seem promising because the builder needs to fetch fmod libraries somehow, which requires an account.
# Though this whole process of registration and downloading can possibly be automated, this is probably against the TOS.
let
  pname = "celeste-unwrapped";
  version = "1.4.0.0";
  downloadPage = "https://maddymakesgamesinc.itch.io/celeste";
  description = "2D platformer game about climing a mountain";
  phome = "$out/lib/Celeste";

  launchFlags' =
    if launchFlags != "" && everest == null then
      lib.warn "launchFlags is useless without Everest." ""
    else
      launchFlags;
  launchEnv' =
    if launchEnv != "" && everest == null then
      lib.warn "launchEnv is useless without Everest." ""
    else
      ''
        EVEREST_LOG_FILENAME=${everestLogFilename}
        EVEREST_TMPDIR=${writableDir}
        ${launchEnv}
      '';
in
stdenvNoCC.mkDerivation {
  pname = "celeste-unwrapped";
  version = version;

  src =
    if overrideSrc == null then
      requireFile {
        name = "celeste-linux.zip";
        hash = "sha256-phNDBBHb7zwMRaBHT5D0hFEilkx9F31p6IllvLhHQb8=";
        url = downloadPage;
      }
    else
      overrideSrc;
  dontUnpack = true;

  nativeBuildInputs = [
    unzip
    yq
    makeWrapper
    copyDesktopItems
  ];
  desktopItems =
    if desktopItems != null then
      desktopItems
    else
      [
        (makeDesktopItem {
          name = "Celeste";
          desktopName = "Celeste";
          genericName = "Celeste";
          comment = description;
          exec = "${executableName}";
          icon = "Celeste";
          categories = [ "Game" ];
        })
      ];

  postInstall = ''
    mkdir -p ${phome}
    unzip -q $src -d ${phome}
  ''
  + lib.optionalString (everest != null) ''
    cp -r ${everest}/* $out
    chmod -R +w ${phome} # Files copied from other derivations are not writable by default

    # There will still be a runtime error saying chmod failed for the splash,
    # but it doesn't matter because we make it executable here.
    # https://github.com/EverestAPI/Everest/blob/050b4a1b4a7918b22d3d5140224f9c0472e1655a/Celeste.Mod.mm/Mod/Everest/EverestSplashHandler.cs#L73-L81
    chmod +x ${phome}/EverestSplash/EverestSplash-linux

    # Everest determines whether it is FNA or XNA by the existence of the file.
    # Creating this now prevents it from creating it in the future
    # when the file system is read-only.
    # https://github.com/EverestAPI/Everest/blob/050b4a1b4a7918b22d3d5140224f9c0472e1655a/Celeste.Mod.mm/Patches/Celeste.cs#L41-L47
    touch ${phome}/BuildIsFNA.txt

    # Please Piton by having the runtime.
    # Otherwise it will try to download it.
    # https://github.com/Popax21/Piton/blob/21c7868d06007f0c5e7d9030a0109fe892df1bf3/apphost/src/runtime.rs#L82-L89
    mkdir ${phome}/piton-runtime
    ln -s ${dotnet-runtime_8}/share/dotnet/* -t ${phome}/piton-runtime
    platform=linux-x86_64
    echo -n "$platform $(yq -r .\"$platform\".version ${phome}/piton-runtime.yaml)" > ${phome}/piton-runtime/piton-runtime-id.txt

    chmod +x ${phome}/MiniInstaller-linux
    ${phome}/MiniInstaller-linux

    echo "${launchFlags'}" > ${phome}/everest-launch.txt
    echo "${launchEnv'}" > ${phome}/everest-env.txt
  ''
  + (
    if writableDir != null then
      ''
        mv ${phome}/Celeste ${phome}/Celeste-unwrapped
        ln -s ${writableDir}/Celeste ${phome}/Celeste
      ''
    else
      ''
        ln -s ${phome}/Celeste ${phome}/Celeste-unwrapped
      ''
  )
  + ''
    makeWrapper ${phome}/Celeste-unwrapped $out/bin/${executableName} ${
      # If ${phome}/lib64-linux is not present in LD_LIBRARY_PATH, Everest will try to restart:
      # https://github.com/EverestAPI/Everest/blob/7bd41c26850bbdfef937e2ed929174e864101c4c/Celeste.Mod.mm/Mod/Everest/BOOT.cs#L188-L201
      # It is hardcoded that it launches Celeste instead of Celeste-unwrapped.
      # Therefore, we need to prevent it from restarting.
      lib.optionalString (everest != null) "--prefix LD_LIBRARY_PATH : ${phome}/lib64-linux"
    } --chdir ${phome}

    icon=$out/share/icons/hicolor/512x512/apps/Celeste.png
    mkdir -p $(dirname $icon)
    ln -s ${phome}/Celeste.png $icon
  '';

  dontPatchELF = true;
  dontStrip = true;
  dontPatchShebangs = true;
  postFixup =
    lib.optionalString (everest != null) ''
      rm -r ${phome}/Mods # Currently it is empty.
      ln -s "${writableDir}"/{Mods,LogHistory,CrashLogs,${everestLogFilename}} -t ${phome}
    ''
    + lib.optionalString (writableDir != null) ''
      ln -s "${writableDir}/log.txt" -t ${phome}
    '';

  meta = {
    inherit downloadPage description;
    homepage = "https://www.celestegame.com";
    license = with lib.licenses; [ unfree ];
    sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
    maintainers = with lib.maintainers; [ ulysseszhan ];
    platforms = [
      "x86_64-linux"
      "i686-linux"
    ];
  };
}
+47 −0
Original line number Diff line number Diff line
{
  lib,
  stdenvNoCC,
  fetchzip,
  icu,
  autoPatchelfHook,
}:

let
  pname = "everest";
  version = "5806";
  phome = "$out/lib/Celeste";
in
stdenvNoCC.mkDerivation {
  inherit pname version;
  src = fetchzip {
    url = "https://github.com/EverestAPI/Everest/releases/download/stable-1.5806.0/main.zip";
    extension = "zip";
    hash = "sha256-Hw/BNvWfhdO7bvYrY/Px12BRG1SYcCBeAXBH4QnKyeY=";
  };
  buildInputs = [
    icu
  ];
  nativeBuildInputs = [
    autoPatchelfHook
  ];
  postInstall = ''
    mkdir -p ${phome}
    cp -r * ${phome}
  '';
  dontAutoPatchelf = true;
  dontPatchELF = true;
  dontStrip = true;
  dontPatchShebangs = true;
  postFixup = ''
    autoPatchelf ${phome}/MiniInstaller-linux
  '';
  meta = {
    description = "Celeste mod loader";
    license = with lib.licenses; [ mit ];
    maintainers = with lib.maintainers; [ ulysseszhan ];
    homepage = "https://everestapi.github.io";
    platforms = [ "x86_64-linux" ];
    sourceProvenance = with lib.sourceTypes; [ binaryNativeCode ];
  };

}
+107 −0
Original line number Diff line number Diff line
{
  lib,
  fetchurl,
  fetchFromGitHub,
  buildDotnetModule,
  dotnetCorePackages,
  autoPatchelfHook,
  mono,
  git,
  icu,
}:

let
  pname = "everest";
  version = "5806";
  phome = "$out/lib/Celeste";
in
buildDotnetModule {
  inherit pname version;

  src = fetchFromGitHub {
    owner = "EverestAPI";
    repo = "Everest";
    rev = "e47f67fc8c4b0b60b0a75112c5c90704ed371040";
    fetchSubmodules = true;
    leaveDotGit = true; # MonoMod.SourceGen.Internal needs .git
    hash = "sha256-uxb9LwCDGJIc+JN2EqNqHdLLwULnG7Bd/Az3H1zKf3E=";
  };

  nativeBuildInputs = [
    git
    autoPatchelfHook
  ];

  buildInputs = [
    icu # For autoPatchelf
    mono # See upstream README
  ];

  postPatch = ''
    # MonoMod.ILHelpers.Patcher complains at build phase: You must install .NET to run this application.
    sed -i 's|<Exec Command="&quot;|<Exec Command="DOTNET_ROOT=${dotnetCorePackages.runtime_8_0}/share/dotnet \&quot;|' external/MonoMod/tools/Common.IL.targets

    # Moving files after publishing somehow doesn't work. Will do this manually in postInstall.
    sed -i 's|<Move.*/>||' Celeste.Mod.mm/Celeste.Mod.mm.csproj

    autoPatchelf lib-ext/piton/piton-linux_x64
  '';

  dotnet-sdk = dotnetCorePackages.sdk_9_0;

  preConfigure = ''
    # Microsoft.SourceLink.GitHub complains: Unable to determine repository url, the source code won't be available via source link.
    cd external/MonoMod
    git -c safe.directory='*' remote add origin https://github.com/MonoMod/MonoMod.git
    cd ../..
  '';

  nugetDeps = ./deps.json;

  # Needed for ILAsm projects: https://github.com/NixOS/nixpkgs/issues/370754#issuecomment-2571475814
  linkNugetPackages = true;

  # Microsoft.NET.Sdk complains: The process cannot access the file xxx because it is being used by another process.
  enableParallelBuilding = false;

  preBuild = ''
    # See .azure-pipelines/prebuild.ps1
    sed -i 's|0\.0\.0-dev|1.${version}.0-nixos-'$(git rev-parse --short=5 HEAD)'|' Celeste.Mod.mm/Mod/Everest/Everest.cs
    cat Celeste.Mod.mm/Mod/Everest/Everest.cs
    cat <<-EOF > Celeste.Mod.mm/Mod/Helpers/EverestVersion.cs
      namespace Celeste.Mod.Helpers {
        internal static class EverestBuild${version} {
          public static string EverestBuild = "EverestBuild${version}";
        }
      }
    EOF
  '';

  installPath = builtins.replaceStrings [ "$out" ] [ (placeholder "out") ] phome;

  postInstall = ''
    mkdir tmp-EverestSplash
    mv ${phome}/EverestSplash* tmp-EverestSplash
    mv tmp-EverestSplash ${phome}/EverestSplash
    cp ${phome}/piton-runtime.yaml ${phome}/EverestSplash
  '';

  executables = [ ];

  dontPatchELF = true;
  dontStrip = true;
  dontPatchShebangs = true;
  dontAutoPatchelf = true;

  meta = {
    description = "Celeste mod loader";
    license = with lib.licenses; [ mit ];
    maintainers = with lib.maintainers; [ ulysseszhan ];
    homepage = "https://everestapi.github.io";
    platforms = [ "x86_64-linux" ];
    sourceProvenance = with lib.sourceTypes; [
      binaryNativeCode
      fromSource
    ];
  };
}
+1037 −0

File added.

Preview size limit exceeded, changes collapsed.

+222 −0
Original line number Diff line number Diff line
{
  lib,
  callPackage,
  buildFHSEnv,
  fetchzip,
  makeDesktopItem,
  writeShellScript,
  autoPatchelfHook,
  runtimeShell,

  overrideSrc ? null,
  # A package. Omit to build without Everest.
  everest ? null,
  # If build with Everest, must set writableDir to the path of a writable dir
  # so that the mods can be installed there.
  # It must be an absolute path.
  # Example: "/home/kat/.local/share/Everest"
  writableDir ? null,
  # Optionally set paths of symlinks to the installation dir of Celeste.
  # You can use this in Olympus so that you don't have to change installation dir path
  # every time the nix store path changes.
  # The links are updated every time the command `Celeste` is run.
  gameDir ? [ ],
  # This will be appended to everest-launch.txt.
  launchFlags ? "",
  # This will be appended to everest-env.txt.
  launchEnv ? "",
}:

# For those who would like to use steam-run or alike to launch Celeste
# (useful when using the `olympus` package with its `celesteWrapper` argument overridden),
# install `celestegame.passthru.celeste-unwrapped` instead of `celestegame`, and if you want Everest,
# override `everest` to `celestegame.passthru.everest-bin` instead of `celestegame.passthru.everest`
# (steam-run cannot launch the latter for some currently unclear reason).
# For those who would like to launch Celeste without the need of any additional wrapper like steam-run,
# install `celestegame` with the `writableDir` argument overridden.

let
  pname = "celeste";
  phome = "$out/${celesteHomeRelative}";
  executableName = "Celeste";

  writableDir' =
    if writableDir == null && everest != null then
      lib.warn "writableDir is not set, so mods will not work." "/tmp"
    else
      writableDir;
  gameDir' = lib.toList gameDir;

  everestLogFilename = "everest-log.txt";

  celeste = callPackage ./celeste {
    inherit
      executableName
      everest
      overrideSrc
      launchFlags
      launchEnv
      everestLogFilename
      ;
    desktopItems = [ desktopItem ];
    writableDir = writableDir';
  };
  celesteHomeRelative = "lib/Celeste";
  celesteHome = "${celeste}/${celesteHomeRelative}";

  desktopItem = makeDesktopItem {
    name = "Celeste";
    desktopName = "Celeste";
    genericName = "Celeste";
    comment = celeste.meta.description;
    exec = executableName;
    icon = "Celeste";
    categories = [ "Game" ];
  };

in
buildFHSEnv {
  inherit pname executableName;
  version = celeste.version + (lib.optionalString (everest != null) "+everest.${everest.version}");

  multiPkgs =
    pkgs:
    with pkgs;
    [
      glib
      glibc_multi
      kdePackages.wayland
      libxkbcommon
      libgcc
      mesa
      libdrm
      expat
      alsa-lib
      at-spi2-atk
      libGL
      pcre2
      libffi
      zlib
      util-linux.lib
      libselinux
      nspr
      systemd
      gtk3
      pango
      harfbuzz
      fontconfig
      fribidi
      cairo
      libepoxy
      tinysparql
      libthai
      libpng
      freetype
      pixman
      libcap
      graphite2
      bzip2
      brotli
      libjpeg
      json-glib
      libxml2
      sqlite
      libdatrie
      ffmpeg
      nss
      dbus.lib
      acl
      attr
      gmp
      readline
      libpulseaudio
      pipewire
      vulkan-loader
    ]
    ++ (with xorg; [
      libX11
      libXcomposite
      libXdamage
      libXfixes
      libXext
      libxcb
      libXcursor
      libXinerama
      libXi
      libXrandr
      libXScrnSaver
      libXxf86vm
      libXau
      libXdmcp
    ]);

  targetPkgs = pkgs: [ celeste ];

  extraInstallCommands = ''
    icon=$out/share/icons/hicolor/512x512/apps/Celeste.png
    mkdir -p $(dirname $icon)
    ln -s ${celesteHome}/Celeste.png $icon
    cp -r ${desktopItem}/* $out
  '';

  extraPreBwrapCmds = ''
    export NIX_CELESTE_LAUNCHER=$(realpath --no-symlinks $0)
  '';

  runScript = writeShellScript executableName (
    lib.optionalString (writableDir' != null) ''
      mkdir -p "${writableDir'}"
      touch "${writableDir'}/log.txt"

      # This script is symlinked to gameDir/Celeste, which gets launched by Olympus
      # (if the user set up Olympus to use gameDir as the location of Celeste).
      # Writing the script like this makes Olympus able to launch Celeste wihout any wrapper without any problems.
      echo "#! ${runtimeShell}
      exec $NIX_CELESTE_LAUNCHER"' "$@"' > "${writableDir'}/Celeste"
      chmod +x "${writableDir'}/Celeste"
    ''
    + lib.optionalString (everest != null) ''
      mkdir -p "${writableDir'}"/{LogHistory,Mods,CrashLogs}
      touch "${writableDir'}/${everestLogFilename}"

      # Needed to prevent restarting; see comments in postInstall of ./celeste/default.nix.
      export LD_LIBRARY_PATH="${celesteHome}/lib64-linux:$LD_LIBRARY_PATH"
    ''
    + lib.optionalString (gameDir' != [ ]) (
      lib.concatMapStrings (link: ''
        mkdir -p "$(dirname "${link}")"
        if [ -L "${link}" ]; then
          if [ ${celesteHome} != "$(readlink "${link}")" ]; then
            rm "${link}"
            ln -s ${celesteHome} "${link}"
          fi
        else
          rm -r "${link}"
          ln -s ${celesteHome} "${link}"
        fi
      '') gameDir'
    )
    + ''
      cd /${celesteHomeRelative}
      exec ./Celeste-unwrapped "$@"
    ''
  );

  passthru.celeste-unwrapped = celeste;
  passthru.everest = callPackage ./everest { };
  passthru.everest-bin = callPackage ./everest-bin { };

  passthru.updateScript = ./update.sh;

  meta = {
    inherit (celeste.meta)
      homepage
      downloadPage
      description
      license
      sourceProvenance
      platforms
      ;
    maintainers = with lib.maintainers; [ ulysseszhan ];
  };
}
Loading