From 3917a5e3c4b7784bf535eafb25e0a47662c0978a Mon Sep 17 00:00:00 2001 From: aszlig Date: Tue, 17 Jul 2018 02:59:32 +0200 Subject: monogame-patcher: Switch to MSBuild I really would have preferred a simple Makefile, but with that we can't use buildDotnetPackage and we also need to take care of butchering the dependencies manually. So I moved everything to src/ and added a csproj file to clean up most of the cruft and just use buildDotnetPackage with minimal attributes. In addition to that I also added assembly info, so that the command line help will show the proper author name, copyright, yaddayadda... Signed-off-by: aszlig --- .../build-support/monogame-patcher/src/patcher.cs | 168 +++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 pkgs/games/build-support/monogame-patcher/src/patcher.cs (limited to 'pkgs/games/build-support/monogame-patcher/src/patcher.cs') diff --git a/pkgs/games/build-support/monogame-patcher/src/patcher.cs b/pkgs/games/build-support/monogame-patcher/src/patcher.cs new file mode 100644 index 00000000..c1502ba1 --- /dev/null +++ b/pkgs/games/build-support/monogame-patcher/src/patcher.cs @@ -0,0 +1,168 @@ +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); + } +} + +[Verb("replace-call", HelpText="Replace calls to types.")] +class ReplaceCallCmd : GenericOptions { + [Value(0, Min=2, Max=2, HelpText="Call to replace.")] + public IEnumerable replaceCall { get; set; } + + [Value(2, Required=true, MetaName = "type", HelpText = "Types to patch.")] + public IEnumerable typesToPatch { get; set; } + +}; + +class ReplaceCall : Command { + private string search; + private string replace; + + public ReplaceCall(ReplaceCallCmd options) : base(options) { + this.search = options.replaceCall.ToList()[0]; + this.replace = options.replaceCall.ToList()[1]; + + 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 found = md.Body.Instructions + .Where(i => i.OpCode == OpCodes.Call) + .Where(i => i.Operand.ToString() == this.search); + + var ctorType = this.module.AssemblyReferences.Select( + x => new { + // XXX: Don't hardcode UnityEngine.Application! + type = this.module.AssemblyResolver.Resolve(x) + .MainModule.GetType("UnityEngine.Application") + } + ).Where(x => x.type != null).Select(x => x.type).First(); + + var newMethod = ctorType.GetMethods() + .Single(x => x.ToString() == this.replace); + var refMethod = this.module.ImportReference(newMethod); + + foreach (Instruction i in found.ToList()) + il.Replace(i, il.Create(OpCodes.Call, refMethod)); + } + + 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, ReplaceCallCmd + >(args) + .WithParsed(opts => new FixFileStreams(opts)) + .WithParsed(opts => new ReplaceCall(opts)); + return 0; + } +} -- cgit 1.4.1