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 +++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 pkgs/games/build-support/monogame-patcher/default.nix create mode 100644 pkgs/games/build-support/monogame-patcher/patcher.cs (limited to 'pkgs/games/build-support') 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; + } +} -- cgit 1.4.1