mirror of
https://github.com/sp-tarkov/assembly-tool.git
synced 2025-02-12 16:50: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>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<RunPostBuildEvent>Always</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
|
||||
<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(
|
||||
List<RemapModel> remapModels,
|
||||
string assemblyPath,
|
||||
string outPath,
|
||||
string outPath = "",
|
||||
bool validate = false)
|
||||
{
|
||||
_remaps = [];
|
||||
_remaps = remapModels;
|
||||
_alreadyGivenNames = [];
|
||||
|
||||
Module = DataProvider.LoadModule(assemblyPath);
|
||||
assemblyPath = AssemblyUtils.TryDeObfuscate(
|
||||
DataProvider.LoadModule(assemblyPath),
|
||||
assemblyPath,
|
||||
out var module);
|
||||
|
||||
Module = module;
|
||||
|
||||
OutPath = outPath;
|
||||
|
||||
@ -51,9 +56,13 @@ public class ReMapper
|
||||
Stopwatch.Start();
|
||||
|
||||
var types = Module.GetTypes();
|
||||
|
||||
if (!validate)
|
||||
{
|
||||
GenerateDynamicRemaps(assemblyPath, types);
|
||||
}
|
||||
|
||||
TryDeObfuscate(types, assemblyPath);
|
||||
FindBestMatches(types);
|
||||
FindBestMatches(types, validate);
|
||||
ChooseBestMatches();
|
||||
|
||||
// Don't go any further during a validation
|
||||
@ -71,37 +80,8 @@ public class ReMapper
|
||||
// We are done, write the assembly
|
||||
WriteAssembly();
|
||||
}
|
||||
|
||||
private void TryDeObfuscate(IEnumerable<TypeDef> types, string assemblyPath)
|
||||
{
|
||||
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)
|
||||
|
||||
private void FindBestMatches(IEnumerable<TypeDef> types, bool validate)
|
||||
{
|
||||
Logger.LogSync("Finding Best Matches...", ConsoleColor.Green);
|
||||
|
||||
@ -115,10 +95,13 @@ 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());
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
using Newtonsoft.Json;
|
||||
using ReCodeItLib.Enums;
|
||||
using ReCodeItLib.Models;
|
||||
using ReCodeItLib.Utils;
|
||||
@ -14,7 +15,7 @@ public class Statistics(
|
||||
public void DisplayStatistics(bool validate = false)
|
||||
{
|
||||
DisplayAlternativeMatches();
|
||||
DisplayFailuresAndChanges();
|
||||
DisplayFailuresAndChanges(validate);
|
||||
|
||||
if (!validate)
|
||||
{
|
||||
@ -45,7 +46,7 @@ public class Statistics(
|
||||
}
|
||||
}
|
||||
|
||||
private void DisplayFailuresAndChanges()
|
||||
private void DisplayFailuresAndChanges(bool validate)
|
||||
{
|
||||
var failures = 0;
|
||||
var changes = 0;
|
||||
@ -75,6 +76,17 @@ public class Statistics(
|
||||
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++;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user