using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Mdb; using Mono.Cecil.Pdb; using Mono.Collections.Generic; using MonoMod.InlineRT; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using MonoMod.Utils; #if NETSTANDARD using static System.Reflection.IntrospectionExtensions; using static System.Reflection.TypeExtensions; #endif namespace MonoMod { public delegate bool MethodParser(MonoModder modder, MethodBody body, Instruction instr, ref int instri); public delegate void MethodRewriter(MonoModder modder, MethodDefinition method); public delegate void MethodBodyRewriter(MonoModder modder, MethodBody body, Instruction instr, int instri); public delegate ModuleDefinition MissingDependencyResolver(MonoModder modder, ModuleDefinition main, string name, string fullName); public delegate void PostProcessor(MonoModder modder); public delegate void ModReadEventHandler(MonoModder modder, ModuleDefinition mod); public class RelinkMapEntry { public string Type; public string FindableID; public RelinkMapEntry() { } public RelinkMapEntry(string type, string findableID) { Type = type; FindableID = findableID; } } public enum DebugSymbolFormat { Auto, MDB, PDB } public class MonoModder : IDisposable { public static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; public static readonly Version Version = typeof(MonoModder).GetTypeInfo().Assembly.GetName().Version; // WasIDictionary and the _ IDictionaries are used when upgrading mods. [MonoMod__WasIDictionary__] public Dictionary RelinkMap = new Dictionary(); public IDictionary _RelinkMap { get => RelinkMap; set => RelinkMap = (Dictionary) value; } [MonoMod__WasIDictionary__] public Dictionary RelinkModuleMap = new Dictionary(); public IDictionary _RelinkModuleMap { get => RelinkModuleMap; set => RelinkModuleMap = (Dictionary) value; } public HashSet SkipList = new HashSet(EqualityComparer.Default); [MonoMod__WasIDictionary__] public Dictionary RelinkMapCache = new Dictionary(); public IDictionary _RelinkMapCache { get => RelinkMapCache; set => RelinkMapCache = (Dictionary) value; } [MonoMod__WasIDictionary__] public Dictionary RelinkModuleMapCache = new Dictionary(); public IDictionary _RelinkModuleMapCache { get => RelinkModuleMapCache; set => RelinkModuleMapCache = (Dictionary) value; } public Dictionary ForceCallMap = new Dictionary(); public ModReadEventHandler OnReadMod; public PostProcessor PostProcessors; [MonoMod__WasIDictionary__] public Dictionary CustomAttributeHandlers = new Dictionary(); public IDictionary _CustomAttributeHandlers { get => CustomAttributeHandlers; set => CustomAttributeHandlers = (Dictionary) value; } [MonoMod__WasIDictionary__] public Dictionary CustomMethodAttributeHandlers = new Dictionary(); public IDictionary _CustomMethodAttributeHandlers { get => CustomMethodAttributeHandlers; set => CustomMethodAttributeHandlers = (Dictionary) value; } public MissingDependencyResolver MissingDependencyResolver; public MethodParser MethodParser; public MethodRewriter MethodRewriter; public MethodBodyRewriter MethodBodyRewriter; public Stream Input; public string InputPath; public Stream Output; public string OutputPath; public List DependencyDirs = new List(); public ModuleDefinition Module; [MonoMod__WasIDictionary__] public Dictionary> DependencyMap = new Dictionary>(); public IDictionary> _DependencyMap { get => DependencyMap; set => DependencyMap = (Dictionary>) value; } [MonoMod__WasIDictionary__] public Dictionary DependencyCache = new Dictionary(); public IDictionary _DependencyCache { get => DependencyCache; set => DependencyCache = (Dictionary) value; } public Func ShouldCleanupAttrib; public bool LogVerboseEnabled; public bool RelinkerCacheEnabled; public bool CleanupEnabled; public bool PublicEverything; public List Mods = new List(); public bool Strict; public bool MissingDependencyThrow; public bool RemovePatchReferences; public bool PreventInline; public bool? UpgradeMSCORLIB; public ReadingMode ReadingMode = ReadingMode.Immediate; public DebugSymbolFormat DebugSymbolOutputFormat = DebugSymbolFormat.Auto; public int CurrentRID = 0; protected IAssemblyResolver _assemblyResolver; public virtual IAssemblyResolver AssemblyResolver { get { if (_assemblyResolver == null) { DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver(); foreach (string dir in DependencyDirs) assemblyResolver.AddSearchDirectory(dir); _assemblyResolver = assemblyResolver; } return _assemblyResolver; } set => _assemblyResolver = value; } protected ReaderParameters _readerParameters; public virtual ReaderParameters ReaderParameters { get { if (_readerParameters == null) { _readerParameters = new ReaderParameters(ReadingMode) { AssemblyResolver = AssemblyResolver, ReadSymbols = true }; } return _readerParameters; } set => _readerParameters = value; } protected WriterParameters _writerParameters; public virtual WriterParameters WriterParameters { get { if (_writerParameters == null) { bool pdb = DebugSymbolOutputFormat == DebugSymbolFormat.PDB; bool mdb = DebugSymbolOutputFormat == DebugSymbolFormat.MDB; if (DebugSymbolOutputFormat == DebugSymbolFormat.Auto) { if (((int) PlatformHelper.Current & (int) Platform.Windows) == (int) Platform.Windows) pdb = true; else mdb = true; } _writerParameters = new WriterParameters() { WriteSymbols = true, SymbolWriterProvider = #if !CECIL0_9 pdb ? new NativePdbWriterProvider() : #else pdb ? new PdbWriterProvider() : #endif mdb ? new MdbWriterProvider() : (ISymbolWriterProvider) null }; } return _writerParameters; } set => _writerParameters = value; } protected string[] _GACPaths; public string[] GACPaths { get { if (_GACPaths != null) return _GACPaths; if (!IsMono) { // C:\Windows\Microsoft.NET\assembly\GAC_MSIL\System.Xml string path = Path.Combine(Environment.GetEnvironmentVariable("windir"), "Microsoft.NET"); path = Path.Combine(path, "assembly"); _GACPaths = new string[] { Path.Combine(path, "GAC_32"), Path.Combine(path, "GAC_64"), Path.Combine(path, "GAC_MSIL") }; } else { List paths = new List(); string gac = Path.Combine( Path.GetDirectoryName( Path.GetDirectoryName(typeof(object).GetTypeInfo().Module.FullyQualifiedName) ), "gac" ); if (Directory.Exists(gac)) paths.Add(gac); string prefixesEnv = Environment.GetEnvironmentVariable("MONO_GAC_PREFIX"); if (!string.IsNullOrEmpty(prefixesEnv)) { string[] prefixes = prefixesEnv.Split(Path.PathSeparator); foreach (string prefix in prefixes) { if (string.IsNullOrEmpty(prefix)) continue; string path = prefix; path = Path.Combine(path, "lib"); path = Path.Combine(path, "mono"); path = Path.Combine(path, "gac"); if (Directory.Exists(path) && !paths.Contains(path)) paths.Add(path); } } _GACPaths = paths.ToArray(); } return _GACPaths; } set => _GACPaths = value; } public MonoModder() { MethodParser = DefaultParser; MissingDependencyResolver = DefaultMissingDependencyResolver; PostProcessors += DefaultPostProcessor; string dependencyDirsEnv = Environment.GetEnvironmentVariable("MONOMOD_DEPDIRS"); if (!string.IsNullOrEmpty(dependencyDirsEnv)) { foreach (string dir in dependencyDirsEnv.Split(Path.PathSeparator).Select(dir => dir.Trim())) { (AssemblyResolver as BaseAssemblyResolver)?.AddSearchDirectory(dir); DependencyDirs.Add(dir); } } LogVerboseEnabled = Environment.GetEnvironmentVariable("MONOMOD_LOG_VERBOSE") == "1"; RelinkerCacheEnabled = Environment.GetEnvironmentVariable("MONOMOD_RELINKER_CACHED") == "1"; CleanupEnabled = Environment.GetEnvironmentVariable("MONOMOD_CLEANUP") != "0"; PublicEverything = Environment.GetEnvironmentVariable("MONOMOD_PUBLIC_EVERYTHING") == "1"; PreventInline = Environment.GetEnvironmentVariable("MONOMOD_PREVENTINLINE") == "1"; Strict = Environment.GetEnvironmentVariable("MONOMOD_STRICT") == "1"; MissingDependencyThrow = Environment.GetEnvironmentVariable("MONOMOD_DEPENDENCY_MISSING_THROW") != "0"; RemovePatchReferences = Environment.GetEnvironmentVariable("MONOMOD_DEPENDENCY_REMOVE_PATCH") != "0"; string upgradeMSCORLIBStr = Environment.GetEnvironmentVariable("MONOMOD_MSCORLIB_UPGRADE"); UpgradeMSCORLIB = string.IsNullOrEmpty(upgradeMSCORLIBStr) ? (bool?) null : (upgradeMSCORLIBStr != "0"); MonoModRulesManager.Register(this); } public virtual void ClearCaches(bool all = false, bool shareable = false, bool moduleSpecific = false) { if (all || shareable) { #if !CECIL0_9 foreach (KeyValuePair dep in DependencyCache) dep.Value.Dispose(); #endif DependencyCache.Clear(); } if (all || moduleSpecific) { RelinkMapCache.Clear(); RelinkModuleMapCache.Clear(); } } public virtual void Dispose() { ClearCaches(all: true); #if !CECIL0_9 Module?.Dispose(); #endif Module = null; #if !CECIL0_9 AssemblyResolver?.Dispose(); #endif AssemblyResolver = null; #if !CECIL0_9 foreach (ModuleDefinition mod in Mods) mod?.Dispose(); foreach (List dependencies in DependencyMap.Values) foreach (ModuleDefinition dep in dependencies) dep?.Dispose(); #endif DependencyMap.Clear(); Input?.Dispose(); Output?.Dispose(); } public virtual void Log(object value) { Log(value.ToString()); } public virtual void Log(string text) { Console.Write("[MonoMod] "); Console.WriteLine(text); } public virtual void LogVerbose(object value) { if (!LogVerboseEnabled) return; Log(value); } public virtual void LogVerbose(string text) { if (!LogVerboseEnabled) return; Log(text); } /// /// Reads the main module from the Input stream / InputPath file to Module. /// public virtual void Read() { if (Module == null) { if (Input != null) { Log("Reading input stream into module."); Module = MonoModExt.ReadModule(Input, GenReaderParameters(true)); } else if (InputPath != null) { Log("Reading input file into module."); (AssemblyResolver as BaseAssemblyResolver)?.AddSearchDirectory(Path.GetDirectoryName(InputPath)); DependencyDirs.Add(Path.GetDirectoryName(InputPath)); Module = MonoModExt.ReadModule(InputPath, GenReaderParameters(true, InputPath)); } string modsEnv = Environment.GetEnvironmentVariable("MONOMOD_MODS"); if (!string.IsNullOrEmpty(modsEnv)) { foreach (string path in modsEnv.Split(Path.PathSeparator).Select(path => path.Trim())) { ReadMod(path); } } } } public virtual void MapDependencies() { foreach (ModuleDefinition mod in Mods) MapDependencies(mod); MapDependencies(Module); } public virtual void MapDependencies(ModuleDefinition main) { if (DependencyMap.ContainsKey(main)) return; DependencyMap[main] = new List(); foreach (AssemblyNameReference dep in main.AssemblyReferences) MapDependency(main, dep); } public virtual void MapDependency(ModuleDefinition main, AssemblyNameReference depRef) { MapDependency(main, depRef.Name, depRef.FullName, depRef); } public virtual void MapDependency(ModuleDefinition main, string name, string fullName = null, AssemblyNameReference depRef = null) { if (!DependencyMap.TryGetValue(main, out List mapped)) DependencyMap[main] = mapped = new List(); if (fullName != null && ( DependencyCache.TryGetValue(fullName, out ModuleDefinition dep) || DependencyCache.TryGetValue(fullName + " [RT:" + main.RuntimeVersion + "]", out dep) )) { LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) from cache"); mapped.Add(dep); MapDependencies(dep); return; } if (DependencyCache.TryGetValue(name, out dep) || DependencyCache.TryGetValue(name + " [RT:" + main.RuntimeVersion + "]", out dep) ) { LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} ({name}) from cache"); mapped.Add(dep); MapDependencies(dep); return; } // Used to fix Mono.Cecil.pdb being loaded instead of Mono.Cecil.Pdb.dll string ext = Path.GetExtension(name).ToLowerInvariant(); bool nameRisky = ext == "pdb" || ext == "mdb"; string path = null; foreach (string depDir in DependencyDirs) { path = Path.Combine(depDir, name + ".dll"); if (!File.Exists(path)) path = Path.Combine(depDir, name + ".exe"); if (!File.Exists(path) && !nameRisky) path = Path.Combine(depDir, name); if (File.Exists(path)) break; else path = null; } // If we've got an AssemblyNameReference, use it to resolve the module. if (path == null && depRef != null) { try { dep = AssemblyResolver.Resolve(depRef)?.MainModule; } catch { } if (dep != null) #if !CECIL0_9 path = dep.FileName; #else path = dep.FullyQualifiedName; #endif } // Manually check in GAC if (path == null) { foreach (string gacpath in GACPaths) { path = Path.Combine(gacpath, name); if (Directory.Exists(path)) { string[] versions = Directory.GetDirectories(path); int highest = 0; int highestIndex = 0; for (int i = 0; i < versions.Length; i++) { string versionPath = versions[i]; if (versionPath.StartsWith(path)) versionPath = versionPath.Substring(path.Length + 1); Match versionMatch = Regex.Match(versionPath, "\\d+"); if (!versionMatch.Success) { continue; } int version = int.Parse(versionMatch.Value); if (version > highest) { highest = version; highestIndex = i; } // Maybe check minor versions? } path = Path.Combine(versions[highestIndex], name + ".dll"); break; } else { path = null; } } } // Try to use the AssemblyResolver with the full name (or even partial name). if (path == null) { try { dep = AssemblyResolver.Resolve(AssemblyNameReference.Parse(fullName ?? name))?.MainModule; } catch { } if (dep != null) #if !CECIL0_9 path = dep.FileName; #else path = dep.FullyQualifiedName; #endif } #if !NETSTANDARD // Check if available in GAC // Note: This is a fallback as MonoMod depends on a low version of the .NET Framework. // This unfortunately breaks ReflectionOnlyLoad on targets higher than the MonoMod target. if (path == null && fullName != null) { System.Reflection.Assembly asm = null; try { asm = System.Reflection.Assembly.ReflectionOnlyLoad(fullName); } catch { } path = asm?.Location; } #endif if (dep == null) { if (path != null && File.Exists(path)) { dep = MonoModExt.ReadModule(path, GenReaderParameters(false, path)); } else if ((dep = MissingDependencyResolver?.Invoke(this, main, name, fullName)) == null) { return; } } LogVerbose($"[MapDependency] {main.Name} -> {dep.Name} (({fullName}), ({name})) loaded"); mapped.Add(dep); if (fullName == null) fullName = dep.Assembly.FullName; DependencyCache[fullName] = dep; DependencyCache[name] = dep; MapDependencies(dep); } public virtual ModuleDefinition DefaultMissingDependencyResolver(MonoModder mod, ModuleDefinition main, string name, string fullName) { if (MissingDependencyThrow && Environment.GetEnvironmentVariable("MONOMOD_DEPENDENCY_MISSING_THROW") == "0") { Log("[MissingDependencyResolver] [WARNING] Use MMILRT.Modder.MissingDependencyThrow instead of setting the env var MONOMOD_DEPENDENCY_MISSING_THROW"); MissingDependencyThrow = false; } if (MissingDependencyThrow || Strict ) throw new RelinkTargetNotFoundException($"MonoMod cannot map dependency {main.Name} -> (({fullName}), ({name})) - not found", main); return null; } /// /// Write the modded module to the given stream or the default output. /// /// Output stream. If none given, default Output will be used. public virtual void Write(Stream output = null, string outputPath = null) { output = output ?? Output; outputPath = outputPath ?? OutputPath; PatchRefsInType(PatchWasHere()); if (output != null) { Log("[Write] Writing modded module into output stream."); Module.Write(output, WriterParameters); } else { Log("[Write] Writing modded module into output file."); Module.Write(outputPath, WriterParameters); } } public virtual ReaderParameters GenReaderParameters(bool mainModule, string path = null) { ReaderParameters _rp = ReaderParameters; ReaderParameters rp = new ReaderParameters(_rp.ReadingMode); rp.AssemblyResolver = _rp.AssemblyResolver; rp.MetadataResolver = _rp.MetadataResolver; #if !CECIL0_9 rp.MetadataImporterProvider = _rp.MetadataImporterProvider; rp.ReflectionImporterProvider = _rp.ReflectionImporterProvider; #endif rp.SymbolStream = _rp.SymbolStream; rp.SymbolReaderProvider = _rp.SymbolReaderProvider; rp.ReadSymbols = _rp.ReadSymbols; if (path != null && !File.Exists(path + ".mdb") && !File.Exists(Path.ChangeExtension(path, "pdb"))) rp.ReadSymbols = false; return rp; } public virtual void ReadMod(string path) { if (Directory.Exists(path)) { Log($"[ReadMod] Loading mod dir: {path}"); string mainName = Module.Name.Substring(0, Module.Name.Length - 3); string mainNameSpaceless = mainName.Replace(" ", ""); if (!DependencyDirs.Contains(path)) { (AssemblyResolver as BaseAssemblyResolver)?.AddSearchDirectory(path); DependencyDirs.Add(path); } foreach (string modFile in Directory.GetFiles(path)) if ((Path.GetFileName(modFile).StartsWith(mainName) || Path.GetFileName(modFile).StartsWith(mainNameSpaceless)) && modFile.ToLower().EndsWith(".mm.dll")) ReadMod(modFile); return; } Log($"[ReadMod] Loading mod: {path}"); ModuleDefinition mod = MonoModExt.ReadModule(path, GenReaderParameters(false, path)); string dir = Path.GetDirectoryName(path); if (!DependencyDirs.Contains(dir)) { (AssemblyResolver as BaseAssemblyResolver)?.AddSearchDirectory(dir); DependencyDirs.Add(dir); } Mods.Add(mod); OnReadMod?.Invoke(this, mod); } public virtual void ReadMod(Stream stream) { Log($"[ReadMod] Loading mod: stream#{(uint) stream.GetHashCode()}"); ModuleDefinition mod = MonoModExt.ReadModule(stream, GenReaderParameters(false)); Mods.Add(mod); OnReadMod?.Invoke(this, mod); } public virtual void ParseRules(ModuleDefinition mod) { TypeDefinition rulesType = mod.GetType("MonoMod.MonoModRules"); Type rulesTypeMMILRT = null; if (rulesType != null) { rulesTypeMMILRT = MonoModRulesManager.ExecuteRules(this, rulesType); // Finally, remove the type, otherwise it'll easily conflict with other mods' rules. mod.Types.Remove(rulesType); } // Rule parsing pass: Check for MonoModHook and similar attributes foreach (TypeDefinition type in mod.Types) ParseRulesInType(type, rulesTypeMMILRT); } public virtual void ParseRulesInType(TypeDefinition type, Type rulesTypeMMILRT = null) { string typeName = type.GetPatchFullName(); if (!type.MatchingConditionals(Module)) return; CustomAttribute caHandler; caHandler = type.GetMMAttribute("CustomAttributeAttribute"); if (caHandler != null) CustomAttributeHandlers[type.FullName] = (self, args) => rulesTypeMMILRT.GetMethod((string) caHandler.ConstructorArguments[0].Value).Invoke(self, args); caHandler = type.GetMMAttribute("CustomMethodAttributeAttribute"); if (caHandler != null) CustomMethodAttributeHandlers[type.FullName] = (self, args) => rulesTypeMMILRT.GetMethod((string) caHandler.ConstructorArguments[0].Value).Invoke(self, args); CustomAttribute hook; for (hook = type.GetMMAttribute("Hook"); hook != null; hook = type.GetNextMMAttribute("Hook")) ParseLinkFrom(type, hook); for (hook = type.GetMMAttribute("LinkFrom"); hook != null; hook = type.GetNextMMAttribute("LinkFrom")) ParseLinkFrom(type, hook); for (hook = type.GetMMAttribute("LinkTo"); hook != null; hook = type.GetNextMMAttribute("LinkTo")) ParseLinkTo(type, hook); if (type.HasMMAttribute("Ignore")) return; foreach (MethodDefinition method in type.Methods) { if (!method.MatchingConditionals(Module)) continue; for (hook = method.GetMMAttribute("Hook"); hook != null; hook = method.GetNextMMAttribute("Hook")) ParseLinkFrom(method, hook); for (hook = method.GetMMAttribute("LinkFrom"); hook != null; hook = method.GetNextMMAttribute("LinkFrom")) ParseLinkFrom(method, hook); for (hook = method.GetMMAttribute("LinkTo"); hook != null; hook = method.GetNextMMAttribute("LinkTo")) ParseLinkTo(method, hook); if (method.HasMMAttribute("ForceCall")) ForceCallMap[method.GetFindableID()] = OpCodes.Call; else if (method.HasMMAttribute("ForceCallvirt")) ForceCallMap[method.GetFindableID()] = OpCodes.Callvirt; } foreach (FieldDefinition field in type.Fields) { if (!field.MatchingConditionals(Module)) continue; for (hook = field.GetMMAttribute("Hook"); hook != null; hook = field.GetNextMMAttribute("Hook")) ParseLinkFrom(field, hook); for (hook = field.GetMMAttribute("LinkFrom"); hook != null; hook = field.GetNextMMAttribute("LinkFrom")) ParseLinkFrom(field, hook); for (hook = field.GetMMAttribute("LinkTo"); hook != null; hook = field.GetNextMMAttribute("LinkTo")) ParseLinkTo(field, hook); } foreach (PropertyDefinition prop in type.Properties) { if (!prop.MatchingConditionals(Module)) continue; for (hook = prop.GetMMAttribute("Hook"); hook != null; hook = prop.GetNextMMAttribute("Hook")) ParseLinkFrom(prop, hook); for (hook = prop.GetMMAttribute("LinkFrom"); hook != null; hook = prop.GetNextMMAttribute("LinkFrom")) ParseLinkFrom(prop, hook); for (hook = prop.GetMMAttribute("LinkTo"); hook != null; hook = prop.GetNextMMAttribute("LinkTo")) ParseLinkTo(prop, hook); } foreach (TypeDefinition nested in type.NestedTypes) ParseRulesInType(nested, rulesTypeMMILRT); } public virtual void ParseLinkFrom(MemberReference target, CustomAttribute hook) { string from = (string) hook.ConstructorArguments[0].Value; object to; if (target is TypeReference) to = ((TypeReference) target).GetPatchFullName(); else if (target is MethodReference) to = new RelinkMapEntry( ((MethodReference) target).DeclaringType.GetPatchFullName(), ((MethodReference) target).GetFindableID(withType: false) ); else if (target is FieldReference) to = new RelinkMapEntry( ((FieldReference) target).DeclaringType.GetPatchFullName(), ((FieldReference) target).Name ); else if (target is PropertyReference) to = new RelinkMapEntry( ((PropertyReference) target).DeclaringType.GetPatchFullName(), ((PropertyReference) target).Name ); else return; RelinkMap[from] = to; } public virtual void ParseLinkTo(MemberReference from, CustomAttribute hook) { string fromID = (from as MethodReference)?.GetFindableID() ?? from.GetPatchFullName(); if (hook.ConstructorArguments.Count == 1) RelinkMap[fromID] = (string) hook.ConstructorArguments[0].Value; else RelinkMap[fromID] = new RelinkMapEntry( (string) hook.ConstructorArguments[0].Value, (string) hook.ConstructorArguments[1].Value ); } public virtual void RunCustomAttributeHandlers(ICustomAttributeProvider cap) { if (!cap.HasCustomAttributes) return; foreach (CustomAttribute attrib in cap.CustomAttributes) { if (CustomAttributeHandlers.TryGetValue(attrib.AttributeType.FullName, out FastReflectionDelegate handler)) handler?.Invoke(null, cap, attrib); if (cap is MethodReference && CustomMethodAttributeHandlers.TryGetValue(attrib.AttributeType.FullName, out handler)) handler?.Invoke(null, (MethodDefinition) cap, attrib); } } /// /// Automatically mods the module, loading Input, writing the modded module to Output. /// public virtual void AutoPatch() { Log("[AutoPatch] Parsing rules in loaded mods"); foreach (ModuleDefinition mod in Mods) ParseRules(mod); /* WHY PRE-PATCH? * Custom attributes and other stuff refering to possibly new types * 1. could access yet undefined types that need to be copied over * 2. need to be copied over themselves anyway, regardless if new type or not * To define the order of origMethoding (first types, then references), PrePatch does * the "type addition" job by creating stub types, which then get filled in * the Patch pass. */ Log("[AutoPatch] PrePatch pass"); foreach (ModuleDefinition mod in Mods) PrePatchModule(mod); Log("[AutoPatch] Patch pass"); foreach (ModuleDefinition mod in Mods) PatchModule(mod); /* The PatchRefs pass fixes all references referring to stuff * possibly added in the PrePatch or Patch passes. */ Log("[AutoPatch] PatchRefs pass"); PatchRefs(); if (PostProcessors != null) { Delegate[] pps = PostProcessors.GetInvocationList(); for (int i = 0; i < pps.Length; i++) { Log($"[PostProcessor] PostProcessor pass #{i + 1}"); ((PostProcessor) pps[i])?.Invoke(this); } } } public virtual IMetadataTokenProvider Relinker(IMetadataTokenProvider mtp, IGenericParameterProvider context) { try { // TODO: Handle mtp being deleted but being hooked in a better, Strict-compatible way. IMetadataTokenProvider relinked = PostRelinker( MainRelinker(mtp, context) ?? mtp, context ); if (relinked == null) throw new RelinkTargetNotFoundException(mtp, context); return relinked; } catch (Exception e) { throw new RelinkFailedException(null, e, mtp, context); } } public virtual IMetadataTokenProvider MainRelinker(IMetadataTokenProvider mtp, IGenericParameterProvider context) { if (mtp is TypeReference type) { // Type is coming from the input module - return the original. if (type.Module == Module) return type; // Type isn't coming from a mod module - import the original. if (type.Module != null && !Mods.Contains(type.Module)) return Module.ImportReference(type); // Type **reference** is coming from a mod module - resolve it just to be safe. type = type.SafeResolve() ?? type; TypeReference found = FindTypeDeep(type.GetPatchFullName()); if (found == null) { if (RelinkMap.ContainsKey(type.FullName)) return null; // Let the post-relinker handle this. throw new RelinkTargetNotFoundException(mtp, context); } return Module.ImportReference(found); } if (mtp is FieldReference || mtp is MethodReference || mtp is PropertyReference || mtp is EventReference) // Don't relink those. It'd be useful to f.e. link to member B instead of member A. // MonoModExt already handles the default "deep" relinking. return Module.ImportReference(mtp); throw new InvalidOperationException($"MonoMod default relinker can't handle metadata token providers of the type {mtp.GetType()}"); } public virtual IMetadataTokenProvider PostRelinker(IMetadataTokenProvider mtp, IGenericParameterProvider context) { // The post relinker doesn't care if it can't handle a specific metadata token provider type; Just run ResolveRelinkTarget return ResolveRelinkTarget(mtp) ?? mtp; } public virtual IMetadataTokenProvider ResolveRelinkTarget(IMetadataTokenProvider mtp, bool relink = true, bool relinkModule = true) { string name; string nameAlt = null; if (mtp is TypeReference) { name = ((TypeReference) mtp).FullName; } else if (mtp is MethodReference) { name = ((MethodReference) mtp).GetFindableID(withType: true); nameAlt = ((MethodReference) mtp).GetFindableID(simple: true); } else if (mtp is FieldReference) { name = ((FieldReference) mtp).FullName; } else if (mtp is PropertyReference) { name = ((PropertyReference) mtp).FullName; } else return null; if (RelinkMapCache.TryGetValue(name, out IMetadataTokenProvider cached)) return cached; if (relink && ( RelinkMap.TryGetValue(name, out object val) || (nameAlt != null && RelinkMap.TryGetValue(nameAlt, out val)) )) { // If the value already is a mtp, import and cache the imported reference. if (val is IMetadataTokenProvider) return RelinkMapCache[name] = Module.ImportReference((IMetadataTokenProvider) val); if (val is RelinkMapEntry) { string typeName = ((RelinkMapEntry) val).Type as string; string findableID = ((RelinkMapEntry) val).FindableID as string; TypeDefinition type = FindTypeDeep(typeName)?.SafeResolve(); if (type == null) return RelinkMapCache[name] = ResolveRelinkTarget(mtp, false, relinkModule); val = type.FindMethod(findableID) ?? type.FindField(findableID) ?? type.FindProperty(findableID) ?? (object) null ; if (val == null) { if (Strict) throw new RelinkTargetNotFoundException($"{RelinkTargetNotFoundException.DefaultMessage} ({typeName}, {findableID}) (remap: {mtp})", mtp); else return null; } return RelinkMapCache[name] = Module.ImportReference((IMetadataTokenProvider) val); } if (val is string && mtp is TypeReference) { IMetadataTokenProvider found = FindTypeDeep((string) val); if (found == null) { if (Strict) throw new RelinkTargetNotFoundException($"{RelinkTargetNotFoundException.DefaultMessage} {val} (remap: {mtp})", mtp); else return null; } val = Module.ImportReference( ResolveRelinkTarget(found, false, relinkModule) ?? found ); } if (val is IMetadataTokenProvider) return RelinkMapCache[name] = (IMetadataTokenProvider) val; throw new InvalidOperationException($"MonoMod doesn't support RelinkMap value of type {val.GetType()} (remap: {mtp})"); } if (relinkModule && mtp is TypeReference) { if (RelinkModuleMapCache.TryGetValue(name, out TypeReference type)) return type; type = (TypeReference) mtp; if (RelinkModuleMap.TryGetValue(type.Scope.Name, out ModuleDefinition scope)) { TypeReference found = scope.GetType(type.FullName); if (found == null) { if (Strict) throw new RelinkTargetNotFoundException($"{RelinkTargetNotFoundException.DefaultMessage} {type.FullName} (remap: {mtp})", mtp); else return null; } return RelinkModuleMapCache[name] = Module.ImportReference(found); } // Value types (i.e. enums) as custom attribute parameters aren't marked as value types. // To prevent that and other issues from popping up, don't cache the default. return Module.ImportReference(type); } return null; } public virtual bool DefaultParser(MonoModder mod, MethodBody body, Instruction instr, ref int instri) { return true; } public virtual TypeReference FindType(string name) => FindType(Module, name, new Stack()) ?? Module.GetType(name, false); public virtual TypeReference FindType(string name, bool runtimeName) => FindType(Module, name, new Stack()) ?? Module.GetType(name, runtimeName); protected virtual TypeReference FindType(ModuleDefinition main, string fullName, Stack crawled) { TypeReference type; if ((type = main.GetType(fullName, false)) != null) return type; if (fullName.StartsWith("/")) return null; if (crawled.Contains(main)) return null; crawled.Push(main); foreach (ModuleDefinition dep in DependencyMap[main]) if (!(RemovePatchReferences && dep.Assembly.Name.Name.EndsWith(".mm")) && (type = FindType(dep, fullName, crawled)) != null) return type; return null; } public virtual TypeReference FindTypeDeep(string name) { TypeReference type = FindType(name, false); if (type != null) return type; // Check in the dependencies of the mod modules. Stack crawled = new Stack(); // Set type to null so that an actual break condition exists type = null; foreach (ModuleDefinition mod in Mods) foreach (ModuleDefinition dep in DependencyMap[mod]) if ((type = FindType(dep, name, crawled)) != null) { // Type may come from a dependency. If the assembly reference is missing, add. if (type.Scope is AssemblyNameReference dllRef && !Module.AssemblyReferences.Any(n => n.Name == dllRef.Name)) Module.AssemblyReferences.Add(dllRef); return Module.ImportReference(type); } return null; } #region Pre-Patch Pass /// /// Pre-Patches the module (adds new types, module references, resources, ...). /// /// Mod to patch into the input module. public virtual void PrePatchModule(ModuleDefinition mod) { foreach (TypeDefinition type in mod.Types) PrePatchType(type); foreach (ModuleReference @ref in mod.ModuleReferences) if (!Module.ModuleReferences.Contains(@ref)) Module.ModuleReferences.Add(@ref); foreach (Resource res in mod.Resources) if (res is EmbeddedResource) Module.Resources.Add(new EmbeddedResource( res.Name.StartsWith(mod.Assembly.Name.Name) ? Module.Assembly.Name.Name + res.Name.Substring(mod.Assembly.Name.Name.Length) : res.Name, res.Attributes, ((EmbeddedResource) res).GetResourceData() )); } /// /// Patches the type (adds new types). /// /// Type to patch into the input module. public virtual void PrePatchType(TypeDefinition type, bool forceAdd = false) { string typeName = type.GetPatchFullName(); // Fix legacy issue: Copy / inline any used modifiers. if ((type.Namespace != "MonoMod" && type.HasMMAttribute("Ignore")) || SkipList.Contains(typeName) || !type.MatchingConditionals(Module)) return; // ... Except MonoModRules if (type.FullName == "MonoMod.MonoModRules" && !forceAdd) return; // Check if type exists in target module or dependencies. TypeReference targetType = forceAdd ? null : Module.GetType(typeName, false); // For PrePatch, we need to check in the target assembly only TypeDefinition targetTypeDef = targetType?.SafeResolve(); if (type.HasMMAttribute("Replace") || type.HasMMAttribute("Remove")) { if (targetTypeDef != null) { if (targetTypeDef.DeclaringType == null) Module.Types.Remove(targetTypeDef); else targetTypeDef.DeclaringType.NestedTypes.Remove(targetTypeDef); } if (type.HasMMAttribute("Remove")) return; } else if (targetType != null) { PrePatchNested(type); return; } // Add the type. LogVerbose($"[PrePatchType] Adding {typeName} to the target module."); TypeDefinition newType = new TypeDefinition(type.Namespace, type.Name, type.Attributes, type.BaseType); foreach (GenericParameter genParam in type.GenericParameters) newType.GenericParameters.Add(genParam.Clone().Relink(Relinker, newType)); foreach (InterfaceImplementation interf in type.Interfaces) newType.Interfaces.Add(interf); newType.ClassSize = type.ClassSize; if (type.DeclaringType != null) { // The declaring type is existing as this is being called nestedly. newType.DeclaringType = type.DeclaringType.Relink(Relinker, newType).Resolve(); newType.DeclaringType.NestedTypes.Add(newType); } else { Module.Types.Add(newType); } newType.PackingSize = type.PackingSize; newType.SecurityDeclarations.AddRange(type.SecurityDeclarations); // When adding MonoModAdded, try to reuse the just added MonoModAdded. newType.AddAttribute(GetMonoModAddedCtor()); targetType = newType; PrePatchNested(type); } protected virtual void PrePatchNested(TypeDefinition type) { for (int i = 0; i < type.NestedTypes.Count; i++) { PrePatchType(type.NestedTypes[i]); } } #endregion #region Patch Pass /// /// Patches the module (adds new type members). /// /// Mod to patch into the input module. public virtual void PatchModule(ModuleDefinition mod) { foreach (TypeDefinition type in mod.Types) if ( (type.Namespace == "MonoMod" || type.Namespace.StartsWith("MonoMod.")) && type.BaseType.FullName == "System.Attribute" ) PatchType(type); foreach (TypeDefinition type in mod.Types) if (!( (type.Namespace == "MonoMod" || type.Namespace.StartsWith("MonoMod.")) && type.BaseType.FullName == "System.Attribute" )) PatchType(type); } /// /// Patches the type (adds new members). /// /// Type to patch into the input module. public virtual void PatchType(TypeDefinition type) { string typeName = type.GetPatchFullName(); TypeReference targetType = Module.GetType(typeName, false); if (targetType == null) return; // Type should've been added or removed accordingly. TypeDefinition targetTypeDef = targetType?.SafeResolve(); if ((type.Namespace != "MonoMod" && type.HasMMAttribute("Ignore")) || // Fix legacy issue: Copy / inline any used modifiers. SkipList.Contains(typeName) || !type.MatchingConditionals(Module)) { if (type.HasMMAttribute("Ignore") && targetTypeDef != null) { // MonoModIgnore is a special case, as registered custom attributes should still be applied. foreach (CustomAttribute attrib in type.CustomAttributes) if (CustomAttributeHandlers.ContainsKey(attrib.AttributeType.FullName)) targetTypeDef.CustomAttributes.Add(attrib.Clone()); } PatchNested(type); return; } if (typeName == type.FullName) LogVerbose($"[PatchType] Patching type {typeName}"); else LogVerbose($"[PatchType] Patching type {typeName} (prefixed: {type.FullName})"); // Add "new" custom attributes foreach (CustomAttribute attrib in type.CustomAttributes) if (!targetTypeDef.HasCustomAttribute(attrib.AttributeType.FullName)) targetTypeDef.CustomAttributes.Add(attrib.Clone()); HashSet propMethods = new HashSet(); // In the Patch pass, prop methods exist twice. foreach (PropertyDefinition prop in type.Properties) PatchProperty(targetTypeDef, prop, propMethods); HashSet eventMethods = new HashSet(); // In the Patch pass, prop methods exist twice. foreach (EventDefinition eventdef in type.Events) PatchEvent(targetTypeDef, eventdef, eventMethods); foreach (MethodDefinition method in type.Methods) if (!propMethods.Contains(method) && !eventMethods.Contains(method)) PatchMethod(targetTypeDef, method); if (type.HasMMAttribute("EnumReplace")) { for (int ii = 0; ii < targetTypeDef.Fields.Count;) { if (targetTypeDef.Fields[ii].Name == "value__") { ii++; continue; } targetTypeDef.Fields.RemoveAt(ii); } } foreach (FieldDefinition field in type.Fields) PatchField(targetTypeDef, field); PatchNested(type); } protected virtual void PatchNested(TypeDefinition type) { for (int i = 0; i < type.NestedTypes.Count; i++) { PatchType(type.NestedTypes[i]); } } public virtual void PatchProperty(TypeDefinition targetType, PropertyDefinition prop, HashSet propMethods = null) { if (!prop.MatchingConditionals(Module)) return; MethodDefinition addMethod; PropertyDefinition targetProp = targetType.FindProperty(prop.Name); string backingName = $"<{prop.Name}>__BackingField"; FieldDefinition backing = prop.DeclaringType.FindField(backingName); FieldDefinition targetBacking = targetType.FindField(backingName); // Cheap fix: Apply the mod property attributes on the mod backing field. // Causes the field to be ignored / replaced / ... in its own patch pass further below. if (backing != null) foreach (CustomAttribute attrib in prop.CustomAttributes) backing.CustomAttributes.Add(attrib.Clone()); if (prop.HasMMAttribute("Ignore")) { if (backing != null) backing.DeclaringType.Fields.Remove(backing); // Otherwise the backing field gets added anyway if (prop.GetMethod != null) propMethods?.Add(prop.GetMethod); if (prop.SetMethod != null) propMethods?.Add(prop.SetMethod); foreach (MethodDefinition method in prop.OtherMethods) propMethods?.Add(method); return; } if (prop.HasMMAttribute("Remove") || prop.HasMMAttribute("Replace")) { if (targetProp != null) { targetType.Properties.Remove(targetProp); if (targetBacking != null) targetType.Fields.Remove(targetBacking); if (targetProp.GetMethod != null) targetType.Methods.Remove(targetProp.GetMethod); if (targetProp.SetMethod != null) targetType.Methods.Remove(targetProp.SetMethod); foreach (MethodDefinition method in targetProp.OtherMethods) targetType.Methods.Remove(method); } if (prop.HasMMAttribute("Remove")) return; } if (targetProp == null) { // Add missing property PropertyDefinition newProp = targetProp = new PropertyDefinition(prop.Name, prop.Attributes, prop.PropertyType); newProp.AddAttribute(GetMonoModAddedCtor()); foreach (ParameterDefinition param in prop.Parameters) newProp.Parameters.Add(param.Clone()); newProp.DeclaringType = targetType; targetType.Properties.Add(newProp); if (backing != null) { FieldDefinition newBacking = targetBacking = new FieldDefinition(backingName, backing.Attributes, backing.FieldType); targetType.Fields.Add(newBacking); } } foreach (CustomAttribute attrib in prop.CustomAttributes) targetProp.CustomAttributes.Add(attrib.Clone()); MethodDefinition getter = prop.GetMethod; if (getter != null && (addMethod = PatchMethod(targetType, getter)) != null) { targetProp.GetMethod = addMethod; propMethods?.Add(getter); } MethodDefinition setter = prop.SetMethod; if (setter != null && (addMethod = PatchMethod(targetType, setter)) != null) { targetProp.SetMethod = addMethod; propMethods?.Add(setter); } foreach (MethodDefinition method in prop.OtherMethods) if ((addMethod = PatchMethod(targetType, method)) != null) { targetProp.OtherMethods.Add(addMethod); propMethods?.Add(method); } } public virtual void PatchEvent(TypeDefinition targetType, EventDefinition srcEvent, HashSet propMethods = null) { MethodDefinition patched; EventDefinition targetEvent = targetType.FindEvent(srcEvent.Name); string backingName = $"<{srcEvent.Name}>__BackingField"; FieldDefinition backing = srcEvent.DeclaringType.FindField(backingName); FieldDefinition targetBacking = targetType.FindField(backingName); // Cheap fix: Apply the mod property attributes on the mod backing field. // Causes the field to be ignored / replaced / ... in its own patch pass further below. if (backing != null) foreach (CustomAttribute attrib in srcEvent.CustomAttributes) backing.CustomAttributes.Add(attrib.Clone()); if (srcEvent.HasMMAttribute("Ignore")) { if (backing != null) backing.DeclaringType.Fields.Remove(backing); // Otherwise the backing field gets added anyway if (srcEvent.AddMethod != null) propMethods?.Add(srcEvent.AddMethod); if (srcEvent.RemoveMethod != null) propMethods?.Add(srcEvent.RemoveMethod); if (srcEvent.InvokeMethod != null) propMethods?.Add(srcEvent.InvokeMethod); foreach (MethodDefinition method in srcEvent.OtherMethods) propMethods?.Add(method); return; } if (srcEvent.HasMMAttribute("Remove") || srcEvent.HasMMAttribute("Replace")) { if (targetEvent != null) { targetType.Events.Remove(targetEvent); if (targetBacking != null) targetType.Fields.Remove(targetBacking); if (targetEvent.AddMethod != null) targetType.Methods.Remove(targetEvent.AddMethod); if (targetEvent.RemoveMethod != null) targetType.Methods.Remove(targetEvent.RemoveMethod); if (targetEvent.InvokeMethod != null) targetType.Methods.Remove(targetEvent.InvokeMethod); if (targetEvent.OtherMethods != null) foreach (MethodDefinition method in targetEvent.OtherMethods) targetType.Methods.Remove(method); } if (srcEvent.HasMMAttribute("Remove")) return; } if (targetEvent == null) { // Add missing event EventDefinition newEvent = targetEvent = new EventDefinition(srcEvent.Name, srcEvent.Attributes, srcEvent.EventType); newEvent.AddAttribute(GetMonoModAddedCtor()); newEvent.DeclaringType = targetType; targetType.Events.Add(newEvent); if (backing != null) { FieldDefinition newBacking = new FieldDefinition(backingName, backing.Attributes, backing.FieldType); targetType.Fields.Add(newBacking); } } foreach (CustomAttribute attrib in srcEvent.CustomAttributes) targetEvent.CustomAttributes.Add(attrib.Clone()); MethodDefinition adder = srcEvent.AddMethod; if (adder != null && (patched = PatchMethod(targetType, adder)) != null) { targetEvent.AddMethod = patched; propMethods?.Add(adder); } MethodDefinition remover = srcEvent.RemoveMethod; if (remover != null && (patched = PatchMethod(targetType, remover)) != null) { targetEvent.RemoveMethod = patched; propMethods?.Add(remover); } MethodDefinition invoker = srcEvent.InvokeMethod; if (invoker != null && (patched = PatchMethod(targetType, invoker)) != null) { targetEvent.InvokeMethod = patched; propMethods?.Add(invoker); } foreach (MethodDefinition method in srcEvent.OtherMethods) if ((patched = PatchMethod(targetType, method)) != null) { targetEvent.OtherMethods.Add(patched); propMethods?.Add(method); } } public virtual void PatchField(TypeDefinition targetType, FieldDefinition field) { string typeName = field.DeclaringType.GetPatchFullName(); if (field.HasMMAttribute("NoNew") || SkipList.Contains(typeName + "::" + field.Name) || !field.MatchingConditionals(Module)) return; if (field.HasMMAttribute("Remove") || field.HasMMAttribute("Replace")) { FieldDefinition targetField = targetType.FindField(field.Name); if (targetField != null) targetType.Fields.Remove(targetField); if (field.HasMMAttribute("Remove")) return; } FieldDefinition existingField = targetType.FindField(field.Name); if (field.HasMMAttribute("Ignore") && existingField != null) { // MonoModIgnore is a special case, as registered custom attributes should still be applied. foreach (CustomAttribute attrib in field.CustomAttributes) if (CustomAttributeHandlers.ContainsKey(attrib.AttributeType.FullName)) existingField.CustomAttributes.Add(attrib.Clone()); return; } if (existingField == null) { existingField = new FieldDefinition(field.Name, field.Attributes, field.FieldType); existingField.AddAttribute(GetMonoModAddedCtor()); existingField.InitialValue = field.InitialValue; if (field.HasConstant) existingField.Constant = field.Constant; targetType.Fields.Add(existingField); } foreach (CustomAttribute attrib in field.CustomAttributes) existingField.CustomAttributes.Add(attrib.Clone()); } public virtual MethodDefinition PatchMethod(TypeDefinition targetType, MethodDefinition method) { if (method.Name.StartsWith("orig_") || method.HasMMAttribute("Original")) // Ignore original method stubs return null; if (!AllowedSpecialName(method, targetType) || !method.MatchingConditionals(Module)) // Ignore ignored methods return null; string typeName = targetType.GetPatchFullName(); if (SkipList.Contains(method.GetFindableID(type: typeName))) return null; // If the method's a MonoModConstructor method, just update its attributes to make it look like one. if (method.HasMMAttribute("Constructor")) { // Add MonoModOriginalName as the orig name data gets lost otherwise. if (!method.IsSpecialName && !method.HasMMAttribute("OriginalName")) { CustomAttribute origNameAttrib = new CustomAttribute(GetMonoModOriginalNameCtor()); origNameAttrib.ConstructorArguments.Add(new CustomAttributeArgument(Module.TypeSystem.String, "orig_" + method.Name)); method.AddAttribute(origNameAttrib); } method.Name = method.IsStatic ? ".cctor" : ".ctor"; method.IsSpecialName = true; method.IsRuntimeSpecialName = true; } MethodDefinition existingMethod = targetType.FindMethod(method.GetFindableID(type: typeName)); MethodDefinition origMethod = targetType.FindMethod(method.GetFindableID(type: typeName, name: method.GetOriginalName())); if (method.HasMMAttribute("Ignore")) { // MonoModIgnore is a special case, as registered custom attributes should still be applied. if (existingMethod != null) foreach (CustomAttribute attrib in method.CustomAttributes) if (CustomAttributeHandlers.ContainsKey(attrib.AttributeType.FullName) || CustomMethodAttributeHandlers.ContainsKey(attrib.AttributeType.FullName)) existingMethod.CustomAttributes.Add(attrib.Clone()); return null; } if (existingMethod == null && method.HasMMAttribute("NoNew")) return null; if (method.HasMMAttribute("Remove")) { if (existingMethod != null) targetType.Methods.Remove(existingMethod); return null; } if (method.HasMMAttribute("Replace")) { method.Name = method.GetPatchName(); if (existingMethod != null) { existingMethod.CustomAttributes.Clear(); existingMethod.Attributes = method.Attributes; existingMethod.IsPInvokeImpl = method.IsPInvokeImpl; existingMethod.ImplAttributes = method.ImplAttributes; } } else if (existingMethod != null && origMethod == null) { origMethod = existingMethod.Clone(); origMethod.Name = method.GetOriginalName(); origMethod.Attributes = existingMethod.Attributes & ~MethodAttributes.SpecialName & ~MethodAttributes.RTSpecialName; origMethod.MetadataToken = GetMetadataToken(TokenType.Method); origMethod.IsVirtual = false; // Fix overflow when calling orig_ method, but orig_ method already defined higher up origMethod.Overrides.Clear(); foreach (MethodReference @override in method.Overrides) origMethod.Overrides.Add(@override); origMethod.AddAttribute(GetMonoModOriginalCtor()); // Check if we've got custom attributes on our own orig_ method. MethodDefinition modOrigMethod = method.DeclaringType.FindMethod(method.GetFindableID(name: method.GetOriginalName())); if (modOrigMethod != null) foreach (CustomAttribute attrib in modOrigMethod.CustomAttributes) if (CustomAttributeHandlers.ContainsKey(attrib.AttributeType.FullName) || CustomMethodAttributeHandlers.ContainsKey(attrib.AttributeType.FullName)) origMethod.CustomAttributes.Add(attrib.Clone()); targetType.Methods.Add(origMethod); } // Fix for .cctor not linking to orig_.cctor if (origMethod != null && method.IsConstructor && method.IsStatic && method.HasBody && !method.HasMMAttribute("Constructor")) { Collection instructions = method.Body.Instructions; ILProcessor ilProcessor = method.Body.GetILProcessor(); ilProcessor.InsertBefore(instructions[instructions.Count - 1], ilProcessor.Create(OpCodes.Call, origMethod)); } if (existingMethod != null) { existingMethod.Body = method.Body.Clone(existingMethod); existingMethod.IsManaged = method.IsManaged; existingMethod.IsIL = method.IsIL; existingMethod.IsNative = method.IsNative; existingMethod.PInvokeInfo = method.PInvokeInfo; existingMethod.IsPreserveSig = method.IsPreserveSig; existingMethod.IsInternalCall = method.IsInternalCall; existingMethod.IsPInvokeImpl = method.IsPInvokeImpl; foreach (CustomAttribute attrib in method.CustomAttributes) existingMethod.CustomAttributes.Add(attrib.Clone()); method = existingMethod; } else { MethodDefinition clone = new MethodDefinition(method.Name, method.Attributes, Module.TypeSystem.Void); clone.MetadataToken = GetMetadataToken(TokenType.Method); clone.CallingConvention = method.CallingConvention; clone.ExplicitThis = method.ExplicitThis; clone.MethodReturnType = method.MethodReturnType; clone.Attributes = method.Attributes; clone.ImplAttributes = method.ImplAttributes; clone.SemanticsAttributes = method.SemanticsAttributes; clone.DeclaringType = targetType; clone.ReturnType = method.ReturnType; clone.Body = method.Body.Clone(clone); clone.PInvokeInfo = method.PInvokeInfo; clone.IsPInvokeImpl = method.IsPInvokeImpl; foreach (GenericParameter genParam in method.GenericParameters) clone.GenericParameters.Add(genParam.Clone()); foreach (ParameterDefinition param in method.Parameters) clone.Parameters.Add(param.Clone()); foreach (CustomAttribute attrib in method.CustomAttributes) clone.CustomAttributes.Add(attrib.Clone()); foreach (MethodReference @override in method.Overrides) clone.Overrides.Add(@override); clone.AddAttribute(GetMonoModAddedCtor()); targetType.Methods.Add(clone); method = clone; } if (origMethod != null) { CustomAttribute origNameAttrib = new CustomAttribute(GetMonoModOriginalNameCtor()); origNameAttrib.ConstructorArguments.Add(new CustomAttributeArgument(Module.TypeSystem.String, origMethod.Name)); method.AddAttribute(origNameAttrib); } return method; } #endregion #region PatchRefs Pass public virtual void PatchRefs() { if (Environment.GetEnvironmentVariable("MONOMOD_LEGACY_RELINKMAP") == "1") { _SplitUpgrade(); } if (UpgradeMSCORLIB == null) { // Check if the assembly depends on mscorlib 2.0.5.0, possibly Unity. // If so, upgrade to that version (or away to an even higher version). Version fckUnity = new Version(2, 0, 5, 0); UpgradeMSCORLIB = Module.AssemblyReferences.Any(x => x.Version == fckUnity); } if (UpgradeMSCORLIB.Value) { // Attempt to remap and remove redundant mscorlib references. // Subpass 1: Find newest referred version. List mscorlibDeps = new List(); for (int i = 0; i < Module.AssemblyReferences.Count; i++) { AssemblyNameReference dep = Module.AssemblyReferences[i]; if (dep.Name == "mscorlib") { mscorlibDeps.Add(dep); } } if (mscorlibDeps.Count > 1) { // Subpass 2: Apply changes if found. AssemblyNameReference maxmscorlib = mscorlibDeps.OrderByDescending(x => x.Version).First(); if (DependencyCache.TryGetValue(maxmscorlib.FullName, out ModuleDefinition mscorlib)) { for (int i = 0; i < Module.AssemblyReferences.Count; i++) { AssemblyNameReference dep = Module.AssemblyReferences[i]; if (dep.Name == "mscorlib" && maxmscorlib.Version > dep.Version) { LogVerbose("[PatchRefs] Removing and relinking duplicate mscorlib: " + dep.Version); RelinkModuleMap[dep.FullName] = mscorlib; Module.AssemblyReferences.RemoveAt(i); --i; } } } } } foreach (TypeDefinition type in Module.Types) PatchRefsInType(type); } // Private because this method isn't here to stay. private void _SplitUpgrade() { // This is required to stay compatible with mods created before splitting MonoMod into pieces. // Only run if the mod refers to MonoMod <= 18.03.* and if MonoModExt is no longer present in MonoMod. if (FindType("MonoMod.MonoModExt") != null) return; bool requiresUpgrade = false; List modules = new List(Mods) { Module }; foreach (ModuleDefinition mod in modules) { for (int i = 0; i < mod.AssemblyReferences.Count; i++) { AssemblyNameReference dep = mod.AssemblyReferences[i]; if (dep.Name == "MonoMod") { if (dep.Version.Major < 18 || (dep.Version.Major == 18 && dep.Version.Minor <= 3)) { requiresUpgrade = true; } break; } } if (requiresUpgrade) break; } if (!requiresUpgrade) return; Log("[UpgradeSplit] Upgrading from MonoMod pre-18.03 to 18.04+"); Log("[UpgradeSplit] THIS STEP WILL BE REMOVED IN A FUTURE RELEASE."); Log("[UpgradeSplit] It is only meant to preserve compatibility with mods during the transition to a \"split\" MonoMod."); string root = Path.GetDirectoryName(DependencyCache["MonoMod"] #if !CECIL0_9 .FileName #else .FullyQualifiedName #endif ); bool found = false; // Don't compact this, otherwise it'll only run until the first "true" upgrade. found |= _SplitUpgrade("MonoMod"); found |= _SplitUpgrade("MonoMod.Utils"); found |= _SplitUpgrade("MonoMod.RuntimeDetour"); if (!found) { Log("[UpgradeSplit] No MonoMod \"split\" upgrade targets found. Upgrade skipped."); return; } } private bool _SplitUpgrade(string split) { bool missingDependencyThrow = MissingDependencyThrow; MissingDependencyThrow = false; MapDependency(Module, split); MissingDependencyThrow = missingDependencyThrow; if (!DependencyCache.TryGetValue(split, out ModuleDefinition splitModule)) { Log($"[UpgradeSplit] {split} doesn't exist, skipping it."); return false; } _SplitUpgrade(splitModule); return true; } private void _SplitUpgrade(ModuleDefinition split) { Log($"[UpgradeSplit] Upgrading to split {split.Name}"); foreach (TypeDefinition type in split.Types) _SplitUpgrade(type); } private void _SplitUpgrade(TypeDefinition type) { CustomAttribute typeAttribOldName = type.GetMMAttribute("__OldName__"); string typeOldName = typeAttribOldName == null ? null : (string) typeAttribOldName.ConstructorArguments[0].Value; RelinkMap[type.FullName] = type; if (typeOldName != null) RelinkMap[typeOldName] = type; foreach (FieldDefinition field in type.Fields) { if (field.HasMMAttribute("__WasIDictionary__")) { // MonoMod moved from IDictionary to Dictionary, but provides proxies for old mods. // Relink from old field type + new field name => proxy property. GenericInstanceType fieldType = (GenericInstanceType) field.FieldType; GenericInstanceType fieldTypeOld = new GenericInstanceType( FindTypeDeep("System.Collections.Generic.IDictionary`2") ); fieldTypeOld.GenericArguments.AddRange(fieldType.GenericArguments); RelinkMap[$"{fieldTypeOld} {type.FullName}::{field.Name}"] = type.FindProperty($"_{field.Name}"); if (typeOldName != null) RelinkMap[$"{fieldTypeOld} {typeOldName}::{field.Name}"] = type.FindProperty($"_{field.Name}"); } } foreach (TypeDefinition nested in type.NestedTypes) _SplitUpgrade(nested); } public virtual void PatchRefs(ModuleDefinition mod) { foreach (TypeDefinition type in mod.Types) PatchRefsInType(type); } public virtual void PatchRefsInType(TypeDefinition type) { LogVerbose($"[VERBOSE] [PatchRefsInType] Patching refs in {type}"); if (type.BaseType != null) type.BaseType = type.BaseType.Relink(Relinker, type); // Don't foreach when modifying the collection for (int i = 0; i < type.GenericParameters.Count; i++) type.GenericParameters[i] = type.GenericParameters[i].Relink(Relinker, type); // Don't foreach when modifying the collection for (int i = 0; i < type.Interfaces.Count; i++) { #if !CECIL0_9 InterfaceImplementation interf = type.Interfaces[i]; InterfaceImplementation newInterf = new InterfaceImplementation(interf.InterfaceType.Relink(Relinker, type)); foreach (CustomAttribute attrib in interf.CustomAttributes) newInterf.CustomAttributes.Add(attrib.Relink(Relinker, type)); type.Interfaces[i] = newInterf; #else TypeReference interf = type.Interfaces[i]; TypeReference newInterf = interf.Relink(Relinker, type); type.Interfaces[i] = newInterf; #endif } // Don't foreach when modifying the collection for (int i = 0; i < type.CustomAttributes.Count; i++) type.CustomAttributes[i] = type.CustomAttributes[i].Relink(Relinker, type); foreach (PropertyDefinition prop in type.Properties) { prop.PropertyType = prop.PropertyType.Relink(Relinker, type); // Don't foreach when modifying the collection for (int i = 0; i < prop.CustomAttributes.Count; i++) prop.CustomAttributes[i] = prop.CustomAttributes[i].Relink(Relinker, type); } foreach (EventDefinition eventDef in type.Events) { eventDef.EventType = eventDef.EventType.Relink(Relinker, type); for (int i = 0; i < eventDef.CustomAttributes.Count; i++) eventDef.CustomAttributes[i] = eventDef.CustomAttributes[i].Relink(Relinker, type); } foreach (MethodDefinition method in type.Methods) PatchRefsInMethod(method); foreach (FieldDefinition field in type.Fields) { field.FieldType = field.FieldType.Relink(Relinker, type); // Don't foreach when modifying the collection for (int i = 0; i < field.CustomAttributes.Count; i++) field.CustomAttributes[i] = field.CustomAttributes[i].Relink(Relinker, type); } for (int i = 0; i < type.NestedTypes.Count; i++) PatchRefsInType(type.NestedTypes[i]); } public virtual void PatchRefsInMethod(MethodDefinition method) { LogVerbose($"[VERBOSE] [PatchRefsInMethod] Patching refs in {method}"); // Don't foreach when modifying the collection for (int i = 0; i < method.GenericParameters.Count; i++) method.GenericParameters[i] = method.GenericParameters[i].Relink(Relinker, method); foreach (ParameterDefinition param in method.Parameters) { param.ParameterType = param.ParameterType.Relink(Relinker, method); for (int i = 0; i < param.CustomAttributes.Count; i++) param.CustomAttributes[i] = param.CustomAttributes[i].Relink(Relinker, method); } for (int i = 0; i < method.CustomAttributes.Count; i++) method.CustomAttributes[i] = method.CustomAttributes[i].Relink(Relinker, method); for (int i = 0; i < method.Overrides.Count; i++) method.Overrides[i] = method.Overrides[i].Relink(Relinker, method); method.ReturnType = method.ReturnType.Relink(Relinker, method); if (method.Body == null) return; foreach (VariableDefinition var in method.Body.Variables) var.VariableType = var.VariableType.Relink(Relinker, method); foreach (ExceptionHandler handler in method.Body.ExceptionHandlers) if (handler.CatchType != null) handler.CatchType = handler.CatchType.Relink(Relinker, method); MethodRewriter?.Invoke(this, method); Dictionary tmpAddrLocMap = new Dictionary(); MethodBody body = method.Body; for (int instri = 0; method.HasBody && instri < body.Instructions.Count; instri++) { Instruction instr = body.Instructions[instri]; object operand = instr.Operand; // MonoMod-specific in-code flag setting / ... // TODO: Split out the MonoMod inline parsing. if (!MethodParser(this, body, instr, ref instri)) continue; // Before relinking, check for an existing forced call opcode mapping. OpCode forceCall = default; bool hasForceCall = operand is MethodReference && ( ForceCallMap.TryGetValue((operand as MethodReference).GetFindableID(), out forceCall) || ForceCallMap.TryGetValue((operand as MethodReference).GetFindableID(simple: true), out forceCall) ); // General relinking if (!(operand is ParameterDefinition) && operand is IMetadataTokenProvider) operand = ((IMetadataTokenProvider) operand).Relink(Relinker, method); // Check again after relinking. if (!hasForceCall && operand is MethodReference) { bool hasForceCallRelinked = ForceCallMap.TryGetValue((operand as MethodReference).GetFindableID(), out OpCode forceCallRelinked) || ForceCallMap.TryGetValue((operand as MethodReference).GetFindableID(simple: true), out forceCallRelinked) ; // If a relinked force call exists, prefer it over the existing forced call opcode. // Otherwise keep the existing forced call opcode. if (hasForceCallRelinked) { forceCall = forceCallRelinked; hasForceCall = true; } } // patch_ constructor fix: If referring to itself, refer to the original constructor. if (instr.OpCode == OpCodes.Call && operand is MethodReference && (((MethodReference) operand).Name == ".ctor" || ((MethodReference) operand).Name == ".cctor") && ((MethodReference) operand).FullName == method.FullName) { // ((MethodReference) operand).Name = method.GetOriginalName(); // Above could be enough, but what about the metadata token? operand = method.DeclaringType.FindMethod(method.GetFindableID(name: method.GetOriginalName())); } // .ctor -> static method reference fix: newobj -> call if ((instr.OpCode == OpCodes.Newobj || instr.OpCode == OpCodes.Newarr) && operand is MethodReference && ((MethodReference) operand).Name != ".ctor") { instr.OpCode = ((MethodReference) operand).IsCallvirt() ? OpCodes.Callvirt : OpCodes.Call; // field -> property reference fix: ld(s)fld(a) / st(s)fld(a) <-> call get / set } else if ((instr.OpCode == OpCodes.Ldfld || instr.OpCode == OpCodes.Ldflda || instr.OpCode == OpCodes.Stfld || instr.OpCode == OpCodes.Ldsfld || instr.OpCode == OpCodes.Ldsflda || instr.OpCode == OpCodes.Stsfld) && operand is PropertyReference) { PropertyDefinition prop = ((PropertyReference) operand).Resolve(); if (instr.OpCode == OpCodes.Ldfld || instr.OpCode == OpCodes.Ldflda || instr.OpCode == OpCodes.Ldsfld || instr.OpCode == OpCodes.Ldsflda) operand = prop.GetMethod; else { operand = prop.SetMethod; } if (instr.OpCode == OpCodes.Ldflda || instr.OpCode == OpCodes.Ldsflda) body.AppendGetAddr(instr, prop.PropertyType, tmpAddrLocMap); instr.OpCode = ((MethodReference) operand).IsCallvirt() ? OpCodes.Callvirt : OpCodes.Call; // field <-> method reference fix: ld(s)fld / st(s)fld <-> call } else if ((instr.OpCode == OpCodes.Ldfld || instr.OpCode == OpCodes.Ldflda || instr.OpCode == OpCodes.Stfld) && operand is MethodReference) { if (instr.OpCode == OpCodes.Ldflda) body.AppendGetAddr(instr, ((PropertyReference) operand).PropertyType, tmpAddrLocMap); instr.OpCode = ((MethodReference) operand).IsCallvirt() ? OpCodes.Callvirt : OpCodes.Call; } else if ((instr.OpCode == OpCodes.Ldsfld || instr.OpCode == OpCodes.Ldsflda || instr.OpCode == OpCodes.Stsfld) && operand is MethodReference) { if (instr.OpCode == OpCodes.Ldsflda) body.AppendGetAddr(instr, ((PropertyReference) operand).PropertyType, tmpAddrLocMap); instr.OpCode = OpCodes.Call; } else if ((instr.OpCode == OpCodes.Callvirt || instr.OpCode == OpCodes.Call) && operand is FieldReference) { // Setters don't return anything. TypeReference returnType = ((MethodReference) instr.Operand).ReturnType; bool set = returnType == null || returnType.MetadataType == MetadataType.Void; // This assumption is dangerous. bool instance = ((MethodReference) instr.Operand).HasThis; if (instance) instr.OpCode = set ? OpCodes.Stfld : OpCodes.Ldfld; else instr.OpCode = set ? OpCodes.Stsfld : OpCodes.Ldsfld; // TODO: When should we emit ldflda / ldsflda? } // "general" static method <-> virtual method reference fix: call <-> callvirt else if ((instr.OpCode == OpCodes.Call || instr.OpCode == OpCodes.Callvirt) && operand is MethodReference) { if (hasForceCall) { instr.OpCode = forceCall; } else if (!body.IsBaseMethodCall(operand as MethodReference)) { instr.OpCode = ((MethodReference) operand).IsCallvirt() ? OpCodes.Callvirt : OpCodes.Call; } } // Reference importing if (operand is IMetadataTokenProvider) operand = method.Module.ImportReference((IMetadataTokenProvider) operand); instr.Operand = operand; MethodBodyRewriter?.Invoke(this, body, instr, instri); } } #endregion #region Cleanup Pass public virtual void Cleanup(bool all = false) { for (int i = 0; i < Module.Types.Count; i++) { TypeDefinition type = Module.Types[i]; if (all && (type.Namespace.StartsWith("MonoMod") || type.Name.StartsWith("MonoMod"))) { Log($"[Cleanup] Removing type {type.Name}"); Module.Types.RemoveAt(i); i--; continue; } CleanupType(type, all: all); } Collection deps = Module.AssemblyReferences; for (int i = deps.Count - 1; i > -1; --i) if ((all && deps[i].Name.StartsWith("MonoMod")) || (RemovePatchReferences && deps[i].Name.EndsWith(".mm"))) deps.RemoveAt(i); } public virtual void CleanupType(TypeDefinition type, bool all = false) { Cleanup(type, all: all); foreach (PropertyDefinition prop in type.Properties) Cleanup(prop, all: all); foreach (MethodDefinition method in type.Methods) Cleanup(method, all: all); foreach (FieldDefinition field in type.Fields) Cleanup(field, all: all); foreach (EventDefinition eventDef in type.Events) Cleanup(eventDef, all: all); foreach (TypeDefinition nested in type.NestedTypes) CleanupType(nested, all: all); } public virtual void Cleanup(ICustomAttributeProvider cap, bool all = false) { Collection attribs = cap.CustomAttributes; for (int i = attribs.Count - 1; i > -1; --i) { TypeReference attribType = attribs[i].AttributeType; if (ShouldCleanupAttrib?.Invoke(cap, attribType) ?? ( (attribType.Scope.Name == "MonoMod" || attribType.Scope.Name == "MonoMod.exe" || attribType.Scope.Name == "MonoMod.dll") || (attribType.FullName.StartsWith("MonoMod.MonoMod")) )) { attribs.RemoveAt(i); } } } #endregion #region Default PostProcessor Pass public virtual void DefaultPostProcessor(MonoModder modder) { foreach (TypeDefinition type in Module.Types) DefaultPostProcessType(type); if (CleanupEnabled) Cleanup(all: Environment.GetEnvironmentVariable("MONOMOD_CLEANUP_ALL") == "1"); } public virtual void DefaultPostProcessType(TypeDefinition type) { if (PublicEverything || type.HasMMAttribute("Public")) type.SetPublic(true); RunCustomAttributeHandlers(type); foreach (EventDefinition eventDef in type.Events) { if (PublicEverything || eventDef.HasMMAttribute("Public")) { eventDef.SetPublic(true); eventDef.AddMethod?.SetPublic(true); eventDef.RemoveMethod?.SetPublic(true); foreach (MethodDefinition method in eventDef.OtherMethods) method.SetPublic(true); } RunCustomAttributeHandlers(eventDef); } foreach (PropertyDefinition prop in type.Properties) { if (PublicEverything || prop.HasMMAttribute("Public")) { prop.SetPublic(true); prop.GetMethod?.SetPublic(true); prop.SetMethod?.SetPublic(true); foreach (MethodDefinition method in prop.OtherMethods) method.SetPublic(true); } RunCustomAttributeHandlers(prop); } foreach (MethodDefinition method in type.Methods) { if (PublicEverything || method.HasMMAttribute("Public")) method.SetPublic(true); if (PreventInline && method.HasBody) { method.NoInlining = true; // Remove AggressiveInlining method.ImplAttributes &= (MethodImplAttributes) 0x0100; } method.ConvertShortLongOps(); RunCustomAttributeHandlers(method); } foreach (FieldDefinition field in type.Fields) { if (PublicEverything || field.HasMMAttribute("Public")) field.SetPublic(true); RunCustomAttributeHandlers(field); } foreach (TypeDefinition nested in type.NestedTypes) DefaultPostProcessType(nested); } #endregion #region MonoMod injected types public virtual TypeDefinition PatchWasHere() { for (int ti = 0; ti < Module.Types.Count; ti++) { if (Module.Types[ti].Namespace == "MonoMod" && Module.Types[ti].Name == "WasHere") { LogVerbose("[PatchWasHere] Type MonoMod.WasHere already existing"); return Module.Types[ti]; } } LogVerbose("[PatchWasHere] Adding type MonoMod.WasHere"); TypeDefinition wasHere = new TypeDefinition("MonoMod", "WasHere", TypeAttributes.Public | TypeAttributes.Class) { BaseType = Module.TypeSystem.Object }; Module.Types.Add(wasHere); return wasHere; } protected MethodDefinition _mmOriginalCtor; public virtual MethodReference GetMonoModOriginalCtor() { if (_mmOriginalCtor != null && _mmOriginalCtor.Module != Module) { _mmOriginalCtor = null; } if (_mmOriginalCtor != null) { return _mmOriginalCtor; } TypeDefinition attrType = null; for (int ti = 0; ti < Module.Types.Count; ti++) { if (Module.Types[ti].Namespace == "MonoMod" && Module.Types[ti].Name == "MonoModOriginal") { attrType = Module.Types[ti]; for (int mi = 0; mi < attrType.Methods.Count; mi++) { if (!attrType.Methods[mi].IsConstructor || attrType.Methods[mi].IsStatic) { continue; } return _mmOriginalCtor = attrType.Methods[mi]; } } } LogVerbose("[MonoModOriginal] Adding MonoMod.MonoModOriginal"); attrType = attrType ?? new TypeDefinition("MonoMod", "MonoModOriginal", TypeAttributes.Public | TypeAttributes.Class) { BaseType = Module.ImportReference(typeof(Attribute)) }; _mmOriginalCtor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, Module.TypeSystem.Void ); _mmOriginalCtor.MetadataToken = GetMetadataToken(TokenType.Method); _mmOriginalCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); _mmOriginalCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Module.ImportReference( typeof(Attribute).GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)[0] ))); _mmOriginalCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); attrType.Methods.Add(_mmOriginalCtor); Module.Types.Add(attrType); return _mmOriginalCtor; } protected MethodDefinition _mmOriginalNameCtor; public virtual MethodReference GetMonoModOriginalNameCtor() { if (_mmOriginalNameCtor != null && _mmOriginalNameCtor.Module != Module) { _mmOriginalNameCtor = null; } if (_mmOriginalNameCtor != null) { return _mmOriginalNameCtor; } TypeDefinition attrType = null; for (int ti = 0; ti < Module.Types.Count; ti++) { if (Module.Types[ti].Namespace == "MonoMod" && Module.Types[ti].Name == "MonoModOriginalName") { attrType = Module.Types[ti]; for (int mi = 0; mi < attrType.Methods.Count; mi++) { if (!attrType.Methods[mi].IsConstructor || attrType.Methods[mi].IsStatic) { continue; } return _mmOriginalNameCtor = attrType.Methods[mi]; } } } LogVerbose("[MonoModOriginalName] Adding MonoMod.MonoModOriginalName"); attrType = attrType ?? new TypeDefinition("MonoMod", "MonoModOriginalName", TypeAttributes.Public | TypeAttributes.Class) { BaseType = Module.ImportReference(typeof(Attribute)) }; _mmOriginalNameCtor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, Module.TypeSystem.Void ); _mmOriginalNameCtor.Parameters.Add(new ParameterDefinition("n", ParameterAttributes.None, Module.TypeSystem.String)); _mmOriginalNameCtor.MetadataToken = GetMetadataToken(TokenType.Method); _mmOriginalNameCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); _mmOriginalNameCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Module.ImportReference( typeof(Attribute).GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)[0] ))); _mmOriginalNameCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); attrType.Methods.Add(_mmOriginalNameCtor); Module.Types.Add(attrType); return _mmOriginalNameCtor; } protected MethodDefinition _mmAddedCtor; public virtual MethodReference GetMonoModAddedCtor() { if (_mmAddedCtor != null && _mmAddedCtor.Module != Module) { _mmAddedCtor = null; } if (_mmAddedCtor != null) { return _mmAddedCtor; } TypeDefinition attrType = null; for (int ti = 0; ti < Module.Types.Count; ti++) { if (Module.Types[ti].Namespace == "MonoMod" && Module.Types[ti].Name == "MonoModAdded") { attrType = Module.Types[ti]; for (int mi = 0; mi < attrType.Methods.Count; mi++) { if (!attrType.Methods[mi].IsConstructor || attrType.Methods[mi].IsStatic) { continue; } return _mmAddedCtor = attrType.Methods[mi]; } } } LogVerbose("[MonoModAdded] Adding MonoMod.MonoModAdded"); attrType = attrType ?? new TypeDefinition("MonoMod", "MonoModAdded", TypeAttributes.Public | TypeAttributes.Class) { BaseType = Module.ImportReference(typeof(Attribute)) }; _mmAddedCtor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, Module.TypeSystem.Void ); _mmAddedCtor.MetadataToken = GetMetadataToken(TokenType.Method); _mmAddedCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); _mmAddedCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Module.ImportReference( typeof(Attribute).GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)[0] ))); _mmAddedCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); attrType.Methods.Add(_mmAddedCtor); Module.Types.Add(attrType); return _mmAddedCtor; } protected MethodDefinition _mmPatchCtor; public virtual MethodReference GetMonoModPatchCtor() { if (_mmPatchCtor != null && _mmPatchCtor.Module != Module) { _mmPatchCtor = null; } if (_mmPatchCtor != null) { return _mmPatchCtor; } TypeDefinition attrType = null; for (int ti = 0; ti < Module.Types.Count; ti++) { if (Module.Types[ti].Namespace == "MonoMod" && Module.Types[ti].Name == "MonoModPatch") { attrType = Module.Types[ti]; for (int mi = 0; mi < attrType.Methods.Count; mi++) { if (!attrType.Methods[mi].IsConstructor || attrType.Methods[mi].IsStatic) { continue; } return _mmPatchCtor = attrType.Methods[mi]; } } } LogVerbose("[MonoModPatch] Adding MonoMod.MonoModPatch"); attrType = attrType ?? new TypeDefinition("MonoMod", "MonoModPatch", TypeAttributes.Public | TypeAttributes.Class) { BaseType = Module.ImportReference(typeof(Attribute)) }; _mmPatchCtor = new MethodDefinition(".ctor", MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName, Module.TypeSystem.Void ); _mmPatchCtor.Parameters.Add(new ParameterDefinition("name", ParameterAttributes.None, Module.TypeSystem.String)); _mmPatchCtor.MetadataToken = GetMetadataToken(TokenType.Method); _mmPatchCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0)); _mmPatchCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Module.ImportReference( typeof(Attribute).GetConstructors(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)[0] ))); _mmPatchCtor.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); attrType.Methods.Add(_mmPatchCtor); Module.Types.Add(attrType); return _mmPatchCtor; } #endregion #region Helper methods /// /// Creates a new non-conflicting MetadataToken. /// /// The type of the new token. /// A MetadataToken with an unique RID for the target module. public virtual MetadataToken GetMetadataToken(TokenType type) { /* Notes: * * Mono.Cecil does a great job fixing tokens anyway. * * The ModuleDef must be constructed with a reader, thus * from an image, as that is the only way a MetadataReader * gets assigned to the ModuleDef. * * At the same time, the module has only got a file name when * it has been passed on from the image it has been created from. * * Creating an image from a name-less stream results in an empty string. */ #if !CECIL0_9 if (Module.FileName == null) { ++CurrentRID; } else #endif { try { while (Module.LookupToken(CurrentRID | (int) type) != null) { ++CurrentRID; } } catch { ++CurrentRID; } } return new MetadataToken(type, CurrentRID); } /// /// Checks if the method has a special name that is "allowed" to be patched. /// /// true if the special name used in the method is allowed, false otherwise. /// Method to check. public virtual bool AllowedSpecialName(MethodDefinition method, TypeDefinition targetType = null) { if (method.HasMMAttribute("Added") || method.DeclaringType.HasMMAttribute("Added") || (targetType?.HasMMAttribute("Added") ?? false)) { return true; } // HOW NOT TO SOLVE ISSUES: // if (method.IsConstructor) // return true; // We don't give a f**k anymore. // The legacy behaviour is required to not break anything. It's very, very finnicky. // In retrospect, taking the above "fix" into consideration, it was bound to fail as soon // as other ignored members were accessed from the new constructors. if (method.IsConstructor && (method.HasCustomAttributes || method.IsStatic)) { if (method.IsStatic) return true; // Overriding the constructor manually is generally a horrible idea, but who knows where it may be used. if (method.HasMMAttribute("Constructor")) return true; } if (method.IsGetter || method.IsSetter) return true; if (method.Name.StartsWith("op_")) return true; return !method.IsRuntimeSpecialName; // Formerly SpecialName. If something breaks, blame UnderRail. } #endregion } }