Add project files.
This commit is contained in:
parent
8ee03dd905
commit
f76ba2f091
25
AssemblyRemapper.sln
Normal file
25
AssemblyRemapper.sln
Normal file
@ -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
|
15
AssemblyRemapper/AssemblyRemapper.csproj
Normal file
15
AssemblyRemapper/AssemblyRemapper.csproj
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Mono.Cecil" Version="0.11.5" />
|
||||||
|
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
41
AssemblyRemapper/Commands/CommandProcessor.cs
Normal file
41
AssemblyRemapper/Commands/CommandProcessor.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
65
AssemblyRemapper/Models/AppSettingsModel.cs
Normal file
65
AssemblyRemapper/Models/AppSettingsModel.cs
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
namespace AssemblyRemapper.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Remap config
|
||||||
|
/// </summary>
|
||||||
|
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<Remap> Remaps { get; set; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Object to store linq statements in inside of json to search and remap classes
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Search filters to find types and remap them
|
||||||
|
/// </summary>
|
||||||
|
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<string> MethodNamesToMatch { get; set; } = [];
|
||||||
|
public HashSet<string> MethodNamesToIgnore { get; set; } = [];
|
||||||
|
|
||||||
|
public HashSet<string> FieldNamesToMatch { get; set; } = [];
|
||||||
|
public HashSet<string> FieldNamesToIgnore { get; set; } = [];
|
||||||
|
public HashSet<string> PropertyNamesToMatch { get; set; } = [];
|
||||||
|
public HashSet<string> PropertyNamesToIgnore { get; set; } = [];
|
||||||
|
|
||||||
|
public HashSet<string> NestedTypesToMatch { get; set; } = [];
|
||||||
|
public HashSet<string> NestedTypesToIgnore { get; set; } = [];
|
||||||
|
|
||||||
|
public RemapSearchParams()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
10
AssemblyRemapper/Models/ELogLevel.cs
Normal file
10
AssemblyRemapper/Models/ELogLevel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace AssemblyRemapper.Models;
|
||||||
|
|
||||||
|
internal enum ELogLevel
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Success = 1,
|
||||||
|
Warn = 2,
|
||||||
|
Error = 3,
|
||||||
|
Full = 4,
|
||||||
|
}
|
9
AssemblyRemapper/Models/EMatchResult.cs
Normal file
9
AssemblyRemapper/Models/EMatchResult.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace AssemblyRemapper.Models;
|
||||||
|
|
||||||
|
internal enum EMatchResult
|
||||||
|
{
|
||||||
|
NoMatch = -1,
|
||||||
|
Disabled = 0,
|
||||||
|
SearchedNoResult = 1,
|
||||||
|
Match = 2,
|
||||||
|
}
|
43
AssemblyRemapper/Models/ScoringModel.cs
Normal file
43
AssemblyRemapper/Models/ScoringModel.cs
Normal file
@ -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<ScoringModel> modelHashset))
|
||||||
|
{
|
||||||
|
modelHashset.Add(model);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newHash = new HashSet<ScoringModel>
|
||||||
|
{
|
||||||
|
model
|
||||||
|
};
|
||||||
|
|
||||||
|
Remapper.ScoringModels.Add(model.ProposedNewName, newHash);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
AssemblyRemapper/Program.cs
Normal file
13
AssemblyRemapper/Program.cs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
306
AssemblyRemapper/Reflection/Remapper.cs
Normal file
306
AssemblyRemapper/Reflection/Remapper.cs
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
using AssemblyRemapper.Models;
|
||||||
|
using AssemblyRemapper.Utils;
|
||||||
|
using Mono.Cecil;
|
||||||
|
|
||||||
|
namespace AssemblyRemapper.Reflection;
|
||||||
|
|
||||||
|
internal class Remapper
|
||||||
|
{
|
||||||
|
public static Dictionary<string, HashSet<ScoringModel>> 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<ScoringModel> 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("-----------------------------------------------");
|
||||||
|
}
|
||||||
|
}
|
72
AssemblyRemapper/Reflection/RenameService.cs
Normal file
72
AssemblyRemapper/Reflection/RenameService.cs
Normal file
@ -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<Mono.Cecil.TypeDefinition> 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<Mono.Cecil.TypeDefinition> 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}";
|
||||||
|
}
|
||||||
|
}
|
326
AssemblyRemapper/Reflection/SearchProvider.cs
Normal file
326
AssemblyRemapper/Reflection/SearchProvider.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
65
AssemblyRemapper/Utils/DataProvider.cs
Normal file
65
AssemblyRemapper/Utils/DataProvider.cs
Normal file
@ -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<AppSettings>(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}");
|
||||||
|
}
|
||||||
|
}
|
35
AssemblyRemapper/Utils/Logger.cs
Normal file
35
AssemblyRemapper/Utils/Logger.cs
Normal file
@ -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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user