2209 lines
102 KiB
C#
2209 lines
102 KiB
C#
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<string, object> RelinkMap = new Dictionary<string, object>();
|
|
public IDictionary<string, object> _RelinkMap { get => RelinkMap; set => RelinkMap = (Dictionary<string, object>) value; }
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<string, ModuleDefinition> RelinkModuleMap = new Dictionary<string, ModuleDefinition>();
|
|
public IDictionary<string, ModuleDefinition> _RelinkModuleMap { get => RelinkModuleMap; set => RelinkModuleMap = (Dictionary<string, ModuleDefinition>) value; }
|
|
public HashSet<string> SkipList = new HashSet<string>(EqualityComparer<string>.Default);
|
|
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<string, IMetadataTokenProvider> RelinkMapCache = new Dictionary<string, IMetadataTokenProvider>();
|
|
public IDictionary<string, IMetadataTokenProvider> _RelinkMapCache { get => RelinkMapCache; set => RelinkMapCache = (Dictionary<string, IMetadataTokenProvider>) value; }
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<string, TypeReference> RelinkModuleMapCache = new Dictionary<string, TypeReference>();
|
|
public IDictionary<string, TypeReference> _RelinkModuleMapCache { get => RelinkModuleMapCache; set => RelinkModuleMapCache = (Dictionary<string, TypeReference>) value; }
|
|
|
|
public Dictionary<string, OpCode> ForceCallMap = new Dictionary<string, OpCode>();
|
|
|
|
public ModReadEventHandler OnReadMod;
|
|
public PostProcessor PostProcessors;
|
|
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<string, FastReflectionDelegate> CustomAttributeHandlers = new Dictionary<string, FastReflectionDelegate>();
|
|
public IDictionary<string, FastReflectionDelegate> _CustomAttributeHandlers { get => CustomAttributeHandlers; set => CustomAttributeHandlers = (Dictionary<string, FastReflectionDelegate>) value; }
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<string, FastReflectionDelegate> CustomMethodAttributeHandlers = new Dictionary<string, FastReflectionDelegate>();
|
|
public IDictionary<string, FastReflectionDelegate> _CustomMethodAttributeHandlers { get => CustomMethodAttributeHandlers; set => CustomMethodAttributeHandlers = (Dictionary<string, FastReflectionDelegate>) 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<string> DependencyDirs = new List<string>();
|
|
public ModuleDefinition Module;
|
|
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<ModuleDefinition, List<ModuleDefinition>> DependencyMap = new Dictionary<ModuleDefinition, List<ModuleDefinition>>();
|
|
public IDictionary<ModuleDefinition, List<ModuleDefinition>> _DependencyMap { get => DependencyMap; set => DependencyMap = (Dictionary<ModuleDefinition, List<ModuleDefinition>>) value; }
|
|
[MonoMod__WasIDictionary__]
|
|
public Dictionary<string, ModuleDefinition> DependencyCache = new Dictionary<string, ModuleDefinition>();
|
|
public IDictionary<string, ModuleDefinition> _DependencyCache { get => DependencyCache; set => DependencyCache = (Dictionary<string, ModuleDefinition>) value; }
|
|
|
|
public Func<ICustomAttributeProvider, TypeReference, bool> ShouldCleanupAttrib;
|
|
|
|
public bool LogVerboseEnabled;
|
|
public bool RelinkerCacheEnabled;
|
|
public bool CleanupEnabled;
|
|
public bool PublicEverything;
|
|
|
|
public List<ModuleReference> Mods = new List<ModuleReference>();
|
|
|
|
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<string> paths = new List<string>();
|
|
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<string, ModuleDefinition> 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<ModuleDefinition> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reads the main module from the Input stream / InputPath file to Module.
|
|
/// </summary>
|
|
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<ModuleDefinition>();
|
|
|
|
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<ModuleDefinition> mapped))
|
|
DependencyMap[main] = mapped = new List<ModuleDefinition>();
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write the modded module to the given stream or the default output.
|
|
/// </summary>
|
|
/// <param name="output">Output stream. If none given, default Output will be used.</param>
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Automatically mods the module, loading Input, writing the modded module to Output.
|
|
/// </summary>
|
|
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<ModuleDefinition>()) ?? Module.GetType(name, false);
|
|
public virtual TypeReference FindType(string name, bool runtimeName)
|
|
=> FindType(Module, name, new Stack<ModuleDefinition>()) ?? Module.GetType(name, runtimeName);
|
|
protected virtual TypeReference FindType(ModuleDefinition main, string fullName, Stack<ModuleDefinition> crawled) {
|
|
TypeReference type;
|
|
if ((type = main.GetType(fullName, false)) != null)
|
|
return type;
|
|
if (fullName.StartsWith("<PrivateImplementationDetails>/"))
|
|
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<ModuleDefinition> crawled = new Stack<ModuleDefinition>();
|
|
// 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
|
|
/// <summary>
|
|
/// Pre-Patches the module (adds new types, module references, resources, ...).
|
|
/// </summary>
|
|
/// <param name="mod">Mod to patch into the input module.</param>
|
|
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()
|
|
));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patches the type (adds new types).
|
|
/// </summary>
|
|
/// <param name="type">Type to patch into the input module.</param>
|
|
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
|
|
/// <summary>
|
|
/// Patches the module (adds new type members).
|
|
/// </summary>
|
|
/// <param name="mod">Mod to patch into the input module.</param>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Patches the type (adds new members).
|
|
/// </summary>
|
|
/// <param name="type">Type to patch into the input module.</param>
|
|
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<MethodDefinition> propMethods = new HashSet<MethodDefinition>(); // In the Patch pass, prop methods exist twice.
|
|
foreach (PropertyDefinition prop in type.Properties)
|
|
PatchProperty(targetTypeDef, prop, propMethods);
|
|
|
|
HashSet<MethodDefinition> eventMethods = new HashSet<MethodDefinition>(); // 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<MethodDefinition> 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<MethodDefinition> 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<Instruction> 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<AssemblyNameReference> mscorlibDeps = new List<AssemblyNameReference>();
|
|
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<ModuleReference> modules = new List<ModuleReference>(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<TypeReference, VariableDefinition> tmpAddrLocMap = new Dictionary<TypeReference, VariableDefinition>();
|
|
|
|
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<AssemblyNameReference> 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<CustomAttribute> 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
|
|
/// <summary>
|
|
/// Creates a new non-conflicting MetadataToken.
|
|
/// </summary>
|
|
/// <param name="type">The type of the new token.</param>
|
|
/// <returns>A MetadataToken with an unique RID for the target module.</returns>
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if the method has a special name that is "allowed" to be patched.
|
|
/// </summary>
|
|
/// <returns><c>true</c> if the special name used in the method is allowed, <c>false</c> otherwise.</returns>
|
|
/// <param name="method">Method to check.</param>
|
|
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
|
|
|
|
}
|
|
}
|