From c9dfe485e9b922b35c9412e6c4fdbe119f17f4b5 Mon Sep 17 00:00:00 2001 From: aszlig Date: Mon, 16 Jul 2018 07:09:38 +0200 Subject: 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 --- pkgs/games/build-support/default.nix | 1 + .../build-support/monogame-patcher/default.nix | 31 ++++++ .../build-support/monogame-patcher/patcher.cs | 109 +++++++++++++++++++++ pkgs/games/itch/towerfall-ascension.nix | 100 ++----------------- 4 files changed, 147 insertions(+), 94 deletions(-) create mode 100644 pkgs/games/build-support/monogame-patcher/default.nix create mode 100644 pkgs/games/build-support/monogame-patcher/patcher.cs 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 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(args) + .WithParsed(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 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() { - "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"; -- cgit 1.4.1