about summary refs log tree commit diff
path: root/pkgs/games/humblebundle
diff options
context:
space:
mode:
authoraszlig <aszlig@nix.build>2017-12-13 22:39:48 +0100
committeraszlig <aszlig@nix.build>2017-12-13 22:39:48 +0100
commit523dcef1f77251a4cfeb3428a0b13c0ec1d9c342 (patch)
treeceb0a89a24a4931cd3cc5d427c58f6689f335295 /pkgs/games/humblebundle
parentdfd3d86562f09d812b330893cec053ab3d371bdf (diff)
games/humblebundle: Add Opus Magnum
Upstream URL: http://www.zachtronics.com/opus-magnum/

For this I've ripped apart all of the bundled shared objects including
Mono itself, so we're using Mono from <nixpkgs> instead of the provided
one.

In addition to that I had to patch the intermediate language because the
game really doesn't cope very well when the data files are read-only,
which is the case in the Nix store.

Instead of replacing the CIL, I could have used an LD preloader as well,
but I think this is way less error-prone even though we had to patch a
few internal classes where we couldn't match the name properly.

Compared to this an LD preloader would involve patching several symbols
and also implement some logic to distinguish between files read from the
data directory and files read/written to XDG_DATA_HOME.

The reason why I added $HOME/Desktop to the required sandbox paths is
because the game saves gif images into that directory.

Signed-off-by: aszlig <aszlig@nix.build>
Diffstat (limited to 'pkgs/games/humblebundle')
-rw-r--r--pkgs/games/humblebundle/default.nix1
-rw-r--r--pkgs/games/humblebundle/opus-magnum.nix118
2 files changed, 119 insertions, 0 deletions
diff --git a/pkgs/games/humblebundle/default.nix b/pkgs/games/humblebundle/default.nix
index cbffeaa0..3ed0b3b6 100644
--- a/pkgs/games/humblebundle/default.nix
+++ b/pkgs/games/humblebundle/default.nix
@@ -24,6 +24,7 @@ let
     jamestown = callPackage ./jamestown.nix {};
     liads = callPackage ./liads.nix {};
     megabytepunch = callPackage_i686 ./megabytepunch.nix {};
+    opus-magnum = callPackage ./opus-magnum.nix {};
     pico-8 = callPackage ./pico-8.nix {};
     rocketbirds = callPackage ./rocketbirds.nix {};
     spaz = callPackage ./spaz.nix {};
diff --git a/pkgs/games/humblebundle/opus-magnum.nix b/pkgs/games/humblebundle/opus-magnum.nix
new file mode 100644
index 00000000..9b6d27cd
--- /dev/null
+++ b/pkgs/games/humblebundle/opus-magnum.nix
@@ -0,0 +1,118 @@
+{ stdenv, lib, buildGame, fetchHumbleBundle, makeWrapper, mono50
+, SDL2, SDL2_image, SDL2_mixer, libvorbis
+
+, fetchNuGet, writeText
+}:
+
+let
+  cecil = fetchNuGet {
+    baseName = "Mono.Cecil";
+    version = "0.10-beta7";
+    sha256 = "03bina3llcnylrfrvp5psnwrfn757j7zch5r360rpdn7gmcjjcpl";
+    outputFiles = [ "lib/net40/*" ];
+  };
+
+  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 {
+      public static void Main() {
+        var module = ModuleDefinition.ReadModule("Lightning.exe");
+        var typesToPatch = new List<string>() {
+          "#=qpBcj_0KQZcJ0ffkrxQcNZQ==",
+          "#=qEMEvBoEQXql_zRuLLk1x6w==",
+          "#=qZzmwsUfWWbRLjboUcCgm9sc9s0f6yVsIVJ2gWSL_c4g=",
+          "PsdImage"
+        };
+
+        var filtered = module.Types.Where(p => typesToPatch.Contains(p.Name));
+        foreach (var toPatch in filtered) {
+          foreach (MethodDefinition md in toPatch.Methods) {
+            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 = module.AssemblyReferences.Select(
+                x => new {
+                  type = 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 = module.ImportReference(newCtor);
+              il.Replace(i, il.Create(OpCodes.Newobj, refCtor));
+            }
+          }
+        }
+        module.Write("LightningPatched.exe");
+      }
+    }
+  '';
+
+in buildGame rec {
+  name = "opus-magnum-${version}";
+  version = "1";
+
+  src = fetchHumbleBundle {
+    machineName = "opus_magnum_4vffp_linux_Z9zWf";
+    suffix = "zip";
+    md5 = "aefe8aae8cd3dd3915be8d8cd6f3705d";
+  };
+
+  arch = if stdenv.system == "x86_64-linux" then "x86_64" else "x86";
+
+  nativeBuildInputs = [ makeWrapper mono50 cecil ];
+
+  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 LightningPatched.exe Lightning.exe
+    }
+  '';
+
+  buildPhase = let
+    dllmap = {
+      SDL2 = "${SDL2}/lib/libSDL2.so";
+      SDL2_image = "${SDL2_image}/lib/libSDL2_image.so";
+      SDL2_mixer = "${SDL2_mixer}/lib/libSDL2_mixer.so";
+      libvorbisfile-3 = "${libvorbis}/lib/libvorbisfile.so";
+    };
+  in lib.concatStrings (lib.mapAttrsToList (dll: target: ''
+    sed -i -e '/<dllmap.*dll="${dll}\.dll".*os="linux"/ {
+      s!target="[^"]*"!target="${target}"!
+    }' Lightning.exe.config
+  '') dllmap);
+
+  installPhase = ''
+    mkdir -p "$out/bin" "$out/share/opus-magnum" "$out/libexec/opus-magnum"
+    cp -rvt "$out/share/opus-magnum" Content PackedContent
+    cp -rvt "$out/libexec/opus-magnum" Lightning.exe* Ionic.Zip.Reduced.dll
+
+    makeWrapper ${lib.escapeShellArg mono50}/bin/mono "$out/bin/opus-magnum" \
+      --add-flags "$out/libexec/opus-magnum/Lightning.exe" \
+      --run "cd '$out/share/opus-magnum'"
+  '';
+
+  sandbox.paths.required = [ "$XDG_DATA_HOME/Opus Magnum" "$HOME/Desktop" ];
+}