mirror of
https://github.com/sp-tarkov/assembly-tool.git
synced 2025-02-13 03:30:44 -05:00
Initial work and basic functionality
This commit is contained in:
parent
664ff994cf
commit
02a6c417ab
48
ReCodeItCLI/Commands/AutoMatcher.cs
Normal file
48
ReCodeItCLI/Commands/AutoMatcher.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using CliFx;
|
||||||
|
using CliFx.Attributes;
|
||||||
|
using CliFx.Infrastructure;
|
||||||
|
using ReCodeItLib.Models;
|
||||||
|
using ReCodeItLib.ReMapper;
|
||||||
|
using ReCodeItLib.Utils;
|
||||||
|
|
||||||
|
namespace ReCodeItCLI.Commands;
|
||||||
|
|
||||||
|
[Command("AutoMatch", Description = "Automatically tries to generate a mapping object with the provided arguments.")]
|
||||||
|
public class AutoMatchCommand : ICommand
|
||||||
|
{
|
||||||
|
[CommandParameter(0, IsRequired = true, Description = "The absolute path to your obfuscated assembly or exe file, folder must contain all references to be resolved.")]
|
||||||
|
public required string AssemblyPath { get; init; }
|
||||||
|
|
||||||
|
[CommandParameter(1, IsRequired = true, Description = "Full old type name including namespace")]
|
||||||
|
public required string OldTypeName { get; init; }
|
||||||
|
|
||||||
|
[CommandParameter(2, IsRequired = true, Description = "The name you want the type to be renamed to")]
|
||||||
|
public required string NewTypeName { get; init; }
|
||||||
|
|
||||||
|
[CommandParameter(3, IsRequired = false, Description = "Path to your mapping file so it can be updated if a match is found")]
|
||||||
|
public string MappingsPath { get; init; }
|
||||||
|
|
||||||
|
public ValueTask ExecuteAsync(IConsole console)
|
||||||
|
{
|
||||||
|
DataProvider.IsCli = true;
|
||||||
|
DataProvider.LoadAppSettings();
|
||||||
|
|
||||||
|
Logger.LogSync("Finding match...");
|
||||||
|
|
||||||
|
var remaps = new List<RemapModel>();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(MappingsPath))
|
||||||
|
{
|
||||||
|
remaps.AddRange(DataProvider.LoadMappingFile(MappingsPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
new AutoMatcher(remaps)
|
||||||
|
.AutoMatch(AssemblyPath, OldTypeName, NewTypeName);
|
||||||
|
|
||||||
|
// Wait for log termination
|
||||||
|
Logger.Terminate();
|
||||||
|
while(Logger.IsRunning()) {}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
34
RecodeItLib/Remapper/AssemblyUtils.cs
Normal file
34
RecodeItLib/Remapper/AssemblyUtils.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using dnlib.DotNet;
|
||||||
|
using ReCodeItLib.Utils;
|
||||||
|
|
||||||
|
namespace ReCodeItLib.ReMapper;
|
||||||
|
|
||||||
|
internal static class AssemblyUtils
|
||||||
|
{
|
||||||
|
public static string TryDeObfuscate(ModuleDefMD module, string assemblyPath, out ModuleDefMD cleanedModule)
|
||||||
|
{
|
||||||
|
if (!module!.GetTypes().Any(t => t.Name.Contains("GClass")))
|
||||||
|
{
|
||||||
|
Logger.LogSync("Assembly is obfuscated, running de-obfuscation...\n", ConsoleColor.Yellow);
|
||||||
|
|
||||||
|
module.Dispose();
|
||||||
|
module = null;
|
||||||
|
|
||||||
|
Deobfuscator.Deobfuscate(assemblyPath);
|
||||||
|
|
||||||
|
var cleanedName = Path.GetFileNameWithoutExtension(assemblyPath);
|
||||||
|
cleanedName = $"{cleanedName}-cleaned.dll";
|
||||||
|
|
||||||
|
var newPath = Path.GetDirectoryName(assemblyPath);
|
||||||
|
newPath = Path.Combine(newPath!, cleanedName);
|
||||||
|
|
||||||
|
Logger.LogSync($"Cleaning assembly: {newPath}", ConsoleColor.Green);
|
||||||
|
|
||||||
|
cleanedModule = DataProvider.LoadModule(newPath);
|
||||||
|
return newPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanedModule = module;
|
||||||
|
return assemblyPath;
|
||||||
|
}
|
||||||
|
}
|
271
RecodeItLib/Remapper/AutoMatcher.cs
Normal file
271
RecodeItLib/Remapper/AutoMatcher.cs
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
using dnlib.DotNet;
|
||||||
|
using ReCodeItLib.Models;
|
||||||
|
using ReCodeItLib.Utils;
|
||||||
|
|
||||||
|
namespace ReCodeItLib.ReMapper;
|
||||||
|
|
||||||
|
public class AutoMatcher(List<RemapModel> mappings)
|
||||||
|
{
|
||||||
|
private ModuleDefMD? Module { get; set; }
|
||||||
|
|
||||||
|
private List<TypeDef>? CandidateTypes { get; set; }
|
||||||
|
|
||||||
|
private static List<string> _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>()
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
@ -34,14 +34,19 @@ public class ReMapper
|
|||||||
public void InitializeRemap(
|
public void InitializeRemap(
|
||||||
List<RemapModel> remapModels,
|
List<RemapModel> remapModels,
|
||||||
string assemblyPath,
|
string assemblyPath,
|
||||||
string outPath,
|
string outPath = "",
|
||||||
bool validate = false)
|
bool validate = false)
|
||||||
{
|
{
|
||||||
_remaps = [];
|
_remaps = [];
|
||||||
_remaps = remapModels;
|
_remaps = remapModels;
|
||||||
_alreadyGivenNames = [];
|
_alreadyGivenNames = [];
|
||||||
|
|
||||||
Module = DataProvider.LoadModule(assemblyPath);
|
assemblyPath = AssemblyUtils.TryDeObfuscate(
|
||||||
|
DataProvider.LoadModule(assemblyPath),
|
||||||
|
assemblyPath,
|
||||||
|
out var module);
|
||||||
|
|
||||||
|
Module = module;
|
||||||
|
|
||||||
OutPath = outPath;
|
OutPath = outPath;
|
||||||
|
|
||||||
@ -52,8 +57,12 @@ public class ReMapper
|
|||||||
|
|
||||||
var types = Module.GetTypes();
|
var types = Module.GetTypes();
|
||||||
|
|
||||||
TryDeObfuscate(types, assemblyPath);
|
if (!validate)
|
||||||
FindBestMatches(types);
|
{
|
||||||
|
GenerateDynamicRemaps(assemblyPath, types);
|
||||||
|
}
|
||||||
|
|
||||||
|
FindBestMatches(types, validate);
|
||||||
ChooseBestMatches();
|
ChooseBestMatches();
|
||||||
|
|
||||||
// Don't go any further during a validation
|
// Don't go any further during a validation
|
||||||
@ -72,36 +81,7 @@ public class ReMapper
|
|||||||
WriteAssembly();
|
WriteAssembly();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryDeObfuscate(IEnumerable<TypeDef> types, string assemblyPath)
|
private void FindBestMatches(IEnumerable<TypeDef> types, bool validate)
|
||||||
{
|
|
||||||
if (!Module!.GetTypes().Any(t => t.Name.Contains("GClass")))
|
|
||||||
{
|
|
||||||
Logger.LogSync("Assembly is obfuscated, running de-obfuscation...\n", ConsoleColor.Yellow);
|
|
||||||
|
|
||||||
Module.Dispose();
|
|
||||||
Module = null;
|
|
||||||
|
|
||||||
Deobfuscator.Deobfuscate(assemblyPath);
|
|
||||||
|
|
||||||
var cleanedName = Path.GetFileNameWithoutExtension(assemblyPath);
|
|
||||||
cleanedName = $"{cleanedName}-cleaned.dll";
|
|
||||||
|
|
||||||
var newPath = Path.GetDirectoryName(assemblyPath);
|
|
||||||
newPath = Path.Combine(newPath!, cleanedName);
|
|
||||||
|
|
||||||
Console.WriteLine($"Cleaning assembly: {newPath}");
|
|
||||||
|
|
||||||
Module = DataProvider.LoadModule(newPath);
|
|
||||||
types = Module.GetTypes();
|
|
||||||
|
|
||||||
GenerateDynamicRemaps(newPath, types);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
GenerateDynamicRemaps(assemblyPath, types);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void FindBestMatches(IEnumerable<TypeDef> types)
|
|
||||||
{
|
{
|
||||||
Logger.LogSync("Finding Best Matches...", ConsoleColor.Green);
|
Logger.LogSync("Finding Best Matches...", ConsoleColor.Green);
|
||||||
|
|
||||||
@ -116,9 +96,12 @@ public class ReMapper
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!tasks.TrueForAll(t => t.Status == TaskStatus.RanToCompletion))
|
if (!validate)
|
||||||
{
|
{
|
||||||
Logger.DrawProgressBar(tasks.Where(t => t.IsCompleted)!.Count(), tasks.Count, 50);
|
while (!tasks.TrueForAll(t => t.Status == TaskStatus.RanToCompletion))
|
||||||
|
{
|
||||||
|
Logger.DrawProgressBar(tasks.Where(t => t.IsCompleted)!.Count(), tasks.Count, 50);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.WaitAll(tasks.ToArray());
|
Task.WaitAll(tasks.ToArray());
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using ReCodeItLib.Enums;
|
using ReCodeItLib.Enums;
|
||||||
using ReCodeItLib.Models;
|
using ReCodeItLib.Models;
|
||||||
using ReCodeItLib.Utils;
|
using ReCodeItLib.Utils;
|
||||||
@ -14,7 +15,7 @@ public class Statistics(
|
|||||||
public void DisplayStatistics(bool validate = false)
|
public void DisplayStatistics(bool validate = false)
|
||||||
{
|
{
|
||||||
DisplayAlternativeMatches();
|
DisplayAlternativeMatches();
|
||||||
DisplayFailuresAndChanges();
|
DisplayFailuresAndChanges(validate);
|
||||||
|
|
||||||
if (!validate)
|
if (!validate)
|
||||||
{
|
{
|
||||||
@ -45,7 +46,7 @@ public class Statistics(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisplayFailuresAndChanges()
|
private void DisplayFailuresAndChanges(bool validate)
|
||||||
{
|
{
|
||||||
var failures = 0;
|
var failures = 0;
|
||||||
var changes = 0;
|
var changes = 0;
|
||||||
@ -75,6 +76,17 @@ public class Statistics(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (validate)
|
||||||
|
{
|
||||||
|
var str = JsonConvert.SerializeObject(remap, Formatting.Indented);
|
||||||
|
|
||||||
|
Logger.Log("Generated Model: ", ConsoleColor.Blue);
|
||||||
|
Logger.Log(str, ConsoleColor.Blue);
|
||||||
|
|
||||||
|
Logger.Log("Passed validation", ConsoleColor.Green);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
changes++;
|
changes++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user