2025-01-01 19:17:56 -05:00
|
|
|
|
using dnlib.DotNet;
|
|
|
|
|
using ReCodeItLib.Models;
|
|
|
|
|
using ReCodeItLib.Utils;
|
|
|
|
|
|
|
|
|
|
namespace ReCodeItLib.ReMapper;
|
|
|
|
|
|
2025-01-01 20:07:20 -05:00
|
|
|
|
public class AutoMatcher(List<RemapModel> mappings, string mappingPath)
|
2025-01-01 19:17:56 -05:00
|
|
|
|
{
|
|
|
|
|
private ModuleDefMD? Module { get; set; }
|
|
|
|
|
|
|
|
|
|
private List<TypeDef>? CandidateTypes { get; set; }
|
|
|
|
|
|
|
|
|
|
private static List<string> _tokens = DataProvider.Settings!.Remapper!.TokensToMatch;
|
|
|
|
|
|
|
|
|
|
public void AutoMatch(string assemblyPath, string oldTypeName, string newTypeName)
|
|
|
|
|
{
|
|
|
|
|
assemblyPath = AssemblyUtils.TryDeObfuscate(
|
|
|
|
|
DataProvider.LoadModule(assemblyPath),
|
|
|
|
|
assemblyPath,
|
|
|
|
|
out var module);
|
|
|
|
|
|
|
|
|
|
Module = module;
|
|
|
|
|
CandidateTypes = Module.GetTypes()
|
|
|
|
|
.Where(t => _tokens.Any(token => t.Name.StartsWith(token)))
|
|
|
|
|
// .Where(t => t.Name != oldTypeName)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
var targetTypeDef = FindTargetType(oldTypeName);
|
2025-01-01 20:37:49 -05:00
|
|
|
|
|
|
|
|
|
if (targetTypeDef is null)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogSync($"Could not target type: {oldTypeName}", ConsoleColor.Red);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-01-01 19:17:56 -05:00
|
|
|
|
|
|
|
|
|
Logger.LogSync($"Found target type: {targetTypeDef!.FullName}", ConsoleColor.Green);
|
|
|
|
|
|
|
|
|
|
var remapModel = new RemapModel();
|
|
|
|
|
remapModel.NewTypeName = newTypeName;
|
|
|
|
|
|
|
|
|
|
StartFilter(targetTypeDef, remapModel, assemblyPath);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private TypeDef? FindTargetType(string oldTypeName)
|
|
|
|
|
{
|
|
|
|
|
return Module!.GetTypes().FirstOrDefault(t => t.FullName == oldTypeName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void StartFilter(TypeDef target, RemapModel remapModel, string assemblyPath)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogSync($"Starting Candidates: {CandidateTypes!.Count}", ConsoleColor.Yellow);
|
|
|
|
|
|
|
|
|
|
// Purpose of this pass is to eliminate any types that have no matching parameters
|
|
|
|
|
foreach (var candidate in CandidateTypes!.ToList())
|
|
|
|
|
{
|
|
|
|
|
if (!PassesGeneralChecks(target, candidate, remapModel.SearchParams.GenericParams))
|
|
|
|
|
{
|
|
|
|
|
CandidateTypes!.Remove(candidate);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ContainsTargetMethods(target, candidate, remapModel.SearchParams.Methods))
|
|
|
|
|
{
|
|
|
|
|
CandidateTypes!.Remove(candidate);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ContainsTargetFields(target, candidate, remapModel.SearchParams.Fields))
|
|
|
|
|
{
|
|
|
|
|
CandidateTypes!.Remove(candidate);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!ContainsTargetProperties(target, candidate, remapModel.SearchParams.Properties))
|
|
|
|
|
{
|
|
|
|
|
CandidateTypes!.Remove(candidate);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (CandidateTypes!.Count == 1)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogSync("Narrowed candidates down to one. Testing generated model...", ConsoleColor.Green);
|
|
|
|
|
|
|
|
|
|
var tmpList = new List<RemapModel>()
|
|
|
|
|
{
|
|
|
|
|
remapModel
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
new ReMapper().InitializeRemap(tmpList, assemblyPath, validate: true);
|
2025-01-01 20:07:20 -05:00
|
|
|
|
|
|
|
|
|
ProcessEndQuestions(remapModel, assemblyPath);
|
2025-01-01 19:17:56 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool PassesGeneralChecks(TypeDef target, TypeDef candidate, GenericParams parms)
|
|
|
|
|
{
|
|
|
|
|
if (target.IsPublic != candidate.IsPublic) return false;
|
|
|
|
|
if (target.IsAbstract != candidate.IsAbstract) return false;
|
|
|
|
|
if (target.IsInterface != candidate.IsInterface) return false;
|
|
|
|
|
if (target.IsEnum != candidate.IsEnum) return false;
|
|
|
|
|
if (target.IsValueType != candidate.IsValueType) return false;
|
|
|
|
|
if (target.HasGenericParameters != candidate.HasGenericParameters) return false;
|
|
|
|
|
if (target.IsNested != candidate.IsNested) return false;
|
|
|
|
|
if (target.IsSealed != candidate.IsSealed) return false;
|
|
|
|
|
if (target.HasCustomAttributes != candidate.HasCustomAttributes) return false;
|
|
|
|
|
|
|
|
|
|
parms.IsPublic = target.IsPublic;
|
|
|
|
|
parms.IsAbstract = target.IsAbstract;
|
|
|
|
|
parms.IsInterface = target.IsInterface;
|
|
|
|
|
parms.IsEnum = target.IsEnum;
|
|
|
|
|
parms.IsStruct = target.IsValueType && !target.IsEnum;
|
|
|
|
|
parms.HasGenericParameters = target.HasGenericParameters;
|
|
|
|
|
parms.IsNested = target.IsNested;
|
|
|
|
|
parms.IsSealed = target.IsSealed;
|
|
|
|
|
parms.HasAttribute = target.HasCustomAttributes;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool ContainsTargetMethods(TypeDef target, TypeDef candidate, MethodParams methods)
|
|
|
|
|
{
|
|
|
|
|
// Target has no methods and type has no methods
|
|
|
|
|
if (!target.Methods.Any() && !candidate.Methods.Any())
|
|
|
|
|
{
|
|
|
|
|
methods.MethodCount = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Target has no methods but type has methods
|
|
|
|
|
if (!target.Methods.Any() && candidate.Methods.Any()) return false;
|
|
|
|
|
|
|
|
|
|
// Target has methods but type has no methods
|
|
|
|
|
if (target.Methods.Any() && !candidate.Methods.Any()) return false;
|
|
|
|
|
|
|
|
|
|
// Target has a different number of methods
|
|
|
|
|
if (target.Methods.Count != candidate.Methods.Count) return false;
|
|
|
|
|
|
|
|
|
|
var commonMethods = target.Methods
|
|
|
|
|
.Where(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter)
|
|
|
|
|
.Select(s => s.Name)
|
|
|
|
|
.Intersect(candidate.Methods
|
|
|
|
|
.Where(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter)
|
|
|
|
|
.Select(s => s.Name));
|
|
|
|
|
|
|
|
|
|
// Methods in target that are not in candidate
|
|
|
|
|
var includeMethods = target.Methods
|
|
|
|
|
.Where(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter)
|
|
|
|
|
.Select(s => s.Name.ToString())
|
|
|
|
|
.Except(candidate.Methods
|
|
|
|
|
.Where(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter)
|
|
|
|
|
.Select(s => s.Name.ToString()));
|
|
|
|
|
|
|
|
|
|
// Methods in candidate that are not in target
|
|
|
|
|
var excludeMethods = candidate.Methods
|
|
|
|
|
.Where(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter)
|
|
|
|
|
.Select(s => s.Name.ToString())
|
|
|
|
|
.Except(target.Methods
|
|
|
|
|
.Where(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter)
|
|
|
|
|
.Select(s => s.Name.ToString()));
|
|
|
|
|
|
2025-01-01 20:37:49 -05:00
|
|
|
|
foreach (var include in includeMethods)
|
|
|
|
|
{
|
|
|
|
|
methods.IncludeMethods.Add(include);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var exclude in excludeMethods)
|
|
|
|
|
{
|
|
|
|
|
methods.ExcludeMethods.Add(exclude);
|
|
|
|
|
}
|
2025-01-01 19:17:56 -05:00
|
|
|
|
|
2025-01-01 20:07:20 -05:00
|
|
|
|
methods.MethodCount = target.Methods
|
|
|
|
|
.Count(m => !m.IsConstructor && !m.IsGetter && !m.IsSetter && !m.IsSpecialName);
|
|
|
|
|
|
|
|
|
|
if (target.Methods.Any(m => m.IsConstructor && m.Parameters.Count > 0))
|
|
|
|
|
{
|
|
|
|
|
methods.ConstructorParameterCount = target.Methods.First(m => m.IsConstructor && m.Parameters.Count > 0).Parameters.Count - 1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-01 19:17:56 -05:00
|
|
|
|
return commonMethods.Any();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool ContainsTargetFields(TypeDef target, TypeDef candidate, FieldParams fields)
|
|
|
|
|
{
|
|
|
|
|
// Target has no fields and type has no fields
|
|
|
|
|
if (!target.Fields.Any() && !candidate.Fields.Any())
|
|
|
|
|
{
|
|
|
|
|
fields.FieldCount = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Target has no fields but type has fields
|
|
|
|
|
if (!target.Fields.Any() && candidate.Fields.Any()) return false;
|
|
|
|
|
|
|
|
|
|
// Target has fields but type has no fields
|
|
|
|
|
if (target.Fields.Any() && !candidate.Fields.Any()) return false;
|
|
|
|
|
|
|
|
|
|
// Target has a different number of fields
|
|
|
|
|
if (target.Fields.Count != candidate.Fields.Count) return false;
|
|
|
|
|
|
|
|
|
|
var commonFields = target.Fields
|
|
|
|
|
.Select(s => s.Name)
|
|
|
|
|
.Intersect(candidate.Fields.Select(s => s.Name));
|
|
|
|
|
|
|
|
|
|
// Methods in target that are not in candidate
|
|
|
|
|
var includeFields = target.Fields
|
|
|
|
|
.Select(s => s.Name.ToString())
|
|
|
|
|
.Except(candidate.Fields.Select(s => s.Name.ToString()));
|
|
|
|
|
|
|
|
|
|
// Methods in candidate that are not in target
|
|
|
|
|
var excludeFields = candidate.Fields
|
|
|
|
|
.Select(s => s.Name.ToString())
|
|
|
|
|
.Except(target.Fields.Select(s => s.Name.ToString()));
|
|
|
|
|
|
2025-01-01 20:37:49 -05:00
|
|
|
|
foreach (var include in includeFields)
|
|
|
|
|
{
|
|
|
|
|
fields.IncludeFields.Add(include);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var exclude in excludeFields)
|
|
|
|
|
{
|
|
|
|
|
fields.ExcludeFields.Add(exclude);
|
|
|
|
|
}
|
2025-01-01 19:17:56 -05:00
|
|
|
|
|
2025-01-01 20:07:20 -05:00
|
|
|
|
fields.FieldCount = target.Fields.Count;
|
|
|
|
|
|
2025-01-01 19:17:56 -05:00
|
|
|
|
return commonFields.Any();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool ContainsTargetProperties(TypeDef target, TypeDef candidate, PropertyParams props)
|
|
|
|
|
{
|
|
|
|
|
// Both target and candidate don't have properties
|
|
|
|
|
if (!target.Properties.Any() && !candidate.Properties.Any())
|
|
|
|
|
{
|
|
|
|
|
props.PropertyCount = 0;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Target has no props but type has props
|
|
|
|
|
if (!target.Properties.Any() && candidate.Properties.Any()) return false;
|
|
|
|
|
|
|
|
|
|
// Target has props but type has no props
|
|
|
|
|
if (target.Properties.Any() && !candidate.Properties.Any()) return false;
|
|
|
|
|
|
|
|
|
|
// Target has a different number of props
|
|
|
|
|
if (target.Properties.Count != candidate.Properties.Count) return false;
|
|
|
|
|
|
|
|
|
|
var commonProps = target.Properties
|
|
|
|
|
.Select(s => s.Name)
|
|
|
|
|
.Intersect(candidate.Properties.Select(s => s.Name));
|
|
|
|
|
|
|
|
|
|
// Props in target that are not in candidate
|
|
|
|
|
var includeProps = target.Properties
|
|
|
|
|
.Select(s => s.Name.ToString())
|
|
|
|
|
.Except(candidate.Properties.Select(s => s.Name.ToString()));
|
|
|
|
|
|
|
|
|
|
// Props in candidate that are not in target
|
|
|
|
|
var excludeProps = candidate.Properties
|
|
|
|
|
.Select(s => s.Name.ToString())
|
|
|
|
|
.Except(target.Properties.Select(s => s.Name.ToString()));
|
|
|
|
|
|
2025-01-01 20:37:49 -05:00
|
|
|
|
foreach (var include in includeProps)
|
|
|
|
|
{
|
|
|
|
|
props.IncludeProperties.Add(include);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (var exclude in excludeProps)
|
|
|
|
|
{
|
|
|
|
|
props.ExcludeProperties.Add(exclude);
|
|
|
|
|
}
|
2025-01-01 19:17:56 -05:00
|
|
|
|
|
2025-01-01 20:07:20 -05:00
|
|
|
|
props.PropertyCount = target.Properties.Count;
|
|
|
|
|
|
2025-01-01 19:17:56 -05:00
|
|
|
|
return commonProps.Any();
|
|
|
|
|
}
|
2025-01-01 20:07:20 -05:00
|
|
|
|
|
|
|
|
|
private void ProcessEndQuestions(RemapModel remapModel, string assemblyPath)
|
2025-01-01 19:17:56 -05:00
|
|
|
|
{
|
2025-01-01 20:07:20 -05:00
|
|
|
|
Thread.Sleep(1000);
|
|
|
|
|
|
|
|
|
|
Logger.LogSync("Add remap to existing list?.. (y/n)", ConsoleColor.Yellow);
|
|
|
|
|
var resp = Console.ReadLine();
|
2025-01-01 19:17:56 -05:00
|
|
|
|
|
2025-01-01 20:07:20 -05:00
|
|
|
|
if (resp == "y" || resp == "yes" || resp == "Y")
|
2025-01-01 19:17:56 -05:00
|
|
|
|
{
|
2025-01-01 20:07:20 -05:00
|
|
|
|
if (mappings.Count == 0)
|
|
|
|
|
{
|
|
|
|
|
Logger.LogSync("No remaps loaded. Please restart with a provided mapping path.", ConsoleColor.Red);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mappings.Any(m => m.NewTypeName == remapModel.NewTypeName))
|
|
|
|
|
{
|
|
|
|
|
Logger.LogSync($"Ambiguous new type names found for {remapModel.NewTypeName}. Please pick a different name.", ConsoleColor.Red);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
mappings.Add(remapModel);
|
|
|
|
|
DataProvider.UpdateMapping(mappingPath, mappings);
|
2025-01-01 19:17:56 -05:00
|
|
|
|
}
|
|
|
|
|
|
2025-01-01 20:07:20 -05:00
|
|
|
|
Logger.LogSync("Would you like to run the remap process?... (y/n)", ConsoleColor.Yellow);
|
|
|
|
|
var resp2 = Console.ReadLine();
|
|
|
|
|
|
|
|
|
|
if (resp2 == "y" || resp2 == "yes" || resp2 == "Y")
|
2025-01-01 19:17:56 -05:00
|
|
|
|
{
|
2025-01-01 20:07:20 -05:00
|
|
|
|
var outPath = Path.GetDirectoryName(assemblyPath);
|
|
|
|
|
new ReMapper().InitializeRemap(mappings, assemblyPath, outPath);
|
2025-01-01 19:17:56 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|