about summary refs log tree commit diff
path: root/pkgs/games/itch
diff options
context:
space:
mode:
authoraszlig <aszlig@nix.build>2018-07-01 05:32:49 +0200
committeraszlig <aszlig@nix.build>2018-07-01 05:44:35 +0200
commit72abbc69b13dcf40bac429147dc18a8b8c8bae7b (patch)
treeca0a5b4a1f1eb10a2af4e4207d1e81fb0e095032 /pkgs/games/itch
parentf96adee8203a4001c6d9f4e2522e3ba4b4cba6ff (diff)
games/itch: Add Towerfall Ascension
Packaging is a bit similar to what we had to do back then with Opus
Magnum (see commit 523dcef1f77251a4cfeb3428a0b13c0ec1d9c342) where some
game data files were tried to open in read-write mode.

I'm using the same patcher (which is using Mono.Cecil) that I have used
for Opus Magnum back then but I've cleaned it up a bit.

In the long term I'd try to make the patcher a bit more generic so that
it can be used for other Mono-based games, because opening game data
files in read-only mode seems to be fairly common among a few other
games I haven't fully packaged yet.

The game also tries to execute "xdg-open error_log.txt" in the event a
uncaught exception occurs, which then fails because no xdg-open is
available. So instead, I made a small dummy wrapper which just runs
"head" on the error.log instead.

Another thing which unfortunately is currently not working is the
editor, because it tries to create a directory ("Workshop") within the
game's content directory, which - again - doesn't work within the
read-only Nix store.

Signed-off-by: aszlig <aszlig@nix.build>
Diffstat (limited to 'pkgs/games/itch')
-rw-r--r--pkgs/games/itch/default.nix1
-rw-r--r--pkgs/games/itch/towerfall-ascension.nix178
2 files changed, 179 insertions, 0 deletions
diff --git a/pkgs/games/itch/default.nix b/pkgs/games/itch/default.nix
index 34ffc6ed..cae4b99f 100644
--- a/pkgs/games/itch/default.nix
+++ b/pkgs/games/itch/default.nix
@@ -12,6 +12,7 @@ let
     };
 
     invisigun-heroes = callPackage ./invisigun-heroes.nix {};
