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);
}
}
}