using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; using Mono.Cecil; using Mono.Cecil.Cil; namespace StardewModdingAPI.Framework.ModLoading { /// Provides helper methods for field rewriters. internal static class RewriteHelper { /********* ** Properties *********/ /// A pattern matching type name substrings to strip for display. private static readonly Regex StripTypeNamePattern = new Regex(@"`\d+(?=<)", RegexOptions.Compiled); /********* ** Public methods *********/ /// Get the field reference from an instruction if it matches. /// The IL instruction. public static FieldReference AsFieldReference(Instruction instruction) { return instruction.OpCode == OpCodes.Ldfld || instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Stfld || instruction.OpCode == OpCodes.Stsfld ? (FieldReference)instruction.Operand : null; } /// Get the method reference from an instruction if it matches. /// The IL instruction. public static MethodReference AsMethodReference(Instruction instruction) { return instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt ? (MethodReference)instruction.Operand : null; } /// Get whether a type matches a type reference. /// The defined type. /// The type reference. public static bool IsSameType(Type type, TypeReference reference) { // same namespace & name if (type.Namespace != reference.Namespace || type.Name != reference.Name) return false; // same generic parameters if (type.IsGenericType) { if (!reference.IsGenericInstance) return false; Type[] defGenerics = type.GetGenericArguments(); TypeReference[] refGenerics = ((GenericInstanceType)reference).GenericArguments.ToArray(); if (defGenerics.Length != refGenerics.Length) return false; for (int i = 0; i < defGenerics.Length; i++) { if (!RewriteHelper.IsSameType(defGenerics[i], refGenerics[i])) return false; } } return true; } /// Get whether a method definition matches the signature expected by a method reference. /// The method definition. /// The method reference. public static bool HasMatchingSignature(MethodInfo definition, MethodReference reference) { // same name if (definition.Name != reference.Name) return false; // same arguments ParameterInfo[] definitionParameters = definition.GetParameters(); ParameterDefinition[] referenceParameters = reference.Parameters.ToArray(); if (referenceParameters.Length != definitionParameters.Length) return false; for (int i = 0; i < referenceParameters.Length; i++) { if (!RewriteHelper.IsSameType(definitionParameters[i].ParameterType, referenceParameters[i].ParameterType)) return false; } return true; } /// Get whether a type has a method whose signature matches the one expected by a method reference. /// The type to check. /// The method reference. public static bool HasMatchingSignature(Type type, MethodReference reference) { return type .GetMethods(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public) .Any(method => RewriteHelper.HasMatchingSignature(method, reference)); } /// Determine whether this type ID has a placeholder such as !0. /// The type to check. /// true if the type ID contains a placeholder, false if not. public static bool HasPlaceholder(string typeID) { return typeID.Contains("!0"); } /// returns whether this type ID is a placeholder, i.e., it begins with "!". /// The symbol to validate. /// true if the symbol is a placeholder, false if not public static bool IsPlaceholder(string symbol) { return symbol.StartsWith("!"); } /// Determine whether two type IDs look like the same type, accounting for placeholder values such as !0. /// The type ID to compare. /// The other type ID to compare. /// true if the type IDs look like the same type, false if not. public static bool LooksLikeSameType(TypeReference a, TypeReference b) { string typeA = RewriteHelper.GetComparableTypeID(a); string typeB = RewriteHelper.GetComparableTypeID(b); string placeholderType = "", actualType = ""; if (RewriteHelper.HasPlaceholder(typeA)) { placeholderType = typeA; actualType = typeB; } else if (RewriteHelper.HasPlaceholder(typeB)) { placeholderType = typeB; actualType = typeA; } else return typeA == typeB; return RewriteHelper.PlaceholderTypeValidates(placeholderType, actualType); } /// Get a unique string representation of a type. /// The type reference. private static string GetComparableTypeID(TypeReference type) { return RewriteHelper.StripTypeNamePattern.Replace(type.FullName, ""); } protected class SymbolLocation { public string symbol; public int depth; public SymbolLocation(string symbol, int depth) { this.symbol = symbol; this.depth = depth; } } private static List symbolBoundaries = new List { '<', '>', ',' }; /// Traverses and parses out symbols from a type which does not contain placeholder values. /// The type to traverse. /// A List in which to store the parsed symbols. private static void TraverseActualType(string type, List typeSymbols) { int depth = 0; string symbol = ""; foreach (char c in type) { if (RewriteHelper.symbolBoundaries.Contains(c)) { typeSymbols.Add(new SymbolLocation(symbol, depth)); symbol = ""; switch (c) { case '<': depth++; break; case '>': depth--; break; default: break; } } else symbol += c; } } /// Determines whether two symbols in a type ID match, accounting for placeholders such as !0. /// A symbol in a typename which contains placeholders. /// A symbol in a typename which does not contain placeholders. /// A dictionary containing a mapping of placeholders to concrete types. /// true if the symbols match, false if not. private static bool SymbolsMatch(SymbolLocation symbolA, SymbolLocation symbolB, Dictionary placeholderMap) { if (symbolA.depth != symbolB.depth) return false; if (!RewriteHelper.IsPlaceholder(symbolA.symbol)) { return symbolA.symbol == symbolB.symbol; } if (placeholderMap.ContainsKey(symbolA.symbol)) { return placeholderMap[symbolA.symbol] == symbolB.symbol; } placeholderMap[symbolA.symbol] = symbolB.symbol; return true; } /// Determines whether a type which has placeholders correctly resolves to the concrete type provided. /// A type containing placeholders such as !0. /// The list of symbols extracted from the concrete type. /// true if the type resolves correctly, false if not. private static bool PlaceholderTypeResolvesToActualType(string type, List typeSymbols) { Dictionary placeholderMap = new Dictionary(); int depth = 0, symbolCount = 0; string symbol = ""; foreach (char c in type) { if (symbolBoundaries.Contains(c)) { bool match = RewriteHelper.SymbolsMatch(new SymbolLocation(symbol, depth), typeSymbols[symbolCount], placeholderMap); if (typeSymbols.Count <= symbolCount || !match) return false; symbolCount++; symbol = ""; switch (c) { case '<': depth++; break; case '>': depth--; break; default: break; } } else symbol += c; } return true; } /// Determines whether a type with placeholders in it matches a type without placeholders. /// The type with placeholders in it. /// The type without placeholders. /// true if the placeholder type can resolve to the actual type, false if not. private static bool PlaceholderTypeValidates(string placeholderType, string actualType) { List typeSymbols = new List(); RewriteHelper.TraverseActualType(actualType, typeSymbols); return PlaceholderTypeResolvesToActualType(placeholderType, typeSymbols); } } }