+    towerfall-ascension = callPackage ./towerfall-ascension.nix {};
   };
 in {
   options.itch.apiKey = lib.mkOption {
diff --git a/pkgs/games/itch/towerfall-ascension.nix b/pkgs/games/itch/towerfall-ascension.nix
new file mode 100644
index 00000000..a4ba5abf
--- /dev/null
+++ b/pkgs/games/itch/towerfall-ascension.nix
@@ -0,0 +1,178 @@
+{ stdenv, lib, buildGame, fetchItch, makeWrapper, p7zip, unzip, mono50
+, SDL2, SDL2_image, libGL, libvorbis, openal
+
+, writeScriptBin, coreutils
+, fetchNuGet, writeText
+
+, darkWorldExpansion ? true
+}:
+
+let
+  cecil = fetchNuGet {
+    baseName = "Mono.Cecil";
+    version = "0.10-beta7";
+    sha256 = "03bina3llcnylrfrvp5psnwrfn757j7zch5r360rpdn7gmcjjcpl";
+    outputFiles = [ "lib/net40/*" ];
+  };
+
+  # We need to patch a few occurences of System.IO.FileStream which are used
+  # with the default arguments for FileAccess. The defaults open the file in
+  # read-write mode and given that all the game data files are in the read-only
+  # Nix store, we'd get an UnauthorizedAccessException.
+  patcher = writeText "patcher.cs" ''
+    using System.Linq;
+    using System.Collections.Generic;
+    using Mono.Cecil;
+    using Mono.Cecil.Cil;
+    using Mono.Cecil.Rocks;
+
+    public class patcher {
+      private ModuleDefinition module;
+
+      private patcher(List<string> typesToPatch) {
+        this.module = ModuleDefinition.ReadModule("TowerFall.exe");
+
+        var filtered = this.module.Types
+          .Where(p => typesToPatch.Contains(p.Name));
+
+        foreach (var toPatch in filtered) {
+          patch_class(toPatch);
+        }
+
+        this.module.Write("TowerFallPatched.exe");
+      }
+
+      private void patch_method(MethodDefinition md) {
+        var il = md.Body.GetILProcessor();
+
+        var fileStreams = md.Body.Instructions
+          .Where(i => i.OpCode == OpCodes.Newobj)
+          .Where(i => (i.Operand as MethodReference).DeclaringType
+                      .FullName == "System.IO.FileStream");
+
+        foreach (Instruction i in fileStreams.ToList()) {
+          var fileAccessRead = il.Create(OpCodes.Ldc_I4_1);
+          il.InsertBefore(i, fileAccessRead);
+
+          var ctorType = this.module.AssemblyReferences.Select(
+            x => new {
+              type = this.module.AssemblyResolver.Resolve(x)
+                .MainModule.GetType("System.IO.FileStream")
+            }
+          ).Where(x => x.type != null).Select(x => x.type).First();
+
+          string wantedCtor = "System.Void System.IO.FileStream"
+                            + "::.ctor(System.String,"
+                            + "System.IO.FileMode,"
+                            + "System.IO.FileAccess)";
+
+          var newCtor = ctorType.GetConstructors()
+            .Single(x => x.ToString() == wantedCtor);
+
+          var refCtor = this.module.ImportReference(newCtor);
+          il.Replace(i, il.Create(OpCodes.Newobj, refCtor));
+        }
+      }
+
+      private void patch_class(TypeDefinition td) {
+        foreach (var nested in td.NestedTypes) patch_class(nested);
+        foreach (MethodDefinition md in td.Methods) patch_method(md);
+      }
+
+      public static void Main() {
+        var typesToPatch = new List<string>() {
+          "Texture", "IntroScene", "SFX", "SFXVaried"
+        };
+        new patcher(typesToPatch);
+      }
+    }
+  '';
+
+in buildGame rec {
+  name = "towerfall-ascension-${version}";
+  version = "20160723";
+
+  srcs = lib.singleton (fetchItch {
+    name = "${name}.bin";
+    gameId = 22755;
+    uploadId = 243755;
+    sha256 = "01ipq3z0c2k4h88r7j17nfp43p5sav12a9syangqm0syflvwqxb6";
+  }) ++ lib.optional darkWorldExpansion (fetchItch {
+    name = "towerfall-darkworld.zip";
+    gameId = 24962;
+    uploadId = 216070;
+    sha256 = "1nb26m2l74rsnlwv9mv33l2s5n873867k9zypc84sm3iljvrdkmg";
+  });
+
+  unpackCmd = ''
+    case "$curSrc" in
+      *.bin) ${p7zip}/bin/7z x "$curSrc" data;;
+      *.zip) ${unzip}/bin/unzip -qq "$curSrc" -d data;;
+      *) false;;
+    esac
+  '';
+
+  patchPhase = ''
+    { export MONO_PATH=${cecil}/lib/dotnet/Mono.Cecil
+      mcs ${lib.escapeShellArg patcher} -out:patcher \
+        -lib:${cecil}/lib/dotnet/Mono.Cecil \
+        -r:Mono.Cecil -r:Mono.Cecil.Rocks
+      mono patcher
+      mv TowerFallPatched.exe TowerFall.exe
+    }
+  '';
+
+  nativeBuildInputs = [ makeWrapper mono50 ];
+
+  libdir = if stdenv.system == "x86_64-linux" then "lib64" else "lib";
+
+  buildPhase = let
+    dllmap = {
+      SDL2 = "${SDL2}/lib/libSDL2.so";
+      SDL2_image = "${SDL2_image}/lib/libSDL2_image.so";
+      soft_oal = "${openal}/lib/libopenal.so";
+      libvorbisfile-3 = "${libvorbis}/lib/libvorbisfile.so";
+      MojoShader = "$out/libexec/towerfall-ascension/libmojoshader.so";
+    };
+  in lib.concatStrings (lib.mapAttrsToList (dll: target: ''
+    sed -i -e '/<dllmap.*dll="${dll}\.dll".*os="linux"/ {
+      s!target="[^"]*"!target="'"${target}"'"!
+    }' FNA.dll.config
+  '') dllmap);
+
+  dummyXdgOpen = writeScriptBin "xdg-open" ''
+    #!${stdenv.shell} -e
+    if [ "''${1##*.}" = txt ]; then
+      exec ${coreutils}/bin/head -v -n 20 "$1"
+    else
+      echo "Unable to open file $1" >&2
+      exit 1
+    fi
+  '';
+
+  installPhase = ''
+    mkdir -p "$out/bin" \
+             "$out/share/towerfall-ascension" \
+             "$out/libexec/towerfall-ascension"
+    cp -rvt "$out/share/towerfall-ascension" Content
+    cp -rv mono/config "$out/libexec/towerfall-ascension/TowerFall.exe.config"
+    cp -rvt "$out/libexec/towerfall-ascension" TowerFall.exe FNA.dll* \
+      "$libdir/libmojoshader.so"
+    ln -s "$out/share/towerfall-ascension/Content" \
+          "$out/libexec/towerfall-ascension/Content"
+
+    if [ -e "TowerFall Dark World Expansion" ]; then
+      cp -rvt "$out/share/towerfall-ascension" \
+        "TowerFall Dark World Expansion/DarkWorldContent"
+    fi
+
+    makeWrapper ${lib.escapeShellArg mono50}/bin/mono \
+      "$out/bin/towerfall-ascension" \
+      --set SDL_OPENGL_LIBRARY ${lib.escapeShellArg "${libGL}/lib/libGL.so"} \
+      --set PATH "$dummyXdgOpen/bin" \
+      --add-flags "$out/libexec/towerfall-ascension/TowerFall.exe" \
+      --run "cd '$out/share/towerfall-ascension'"
+  '';
+
+  sandbox.paths.required = [ "$XDG_DATA_HOME/TowerFall" ];
+}