diff --git a/AssemblyRemapper.sln b/AssemblyRemapper.sln new file mode 100644 index 0000000..940c7c6 --- /dev/null +++ b/AssemblyRemapper.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssemblyRemapper", "AssemblyRemapper\AssemblyRemapper.csproj", "{84BF5F6E-9EEC-4B08-BE72-9F419A369124}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {84BF5F6E-9EEC-4B08-BE72-9F419A369124}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {84BF5F6E-9EEC-4B08-BE72-9F419A369124}.Debug|Any CPU.Build.0 = Debug|Any CPU + {84BF5F6E-9EEC-4B08-BE72-9F419A369124}.Release|Any CPU.ActiveCfg = Release|Any CPU + {84BF5F6E-9EEC-4B08-BE72-9F419A369124}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C2C7C51D-6773-404D-B51D-BC279AC9B923} + EndGlobalSection +EndGlobal diff --git a/AssemblyRemapper/AssemblyRemapper.csproj b/AssemblyRemapper/AssemblyRemapper.csproj new file mode 100644 index 0000000..b21798a --- /dev/null +++ b/AssemblyRemapper/AssemblyRemapper.csproj @@ -0,0 +1,15 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + diff --git a/AssemblyRemapper/Commands/CommandProcessor.cs b/AssemblyRemapper/Commands/CommandProcessor.cs new file mode 100644 index 0000000..bc424c6 --- /dev/null +++ b/AssemblyRemapper/Commands/CommandProcessor.cs @@ -0,0 +1,41 @@ +using AssemblyRemapper.Reflection; +using AssemblyRemapper.Utils; + +namespace AssemblyRemapper.Commands +{ + internal class CommandProcessor + { + public CommandProcessor() + { } + + public void CommandLoop() + { + ShowStartText(); + + while (true) + { + var input = Console.ReadLine(); + ProcessCommand(input); + } + } + + private void ProcessCommand(string command) + { + if (command == "remap" || command == "Remap") + { + var remapper = new Remapper(); + + remapper.InitializeRemap(); + } + } + + private void ShowStartText() + { + Logger.Log($"-----------------------------------------------------------------", ConsoleColor.Green); + Logger.Log($"Cj's Assembly Tool", ConsoleColor.Green); + Logger.Log($"Version 0.1.0", ConsoleColor.Green); + Logger.Log($"Available Commands: `remap` `help`", ConsoleColor.Green); + Logger.Log($"-----------------------------------------------------------------", ConsoleColor.Green); + } + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Models/AppSettingsModel.cs b/AssemblyRemapper/Models/AppSettingsModel.cs new file mode 100644 index 0000000..f2044c8 --- /dev/null +++ b/AssemblyRemapper/Models/AppSettingsModel.cs @@ -0,0 +1,65 @@ +namespace AssemblyRemapper.Models; + +/// +/// Remap config +/// +internal class AppSettings +{ + public bool Debug { get; set; } + public bool SilentMode { get; set; } + + public bool ScoringMode { get; set; } + public bool Publicize { get; set; } + public bool Unseal { get; set; } + + public string AssemblyPath { get; set; } + public string OutputPath { get; set; } + + public HashSet Remaps { get; set; } = []; +} + +/// +/// Object to store linq statements in inside of json to search and remap classes +/// +internal class Remap +{ + public string NewTypeName { get; set; } = string.Empty; + + public string OldTypeName { get; set; } = string.Empty; + + public bool UseDirectRename { get; set; } + + public RemapSearchParams SearchParams { get; set; } = new(); +} + +/// +/// Search filters to find types and remap them +/// +internal class RemapSearchParams +{ + public bool? IsPublic { get; set; } = null; + public bool? IsAbstract { get; set; } = null; + public bool? IsInterface { get; set; } = null; + public bool? IsEnum { get; set; } = null; + public bool? IsNested { get; set; } = null; + public string? ParentName { get; set; } = null; + public bool? IsSealed { get; set; } = null; + public bool? HasAttribute { get; set; } = null; + public bool? IsDerived { get; set; } = null; + public string? BaseClassName { get; set; } = null; + public bool? IsGeneric { get; set; } = null; + public HashSet MethodNamesToMatch { get; set; } = []; + public HashSet MethodNamesToIgnore { get; set; } = []; + + public HashSet FieldNamesToMatch { get; set; } = []; + public HashSet FieldNamesToIgnore { get; set; } = []; + public HashSet PropertyNamesToMatch { get; set; } = []; + public HashSet PropertyNamesToIgnore { get; set; } = []; + + public HashSet NestedTypesToMatch { get; set; } = []; + public HashSet NestedTypesToIgnore { get; set; } = []; + + public RemapSearchParams() + { + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Models/ELogLevel.cs b/AssemblyRemapper/Models/ELogLevel.cs new file mode 100644 index 0000000..eb2b590 --- /dev/null +++ b/AssemblyRemapper/Models/ELogLevel.cs @@ -0,0 +1,10 @@ +namespace AssemblyRemapper.Models; + +internal enum ELogLevel +{ + None = 0, + Success = 1, + Warn = 2, + Error = 3, + Full = 4, +} \ No newline at end of file diff --git a/AssemblyRemapper/Models/EMatchResult.cs b/AssemblyRemapper/Models/EMatchResult.cs new file mode 100644 index 0000000..7fe4512 --- /dev/null +++ b/AssemblyRemapper/Models/EMatchResult.cs @@ -0,0 +1,9 @@ +namespace AssemblyRemapper.Models; + +internal enum EMatchResult +{ + NoMatch = -1, + Disabled = 0, + SearchedNoResult = 1, + Match = 2, +} \ No newline at end of file diff --git a/AssemblyRemapper/Models/ScoringModel.cs b/AssemblyRemapper/Models/ScoringModel.cs new file mode 100644 index 0000000..8f2d2c6 --- /dev/null +++ b/AssemblyRemapper/Models/ScoringModel.cs @@ -0,0 +1,43 @@ +using AssemblyRemapper.Reflection; +using Mono.Cecil; + +namespace AssemblyRemapper.Models; + +internal class ScoringModel +{ + public int Score { get; set; } = 0; + + public string ProposedNewName { get; set; } + + public TypeDefinition Definition { get; set; } + + public ScoringModel() + { + } +} + +internal static class ScoringModelExtensions +{ + public static void AddModelToResult(this ScoringModel model) + { + try + { + if (Remapper.ScoringModels.TryGetValue(model.ProposedNewName, out HashSet modelHashset)) + { + modelHashset.Add(model); + return; + } + + var newHash = new HashSet + { + model + }; + + Remapper.ScoringModels.Add(model.ProposedNewName, newHash); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Program.cs b/AssemblyRemapper/Program.cs new file mode 100644 index 0000000..e0d3bec --- /dev/null +++ b/AssemblyRemapper/Program.cs @@ -0,0 +1,13 @@ +using AssemblyRemapper.Commands; + +namespace AssemblyRemapper; + +public static class Program +{ + public static void Main(string[] args) + { + var cmd = new CommandProcessor(); + + cmd.CommandLoop(); + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Reflection/Remapper.cs b/AssemblyRemapper/Reflection/Remapper.cs new file mode 100644 index 0000000..2cb55cb --- /dev/null +++ b/AssemblyRemapper/Reflection/Remapper.cs @@ -0,0 +1,306 @@ +using AssemblyRemapper.Models; +using AssemblyRemapper.Utils; +using Mono.Cecil; + +namespace AssemblyRemapper.Reflection; + +internal class Remapper +{ + public static Dictionary> ScoringModels { get; set; } = []; + + public void InitializeRemap() + { + // Make sure any previous results are cleared just incase + ScoringModels.Clear(); + + DisplayBasicModuleInformation(); + StartRemap(); + } + + private void DisplayBasicModuleInformation() + { + Logger.Log($"Module contains {DataProvider.ModuleDefinition.Types.Count} Types"); + Logger.Log($"Starting remap..."); + Logger.Log($"Publicize: {DataProvider.AppSettings.Publicize}"); + Logger.Log($"Unseal: {DataProvider.AppSettings.Unseal}"); + } + + private void StartRemap() + { + foreach (var remap in DataProvider.AppSettings.Remaps) + { + Logger.Log("-----------------------------------------------"); + Logger.Log($"Trying to remap {remap.NewTypeName}..."); + + HandleMapping(remap); + } + + ChooseBestMatches(); + + if (DataProvider.AppSettings.ScoringMode) { return; } + + // Dont publicize and unseal until after the remapping so we can use those as search parameters + HandlePublicize(); + HandleUnseal(); + + // We are done, write the assembly + WriteAssembly(); + } + + private void HandleMapping(Remap mapping) + { + string newName = mapping.NewTypeName; + string oldName = mapping?.OldTypeName ?? string.Empty; + + bool useDirectRename = mapping.UseDirectRename; + + foreach (var type in DataProvider.ModuleDefinition.Types) + { + // Handle Direct Remaps by strict naming first bypasses everything else + if (useDirectRename) + { + HandleByDirectName(oldName, newName, type); + continue; + } + + ScoreType(type, mapping); + } + } + + private void HandlePublicize() + { + if (!DataProvider.AppSettings.Publicize) { return; } + + Logger.Log("Starting publicization..."); + + foreach (var type in DataProvider.ModuleDefinition.Types) + { + if (type.IsNotPublic) { type.IsPublic = true; } + + // We only want to do methods and properties + + if (type.HasMethods) + { + foreach (var method in type.Methods) + { + method.IsPublic = true; + } + } + + if (type.HasProperties) + { + foreach (var property in type.Properties) + { + if (property.SetMethod != null) + { + property.SetMethod.IsPublic = true; + } + + if (property.GetMethod != null) + { + property.GetMethod.IsPublic = true; + } + } + } + } + } + + private void HandleUnseal() + { + if (!DataProvider.AppSettings.Unseal) { return; } + + Logger.Log("Starting unseal..."); + + foreach (var type in DataProvider.ModuleDefinition.Types) + { + if (type.IsSealed) { type.IsSealed = false; } + } + } + + private void HandleByDirectName(string oldName, string newName, TypeDefinition type) + { + if (type.Name != oldName) { return; } + + Logger.Log($"Renaming directly..."); + + type.Name = newName; + + RenameService.RenameAllFields(oldName, newName, DataProvider.ModuleDefinition.Types); + RenameService.RenameAllProperties(oldName, newName, DataProvider.ModuleDefinition.Types); + + Logger.Log($"Renamed {oldName} to {newName}"); + Logger.Log("-----------------------------------------------"); + } + + private void ScoreType(TypeDefinition type, Remap remap, string parentTypeName = "") + { + foreach (var nestedType in type.NestedTypes) + { + ScoreType(nestedType, remap, type.Name); + } + + var score = new ScoringModel + { + Definition = type, + ProposedNewName = remap.NewTypeName, + }; + + if (type.MatchIsAbstract(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + /* + if (type.MatchIsEnum(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchIsNested(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + type.MatchIsSealed(remap.SearchParams, score); + + if (type.MatchIsSealed(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchIsDerived(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchIsInterface(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchIsGeneric(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchIsPublic(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchHasAttribute(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchMethods(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchFields(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchProperties(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + + if (type.MatchNestedTypes(remap.SearchParams, score) == EMatchResult.NoMatch) + { + return; + } + */ + ScoringModelExtensions.AddModelToResult(score); + } + + private void ChooseBestMatches() + { + foreach (var remap in ScoringModels) + { + ChooseBestMatch(remap.Value, true); + } + } + + private void ChooseBestMatch(HashSet scores, bool isBest = false) + { + if (ScoringModels.Count == 0) + { + return; + } + + var highestScore = scores.OrderByDescending(model => model.Score).FirstOrDefault(); + var secondScore = scores.OrderByDescending(model => model.Score).Skip(1).FirstOrDefault(); + + if (highestScore is null || secondScore is null) { return; } + + var potentialText = isBest + ? "Best potential" + : "Next potential"; + + if (highestScore.Score <= 0) { return; } + + Logger.Log("-----------------------------------------------"); + Logger.Log($"Found {scores.Count} possible matches"); + Logger.Log($"Scored: {highestScore.Score} points"); + Logger.Log($"Next Best: {secondScore.Score} points"); + Logger.Log($"{potentialText} match is `{highestScore.Definition.Name}` for `{highestScore.ProposedNewName}`"); + + if (DataProvider.AppSettings.ScoringMode) + { + Logger.Log("Show next result? (y/n)"); + var answer = Console.ReadLine(); + + if (answer == "yes" || answer == "y") + { + scores.Remove(highestScore); + ChooseBestMatch(scores); + } + + Logger.Log("-----------------------------------------------"); + return; + } + + var anwser = ""; + + if (!DataProvider.AppSettings.SilentMode) + { + Logger.Log($"Should we continue? (y/n)"); + anwser = Console.ReadLine(); + } + + if (anwser == "yes" || anwser == "y" || DataProvider.AppSettings.SilentMode) + { + var oldName = highestScore.Definition.Name; + + highestScore.Definition.Name = highestScore.ProposedNewName; + + RenameService.RenameAllFields(oldName, highestScore.Definition.Name, DataProvider.ModuleDefinition.Types); + RenameService.RenameAllProperties(oldName, highestScore.Definition.Name, DataProvider.ModuleDefinition.Types); + + Logger.Log($"Remapped {oldName} to `{highestScore.Definition.Name}`"); + Logger.Log("-----------------------------------------------"); + return; + } + + scores.Remove(highestScore); + ChooseBestMatch(scores); + } + + private void WriteAssembly() + { + var filename = Path.GetFileNameWithoutExtension(DataProvider.AppSettings.AssemblyPath); + var strippedPath = Path.GetDirectoryName(filename); + + filename = $"{filename}-Remapped.dll"; + + var remappedPath = Path.Combine(strippedPath, filename); + + DataProvider.AssemblyDefinition.Write(remappedPath); + + Logger.Log("-----------------------------------------------"); + Logger.Log($"Complete: Assembly written to `{remappedPath}`"); + Logger.Log("-----------------------------------------------"); + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Reflection/RenameService.cs b/AssemblyRemapper/Reflection/RenameService.cs new file mode 100644 index 0000000..fe14896 --- /dev/null +++ b/AssemblyRemapper/Reflection/RenameService.cs @@ -0,0 +1,72 @@ +using AssemblyRemapper.Utils; +using Mono.Collections.Generic; + +namespace AssemblyRemapper.Reflection; + +internal static class RenameService +{ + public static void RenameAllFields( + string oldName, + string newName, + Collection types) + { + foreach (var type in types) + { + int fieldCount = 0; + + foreach (var field in type.Fields) + { + if (field.FieldType.ToString() == newName) + { + Logger.Log($"Renaming Field: `{field.Name}` on Type `{type}`"); + field.Name = GetNewFieldName(newName, field.IsPrivate, fieldCount); + fieldCount++; + } + } + + if (type.HasNestedTypes) + { + foreach (var _ in type.NestedTypes) + { + RenameAllFields(oldName, newName, type.NestedTypes); + } + } + } + } + + public static void RenameAllProperties( + string oldName, + string newName, + Collection types) + { + foreach (var type in types) + { + int propertyCount = 0; + + foreach (var property in type.Properties) + { + if (property.PropertyType.ToString() == newName) + { + Logger.Log($"Renaming Property: `{property.Name}` on Type `{type}`"); + property.Name = propertyCount > 0 ? $"{newName}_{propertyCount}" : newName; + } + } + + if (type.HasNestedTypes) + { + foreach (var _ in type.NestedTypes) + { + RenameAllProperties(oldName, newName, type.NestedTypes); + } + } + } + } + + private static string GetNewFieldName(string TypeName, bool isPrivate, int fieldCount = 0) + { + var discard = isPrivate ? "_" : ""; + string newFieldCount = fieldCount > 0 ? $"_{fieldCount}" : string.Empty; + + return $"{discard}{char.ToLower(TypeName[0])}{TypeName[1..]}{newFieldCount}"; + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Reflection/SearchProvider.cs b/AssemblyRemapper/Reflection/SearchProvider.cs new file mode 100644 index 0000000..c8ce93b --- /dev/null +++ b/AssemblyRemapper/Reflection/SearchProvider.cs @@ -0,0 +1,326 @@ +using AssemblyRemapper.Models; +using AssemblyRemapper.Utils; +using Mono.Cecil; +using Mono.Cecil.Rocks; + +namespace AssemblyRemapper.Reflection; + +internal static class SearchProvider +{ + public static int MatchCount { get; private set; } + + public static EMatchResult MatchIsAbstract(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsAbstract is null) + { + return EMatchResult.Disabled; + } + + // Interfaces cannot be abstract, and abstract cannot be static + if (type.IsInterface || type.GetStaticConstructor() != null) + { + Logger.Log($"Searching for an abstract type, skipping interface or static"); + return EMatchResult.NoMatch; + } + + if (type.IsAbstract != parms.IsAbstract) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsAbstract"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsAbstract does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsEnum(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsEnum is null) + { + return EMatchResult.Disabled; + } + + if (type.IsEnum == parms.IsEnum) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsEnum"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsEnum does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsNested(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsNested is null) + { + return EMatchResult.Disabled; + } + + if (type.IsNested == parms.IsNested) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsNested"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsNested does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsSealed(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsSealed is null) + { + return EMatchResult.Disabled; + } + + if (type.IsSealed == parms.IsSealed) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsSealed"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsSealed does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsDerived(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsDerived is null) + { + return EMatchResult.Disabled; + } + + if (type.BaseType != null && (bool)parms.IsDerived) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsDerived"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsDerived does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsInterface(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsInterface is null) + { + return EMatchResult.Disabled; + } + + // Interfaces cannot be a class + if (type.IsClass) + { + return EMatchResult.NoMatch; + } + + if (type.IsInterface != parms.IsInterface) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsInterface"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsInterface does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsGeneric(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsGeneric is null) + { + return EMatchResult.Disabled; + } + + if (type.HasGenericParameters == parms.IsGeneric) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsGeneric"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsGeneric does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchIsPublic(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.IsPublic is null) + { + return EMatchResult.Disabled; + } + + if (type.IsPublic == parms.IsPublic) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : IsPublic"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` IsPublic does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchHasAttribute(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + if (parms.HasAttribute is null) + { + return EMatchResult.Disabled; + } + + if (type.HasCustomAttributes == parms.HasAttribute) + { + score.Score += 1; + Logger.Log($"Matched `{type.Name}` on search `{score.ProposedNewName}` : HasAttribute"); + return EMatchResult.Match; + } + + Logger.Log($"Skipping `{type.Name}` on search `{score.ProposedNewName}` HasAttribute does not match."); + return EMatchResult.NoMatch; + } + + public static EMatchResult MatchMethods(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + // Ignore types that dont have methods when we are looking for them, and ignore types that + // have methods while we're not looking for them + if ((type.HasMethods is true && parms.MethodNamesToMatch.Count == 0) || (type.HasMethods is false && parms.MethodNamesToMatch.Count > 0)) + { + return EMatchResult.NoMatch; + } + + // `*` is the wildcard to ignore all methods that exist on types + if (parms.MethodNamesToIgnore.Contains("*")) + { + return EMatchResult.NoMatch; + } + + int matchCount = 0; + + foreach (var method in type.Methods) + { + if (parms.MethodNamesToIgnore.Contains(method.Name)) + { + // Type contains blacklisted method + return EMatchResult.NoMatch; + } + + if (parms.MethodNamesToMatch.Contains(method.Name)) + { + matchCount++; + score.Score += 2; + } + } + + return matchCount > 0 ? EMatchResult.Match : EMatchResult.NoMatch; + } + + public static EMatchResult MatchFields(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + // Ignore types that dont have fields when we are looking for them, and ignore types that + // have fields while we're not looking for them + if ((type.HasFields is true && parms.FieldNamesToMatch.Count == 0) || (type.HasFields is false && parms.FieldNamesToMatch.Count > 0)) + { + return EMatchResult.NoMatch; + } + + // `*` is the wildcard to ignore all fields that exist on types + if (parms.FieldNamesToIgnore.Contains("*")) + { + return EMatchResult.NoMatch; + } + + int matchCount = 0; + + foreach (var field in type.Fields) + { + if (parms.FieldNamesToIgnore.Contains(field.Name)) + { + // Type contains blacklisted field + return EMatchResult.NoMatch; + } + + if (parms.FieldNamesToMatch.Contains(field.Name)) + { + matchCount++; + score.Score += 2; + } + } + + return matchCount > 0 ? EMatchResult.Match : EMatchResult.NoMatch; + } + + public static EMatchResult MatchProperties(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + // Ignore types that dont have properties when we are looking for them, and ignore types + // that have properties while we're not looking for them + if ((type.HasProperties is true && parms.PropertyNamesToMatch.Count == 0) || (type.HasProperties is false && parms.PropertyNamesToMatch.Count > 0)) + { + return EMatchResult.NoMatch; + } + + // `*` is the wildcard to ignore all properties that exist on types + if (parms.PropertyNamesToIgnore.Contains("*")) + { + return EMatchResult.NoMatch; + } + + int matchCount = 0; + + foreach (var property in type.Properties) + { + if (parms.PropertyNamesToIgnore.Contains(property.Name)) + { + // Type contains blacklisted property + return EMatchResult.NoMatch; + } + + if (parms.PropertyNamesToMatch.Contains(property.Name)) + { + matchCount++; + score.Score += 2; + } + } + + return matchCount > 0 ? EMatchResult.Match : EMatchResult.NoMatch; + } + + public static EMatchResult MatchNestedTypes(this TypeDefinition type, RemapSearchParams parms, ScoringModel score) + { + // Ignore types that dont have nested types when we are looking for them, and ignore types + // that have nested types while we're not looking for them + if ((type.HasNestedTypes is true && parms.NestedTypesToMatch.Count == 0) || (type.HasNestedTypes is false && parms.NestedTypesToMatch.Count > 0)) + { + return EMatchResult.NoMatch; + } + + // `*` is the wildcard to ignore all nested types that exist on types + if (parms.PropertyNamesToIgnore.Contains("*")) + { + return EMatchResult.NoMatch; + } + + int matchCount = 0; + + foreach (var nestedType in type.NestedTypes) + { + if (parms.NestedTypesToIgnore.Contains(nestedType.Name)) + { + // Type contains blacklisted nested type + return EMatchResult.NoMatch; + } + + if (parms.NestedTypesToMatch.Contains(nestedType.Name)) + { + matchCount++; + score.Score += 2; + } + } + + return matchCount > 0 ? EMatchResult.Match : EMatchResult.NoMatch; + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Utils/DataProvider.cs b/AssemblyRemapper/Utils/DataProvider.cs new file mode 100644 index 0000000..c3ac009 --- /dev/null +++ b/AssemblyRemapper/Utils/DataProvider.cs @@ -0,0 +1,65 @@ +using AssemblyRemapper.Models; +using Mono.Cecil; +using Newtonsoft.Json; + +namespace AssemblyRemapper.Utils; + +internal static class DataProvider +{ + static DataProvider() + { + LoadAppSettings(); + LoadAssemblyDefinition(); + } + + public static AppSettings AppSettings { get; private set; } + + public static AssemblyDefinition AssemblyDefinition { get; private set; } + + public static ModuleDefinition ModuleDefinition { get; private set; } + + private static void LoadAppSettings() + { + var settingsPath = Path.Combine(AppContext.BaseDirectory, "Data", "Settings.jsonc"); + + if (!File.Exists(settingsPath)) + { + throw new InvalidOperationException($"path `{settingsPath}` does not exist..."); + } + + var jsonText = File.ReadAllText(settingsPath); + + JsonSerializerSettings settings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }; + + AppSettings = JsonConvert.DeserializeObject(jsonText, settings); + } + + private static void LoadAssemblyDefinition() + { + DefaultAssemblyResolver resolver = new(); + resolver.AddSearchDirectory(Path.GetDirectoryName(AppSettings.AssemblyPath)); // Replace with the correct path + ReaderParameters parameters = new() { AssemblyResolver = resolver }; + + AssemblyDefinition = AssemblyDefinition.ReadAssembly(AppSettings.AssemblyPath, parameters); + + if (AssemblyDefinition is null) + { + throw new InvalidOperationException("AssemblyDefinition was null..."); + } + + var fileName = Path.GetFileName(AppSettings.AssemblyPath); + + foreach (var module in AssemblyDefinition.Modules.ToArray()) + { + if (module.Name == fileName) + { + ModuleDefinition = module; + } + } + + Logger.Log($"Module `{fileName}` not found in assembly {fileName}"); + } +} \ No newline at end of file diff --git a/AssemblyRemapper/Utils/Logger.cs b/AssemblyRemapper/Utils/Logger.cs new file mode 100644 index 0000000..9714f4d --- /dev/null +++ b/AssemblyRemapper/Utils/Logger.cs @@ -0,0 +1,35 @@ +namespace AssemblyRemapper.Utils; + +internal static class Logger +{ + static Logger() + { + if (File.Exists(_logPath)) + { + File.Delete(_logPath); + File.Create(_logPath).Close(); + } + } + + private static string _logPath = Path.Combine(AppContext.BaseDirectory, "Data", "Log.log"); + + public static void Log(string message, ConsoleColor color = ConsoleColor.Gray) + { + Console.ForegroundColor = color; + Console.WriteLine(message); + Console.ResetColor(); + + try + { + using (StreamWriter sw = File.AppendText(_logPath)) + { + sw.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss} - {message}"); + } + } + catch (IOException ex) + { + // Handle potential file writing errors gracefully + Console.WriteLine($"Error logging: {ex.Message}"); + } + } +} \ No newline at end of file