about summary refs log tree commit diff
path: root/pkgs/games
diff options
context:
space:
mode:
authoraszlig <aszlig@nix.build>2018-07-16 07:09:38 +0200
committeraszlig <aszlig@nix.build>2018-07-19 06:35:06 +0200
commitc9dfe485e9b922b35c9412e6c4fdbe119f17f4b5 (patch)
tree96180ef3647607a6eb764bfe2aa4c48d0fe0999e /pkgs/games
parentbc0be07dd9920c8dfc16be8f5bdb269bc9e3f986 (diff)
towerfall-ascension: Move patcher into own pkg
The patcher using Cecil is now in its own derivation and can thus be
easily added via nativeBuildInputs. Patching FileStream types is so
common that it comes in handy for other games using Mono.

I also improved the patcher a little bit so it accepts command line
arguments and it's easier to add the types that needed to be patched
directly via command line arguments.

Signed-off-by: aszlig <aszlig@nix.build>
Diffstat (limited to 'pkgs/games')
-rw-r--r--pkgs/games/build-support/default.nix1
-rw-r--r--pkgs/games/build-support/monogame-patcher/default.nix31
-rw-r--r--pkgs/games/build-support/monogame-patcher/patcher.cs109
-rw-r--r--pkgs/games/itch/towerfall-ascension.nix100
4 files changed, 147 insertions, 94 deletions
diff --git a/pkgs/games/build-support/default.nix b/pkgs/games/build-support/default.nix
index 8e227afc..1832f987 100644
--- a/pkgs/games/build-support/default.nix
+++ b/pkgs/games/build-support/default.nix
@@ -5,4 +5,5 @@
     withPulseAudio = config.pulseaudio or true;
   };
   buildUnity = callPackage ./build-unity.nix {};
+  monogamePatcher = callPackage ./monogame-patcher {};
 }
diff --git a/pkgs/games/build-support/monogame-patcher/default.nix b/pkgs/games/build-support/monogame-patcher/default.nix
new file mode 100644
index 00000000..fffa6825
--- /dev/null
+++ b/pkgs/games/build-support/monogame-patcher/default.nix
@@ -0,0 +1,31 @@
+{ lib, runCommand, makeWrapper, fetchNuGet, mono, dotnetPackages }:
+
+runCommand "monogame-patcher" {
+  nativeBuildInputs = [ mono makeWrapper ];
+
+  src = ./patcher.cs;
+
+  cecil = "${fetchNuGet {
+    baseName = "Mono.Cecil";
+    version = "0.10-beta7";
+    sha256 = "03bina3llcnylrfrvp5psnwrfn757j7zch5r360rpdn7gmcjjcpl";
+    outputFiles = [ "lib/net40/*" ];
+  }}/lib/dotnet/Mono.Cecil";
+
+  cliparser = "${fetchNuGet {
+    baseName = "CommandLineParser";
+    version = "2.2.1";
+    sha256 = "0wf8mzr16d2ni008m60rrk738v8ypk74llk6g8mlyx7rrlchnxaf";
+    outputFiles = [ "lib/net45/*" ];
+  }}/lib/dotnet/CommandLineParser";
+
+} ''
+  mkdir -p "$out/bin" "$out/libexec/monogame-patcher"
+  mcs "$src" -out:"$out/libexec/monogame-patcher/patcher.exe" \
+    -lib:"$cecil" -lib:"$cliparser" \
+    -r:Mono.Cecil -r:Mono.Cecil.Rocks -r:CommandLine
+
+  makeWrapper ${lib.escapeShellArg "${mono}/bin/mono"} "$out/bin/$name" \
+    --set MONO_PATH "$cecil:$cliparser" \
+    --add-flags "$out/libexec/monogame-patcher/patcher.exe"
+''
diff --git a/pkgs/games/build-support/monogame-patcher/patcher.cs b/pkgs/games/build-support/monogame-patcher/patcher.cs
new file mode 100644
index 00000000..16878831
--- /dev/null
+++ b/pkgs/games/build-support/monogame-patcher/patcher.cs
@@ -0,0 +1,109 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System;
+
+using Mono.Cecil.Cil;
+using Mono.Cecil.Rocks;
+using Mono.Cecil;
+
+using CommandLine;
+
+class GenericOptions
+{
+    [Option('i', "infile", Required=true, HelpText="Input file to transform.")]
+    public string inputFile { get; set; }
+    [Option('o', "outfile", HelpText="File to write transformed data to.")]
+    public string outputFile { get; set; }
+}
+
+class Command {
+    protected string infile;
+    protected string outfile;
+    protected ModuleDefinition module;
+
+    public Command(GenericOptions options) {
+        if (options.outputFile == null)
+            this.outfile = options.inputFile;
+        else
+            this.outfile = options.outputFile;
+        this.infile = options.inputFile;
+
+        var rp = new ReaderParameters {
+            ReadWrite = this.infile == this.outfile
+        };
+        this.module = ModuleDefinition.ReadModule(this.infile, rp);
+    }
+
+    public void save() {
+        if (this.outfile == this.infile)
+            this.module.Write();
+        else
+            this.module.Write(this.outfile);
+    }
+}
+
+[Verb("fix-filestreams", HelpText="Fix System.IO.FileStream constructors"
+                                 +" to open files read-only.")]
+class FixFileStreamsCmd : GenericOptions {
+    [Value(0, Required=true, MetaName = "type", HelpText = "Types to patch.")]
+    public IEnumerable<string> typesToPatch { get; set; }
+};
+
+class FixFileStreams : Command {
+    public FixFileStreams(FixFileStreamsCmd options) : base(options) {
+        var filtered = this.module.Types
+            .Where(p => options.typesToPatch.Contains(p.Name));
+
+        foreach (var toPatch in filtered)
+            patch_type(toPatch);
+
+        this.save();
+    }
+
+    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_type(TypeDefinition td) {
+        foreach (var nested in td.NestedTypes) patch_type(nested);
+        foreach (MethodDefinition md in td.Methods) patch_method(md);
+    }
+}
+
+public class patcher {
+
+    public static int Main(string[] args) {
+        Parser.Default.ParseArguments<GenericOptions, FixFileStreamsCmd>(args)
+            .WithParsed<FixFileStreamsCmd>(opts => new FixFileStreams(opts));
+        return 0;
+    }
+}
diff --git a/pkgs/games/itch/towerfall-ascension.nix b/pkgs/games/itch/towerfall-ascension.nix
index a4ba5abf..21704211 100644
--- a/pkgs/games/itch/towerfall-ascension.nix
+++ b/pkgs/games/itch/towerfall-ascension.nix
@@ -1,94 +1,11 @@
 { stdenv, lib, buildGame, fetchItch, makeWrapper, p7zip, unzip, mono50
-, SDL2, SDL2_image, libGL, libvorbis, openal
-
-, writeScriptBin, coreutils
-, fetchNuGet, writeText
+, SDL2, SDL2_image, libGL, libvorbis, openal, monogamePatcher, writeScriptBin
+, coreutils
 
 , 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 {
+buildGame rec {
   name = "towerfall-ascension-${version}";
   version = "20160723";
 
@@ -113,16 +30,11 @@ in buildGame rec {
   '';
 
   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
-    }
+    monogame-patcher fix-filestreams -i TowerFall.exe \
+      Texture IntroScene SFX SFXVaried
   '';
 
-  nativeBuildInputs = [ makeWrapper mono50 ];
+  nativeBuildInputs = [ makeWrapper mono50 monogamePatcher ];
 
   libdir = if stdenv.system == "x86_64-linux" then "lib64" else "lib";