using Mono.Cecil; using ReCodeIt.CrossCompiler; using ReCodeIt.Enums; using ReCodeIt.Models; using ReCodeIt.ReMapper.Search; using ReCodeIt.Utils; using System.Diagnostics; namespace ReCodeIt.ReMapper; public class ReCodeItRemapper { public ReCodeItRemapper(ReCodeItCrossCompiler compiler) { _compiler = compiler; } public ReCodeItRemapper() { } private readonly ReCodeItCrossCompiler _compiler; public static bool IsRunning { get; private set; } = false; public delegate void OnCompleteHandler(); public event OnCompleteHandler? OnComplete; private static readonly Stopwatch Stopwatch = new(); private RemapperSettings Settings => DataProvider.Settings.Remapper; private string OutPath { get; set; } = string.Empty; private bool CrossMapMode { get; set; } = false; /// /// Start the remapping process /// public void InitializeRemap( List remapModels, string assemblyPath, string outPath, bool crossMapMode = false) { DataProvider.LoadAssemblyDefinition(assemblyPath); CrossMapMode = crossMapMode; OutPath = outPath; IsRunning = true; DisplayBasicModuleInformation(); Stopwatch.Start(); foreach (var remap in remapModels) { Logger.Log($"Finding best match for {remap.NewTypeName}...", ConsoleColor.Gray); ScoreMapping(remap); } ChooseBestMatches(); // Dont publicize and unseal until after the remapping so we can use those as search parameters if (!Settings.MappingSettings.Publicize) { Publicizer.Publicize(); } if (!Settings.MappingSettings.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: {Settings.MappingSettings.Publicize}", ConsoleColor.Yellow); Logger.Log($"Unseal: {Settings.MappingSettings.Unseal}", ConsoleColor.Yellow); Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); } /// /// Loop over all types in the assembly and score them /// /// Mapping to score public void ScoreMapping(RemapModel mapping) { foreach (var type in DataProvider.ModuleDefinition.Types) { FindMatch(type, mapping); } } /// /// Find a match result /// /// OriginalTypeRef to score /// Remap to check against /// /// EMatchResult private void FindMatch(TypeDefinition type, RemapModel remap) { // Handle Direct Remaps by strict naming first bypasses everything else if (remap.UseForceRename) { HandleByDirectName(type, remap); return; } 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) { remap.FailureReason = score.FailureReason; return; } 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(); } } 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; if (CrossMapMode) { // Store the original types for caching _compiler.ChangedTypes.Add(remap.NewTypeName, type.Name); } type.Name = remap.NewTypeName; Logger.Log("-----------------------------------------------", ConsoleColor.Green); Logger.Log($"Renamed {oldName} to {type.Name} directly", ConsoleColor.Green); RenameHelper.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++; continue; } 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); 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); } } // highestScore.ReMap.OriginalTypeName = highestScore.Definition.Name; if (CrossMapMode) {// Store the original types for caching _compiler.ChangedTypes.Add(highestScore.ProposedNewName, highestScore.Definition.Name); } // Rename type and all associated type members RenameHelper.RenameAll(highestScore); Logger.Log("-----------------------------------------------", ConsoleColor.Green); } /// /// Write the assembly back to disk and update the mapping file on disk /// private void WriteAssembly() { var path = DataProvider.WriteAssemblyDefinition(OutPath); Logger.Log("-----------------------------------------------", ConsoleColor.Green); Logger.Log($"Complete: Assembly written to `{path}`", ConsoleColor.Green); Logger.Log("Original type names updated on mapping file.", ConsoleColor.Green); Logger.Log($"Remap took {Stopwatch.Elapsed.TotalSeconds:F1} seconds", ConsoleColor.Green); Logger.Log("-----------------------------------------------", ConsoleColor.Green); Reset(); IsRunning = false; OnComplete?.Invoke(); } private void Reset() { Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); Logger.Log("Reloading assembly definitions", ConsoleColor.Yellow); Logger.Log("-----------------------------------------------", ConsoleColor.Yellow); DataProvider.LoadAssemblyDefinition(Settings.AssemblyPath); DataProvider.ScoringModels = []; Stopwatch.Reset(); } }