using dnlib.DotNet; using ReCodeItLib.Models; using ReCodeItLib.Utils; namespace ReCodeItLib.ReMapper; public class AutoMatcher(List mappings) { private ModuleDefMD? Module { get; set; } private List? CandidateTypes { get; set; } private static List _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); 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 }; new ReMapper().InitializeRemap(tmpList, assemblyPath, validate: true); } } 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())); methods.IncludeMethods.Clear(); methods.IncludeMethods.AddRange(includeMethods); methods.ExcludeMethods.AddRange(excludeMethods); 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())); fields.IncludeFields.Clear(); fields.IncludeFields.AddRange(includeFields); fields.ExcludeFields.AddRange(excludeFields); 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())); props.IncludeProperties.Clear(); props.IncludeProperties.AddRange(includeProps); props.ExcludeProperties.AddRange(excludeProps); return commonProps.Any(); } private void CompareMethods(MappingDiff diff) { var diffsByTarget = diff.Target.Methods .Select(m => m.Name) .Except(diff.Candidate.Methods.Select(m => m.Name)) .ToList(); if (diffsByTarget.Any()) { Logger.LogSync($"Methods in target not present in candidate:\n {string.Join(", ", diffsByTarget)}", ConsoleColor.Yellow); } var diffsByCandidate = diff.Candidate.Methods .Select(m => m.Name) .Except(diff.Target.Methods.Select(m => m.Name)) .ToList(); if (diffsByCandidate.Any()) { Logger.LogSync($"Methods in candidate not present in target:\n {string.Join(", ", diffsByCandidate)}", ConsoleColor.Yellow); } } private class MappingDiff { public required TypeDef Target; public required TypeDef Candidate; public RemapModel RemapModel = new(); } }