using AssemblyRemapper.Enums; using AssemblyRemapper.Models; using AssemblyRemapper.Remapper.Search; using AssemblyRemapper.Utils; using Mono.Cecil; using System.Diagnostics; namespace AssemblyRemapper.Remapper; public class Remapper { private static Stopwatch Stopwatch = new(); /// /// Start the remapping process /// public void InitializeRemap() { DisplayBasicModuleInformation(); Stopwatch.Start(); foreach (var remap in DataProvider.Remaps) { Logger.Log($"Finding best match for {remap.NewTypeName}...", ConsoleColor.Gray); HandleMapping(remap); } ChooseBestMatches(); if (DataProvider.Settings.AppSettings.MatchMode) { return; } // Dont publicize and unseal until after the remapping so we can use those as search parameters if (!DataProvider.Settings.RemapperSettings.Publicize) { Publicizer.Publicize(); } if (!DataProvider.Settings.RemapperSettings.Unseal) { Publicizer.Unseal(); } // We are done, write the assembly WriteAssembly(); } /// /// Display information about the module we are remapping /// private void DisplayBasicModuleInformation() { Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); Logger.Log($"Starting remap...", ConsoleColor.Yellow); Logger.Log($"Module contains {DataProvider.ModuleDefinition.Types.Count} Types", ConsoleColor.Yellow); Logger.Log($"Publicize: {DataProvider.Settings.RemapperSettings.Publicize}", ConsoleColor.Yellow); Logger.Log($"Unseal: {DataProvider.Settings.RemapperSettings.Unseal}", ConsoleColor.Yellow); Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); } /// /// Loop over all types in the assembly and score them /// /// Mapping to score private void HandleMapping(RemapModel mapping) { foreach (var type in DataProvider.ModuleDefinition.Types) { var _ = FindMatch(type, mapping); } } /// /// Find a match result /// /// Type to score /// Remap to check against /// /// EMatchResult private EMatchResult FindMatch(TypeDefinition type, RemapModel remap) { // Handle Direct Remaps by strict naming first bypasses everything else if (remap.UseForceRename) { HandleByDirectName(type, remap); return EMatchResult.HandleDirect; } foreach (var nestedType in type.NestedTypes) { FindMatch(nestedType, remap); } var score = new ScoringModel { ProposedNewName = remap.NewTypeName, ReMap = remap, Definition = type, }; var matches = new HashSet { type.MatchIsAbstract(remap.SearchParams, score), type.MatchIsEnum(remap.SearchParams, score) , type.MatchIsNested(remap.SearchParams, score), type.MatchIsSealed(remap.SearchParams, score) , type.MatchIsDerived(remap.SearchParams, score) , type.MatchIsInterface(remap.SearchParams, score), type.MatchHasGenericParameters(remap.SearchParams, score), type.MatchIsPublic(remap.SearchParams, score) , type.MatchHasAttribute(remap.SearchParams, score), type.MatchConstructors(remap.SearchParams, score), type.MatchMethods(remap.SearchParams, score), type.MatchFields(remap.SearchParams, score), type.MatchProperties(remap.SearchParams, score), type.MatchNestedTypes(remap.SearchParams, score) }; var NoMatch = matches.Where(x => x.Equals(EMatchResult.NoMatch)).FirstOrDefault(); if (NoMatch == EMatchResult.NoMatch) { return NoMatch; } var match = matches.Where(x => x.Equals(EMatchResult.Match)).Any(); if (match) { // Set the original type name to be used later score.ReMap.OriginalTypeName = type.Name; remap.OriginalTypeName = type.Name; remap.Succeeded = true; remap.FailureReason = EFailureReason.None; score.AddScoreToResult(); return EMatchResult.Match; } return EMatchResult.Disabled; } private void HandleByDirectName(TypeDefinition type, RemapModel remap) { if (type.Name != remap.OriginalTypeName) { return; } var oldName = type.Name; remap.OriginalTypeName = type.Name; remap.FailureReason = EFailureReason.None; remap.Succeeded = true; type.Name = remap.NewTypeName; Logger.Log("-----------------------------------------------", ConsoleColor.Green); Logger.Log($"Renamed {oldName} to {type.Name} directly", ConsoleColor.Green); Renamer.RenameAllDirect(remap, type); Logger.Log("-----------------------------------------------", ConsoleColor.Green); } /// /// Choose the best possible match from all remaps /// private void ChooseBestMatches() { foreach (var score in DataProvider.ScoringModels) { ChooseBestMatch(score.Value); } var failures = 0; var changes = 0; foreach (var remap in DataProvider.Remaps) { if (remap.Succeeded is false) { Logger.Log("-----------------------------------------------", ConsoleColor.Red); Logger.Log($"Renaming {remap.NewTypeName} failed with reason {remap.FailureReason}", ConsoleColor.Red); Logger.Log("-----------------------------------------------", ConsoleColor.Red); failures++; } changes++; } Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); Logger.Log($"Result: Remapped {changes} Types. Failed to remap {failures} Types", ConsoleColor.Yellow); Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); } /// /// Choose best match from a collection of scores, then start the renaming process /// /// Scores to rate private void ChooseBestMatch(HashSet scores) { if (scores.Count == 0) { return; } var filteredScores = scores .OrderByDescending(score => score.Score) .Take(DataProvider.Settings.RemapperSettings.MaxMatchCount); var highestScore = filteredScores.FirstOrDefault(); if (highestScore is null) { return; } Logger.Log("-----------------------------------------------", ConsoleColor.Green); Logger.Log($"Renaming {highestScore.Definition.Name} to {highestScore.ProposedNewName}", ConsoleColor.Green); Logger.Log($"Max possible score: {highestScore.ReMap.SearchParams.CalculateMaxScore()}", ConsoleColor.Green); Logger.Log($"Scored: {highestScore.Score} points", ConsoleColor.Green); if (scores.Count > 1) { Logger.Log($"Warning! There were {filteredScores.Count()} possible matches. Considering adding more search parameters", ConsoleColor.Yellow); foreach (var score in filteredScores.Skip(1)) { Logger.Log($"{score.Definition.Name} - Score [{score.Score}]", ConsoleColor.Yellow); } } if (DataProvider.Settings.AppSettings.MatchMode) { return; } highestScore.ReMap.OriginalTypeName = highestScore.Definition.Name; // Rename type and all associated type members Renamer.RenameAll(highestScore); Logger.Log("-----------------------------------------------", ConsoleColor.Green); } /// /// Write the assembly back to disk and update the mapping file on disk /// private void WriteAssembly() { var filename = Path.GetFileNameWithoutExtension(DataProvider.Settings.RemapperSettings.AssemblyPath); var strippedPath = Path.GetDirectoryName(filename); filename = $"{filename}-Remapped.dll"; var remappedPath = Path.Combine(strippedPath, filename); DataProvider.AssemblyDefinition.Write(remappedPath); DataProvider.UpdateMapping(); Logger.Log("-----------------------------------------------", ConsoleColor.Green); Logger.Log($"Complete: Assembly written to `{remappedPath}`", ConsoleColor.Green); Logger.Log("Original type names updated on mapping file.", ConsoleColor.Green); Logger.Log($"Remap took {Stopwatch.Elapsed.TotalSeconds:F0} seconds", ConsoleColor.Green); Logger.Log("-----------------------------------------------", ConsoleColor.Green); Stopwatch.Stop(); } }