using Mono.Cecil; using Mono.Cecil.Cil; using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace IPA.Patcher { internal class PatchedModule { private static readonly string[] EntryTypes = { "Input", "Display" }; private readonly FileInfo _file; private ModuleDefinition _module; internal struct PatchData { public bool IsPatched; public Version Version; } public static PatchedModule Load(string engineFile) { return new PatchedModule(engineFile); } private PatchedModule(string engineFile) { _file = new FileInfo(engineFile); LoadModules(); } private void LoadModules() { var resolver = new DefaultAssemblyResolver(); resolver.AddSearchDirectory(_file.DirectoryName); var parameters = new ReaderParameters { AssemblyResolver = resolver, }; _module = ModuleDefinition.ReadModule(_file.FullName, parameters); } public PatchData Data { get { var data = new PatchData { IsPatched = false, Version = null }; foreach (var @ref in _module.AssemblyReferences) { switch (@ref.Name) { case "IllusionInjector": case "IllusionPlugin": data = new PatchData { IsPatched = true, Version = new Version(0, 0, 0, 0) }; break; case "IPA.Injector": return new PatchData { IsPatched = true, Version = @ref.Version }; } } return data; } } public void Patch(Version v) { // First, let's add the reference var nameReference = new AssemblyNameReference("IPA.Injector", v); var injectorPath = Path.Combine(_file.DirectoryName ?? throw new InvalidOperationException(), "IPA.Injector.dll"); var injector = ModuleDefinition.ReadModule(injectorPath); bool hasIPAInjector = false; for (int i = 0; i < _module.AssemblyReferences.Count; i++) { if (_module.AssemblyReferences[i].Name == "IllusionInjector") _module.AssemblyReferences.RemoveAt(i--); if (_module.AssemblyReferences[i].Name == "IllusionPlugin") _module.AssemblyReferences.RemoveAt(i--); if (_module.AssemblyReferences[i].Name == "IPA.Injector") { hasIPAInjector = true; _module.AssemblyReferences[i].Version = v; } } if (!hasIPAInjector) { _module.AssemblyReferences.Add(nameReference); int patched = 0; foreach (var type in FindEntryTypes()) { if (PatchType(type, injector)) { patched++; } } if (patched > 0) { _module.Write(_file.FullName); } else { throw new Exception("Could not find any entry type!"); } } else { _module.Write(_file.FullName); } } private bool PatchType(TypeDefinition targetType, ModuleDefinition injector) { var targetMethod = targetType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic); if (targetMethod != null) { var methodReference = _module.ImportReference(injector.GetType("IPA.Injector.Injector").Methods.First(m => m.Name == "Inject")); targetMethod.Body.Instructions.Insert(0, Instruction.Create(OpCodes.Call, methodReference)); return true; } return false; } private IEnumerable FindEntryTypes() { return _module.GetTypes().Where(m => EntryTypes.Contains(m.Name)); } } }