0
0
mirror of https://github.com/sp-tarkov/assembly-tool.git synced 2025-02-13 09:30:46 -05:00

653 lines
20 KiB
C#
Raw Normal View History

using dnlib.DotNet;
using dnlib.DotNet.Emit;
2024-12-31 13:46:44 -05:00
using ReCodeItLib.Enums;
using ReCodeItLib.Models;
using ReCodeItLib.ReMapper.Search;
using ReCodeItLib.Utils;
2024-06-13 17:05:34 -04:00
using System.Diagnostics;
using System.Reflection;
2024-06-11 19:18:48 -04:00
2024-12-31 13:46:44 -05:00
namespace ReCodeItLib.ReMapper;
2024-06-11 19:18:48 -04:00
2024-12-31 13:47:51 -05:00
public class ReMapper
2024-06-11 19:18:48 -04:00
{
2024-06-28 16:17:54 -04:00
private ModuleDefMD? Module { get; set; }
2024-06-14 14:18:17 -04:00
public static bool IsRunning { get; private set; } = false;
2024-06-18 23:45:00 -04:00
public delegate void OnCompleteHandler();
2024-06-14 18:20:12 -04:00
public event OnCompleteHandler? OnComplete;
2024-06-14 18:20:12 -04:00
private static readonly Stopwatch Stopwatch = new();
2024-06-13 17:05:34 -04:00
2024-12-31 03:13:29 -05:00
private RemapperSettings? Settings => DataProvider.Settings?.Remapper;
2024-06-17 17:29:26 -04:00
2024-06-18 17:35:31 -04:00
private string OutPath { get; set; } = string.Empty;
private List<RemapModel> _remaps = [];
private List<string> _alreadyGivenNames = [];
2024-06-13 01:53:43 -04:00
/// <summary>
/// Start the remapping process
/// </summary>
public void InitializeRemap(
List<RemapModel> remapModels,
string assemblyPath,
string outPath,
2024-06-28 16:17:54 -04:00
bool validate = false)
2024-06-11 19:18:48 -04:00
{
_remaps = [];
_remaps = remapModels;
_alreadyGivenNames = [];
Module = DataProvider.LoadModule(assemblyPath);
2024-06-18 17:35:31 -04:00
OutPath = outPath;
if (!Validate(_remaps)) return;
2024-06-14 14:18:17 -04:00
IsRunning = true;
2024-06-13 17:05:34 -04:00
Stopwatch.Start();
2024-12-31 13:18:11 -05:00
var types = Module.GetTypes();
if (!types.Any(t => t.Name.Contains("GClass")))
{
Logger.Log("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);
2024-12-31 03:13:29 -05:00
newPath = Path.Combine(newPath!, cleanedName);
Console.WriteLine($"Cleaning assembly: {newPath}");
Module = DataProvider.LoadModule(newPath);
types = Module.GetTypes();
GenerateDynamicRemaps(newPath, types);
}
else
{
GenerateDynamicRemaps(assemblyPath, types);
}
2024-12-31 04:48:12 -05:00
Logger.LogSync("Finding Best Matches...", ConsoleColor.Green);
var tasks = new List<Task>(remapModels.Count);
foreach (var remap in remapModels)
2024-06-11 19:18:48 -04:00
{
tasks.Add(
Task.Factory.StartNew(() =>
{
ScoreMapping(remap, types);
})
);
2024-06-11 19:18:48 -04:00
}
2024-12-31 04:48:12 -05:00
while (!tasks.TrueForAll(t => t.Status == TaskStatus.RanToCompletion))
{
Logger.DrawProgressBar(tasks.Where(t => t.IsCompleted)!.Count(), tasks.Count, 50);
}
Task.WaitAll(tasks.ToArray());
2024-06-11 19:18:48 -04:00
ChooseBestMatches();
2024-06-28 16:17:54 -04:00
// Don't go any further during a validation
if (validate)
{
2024-12-31 13:18:11 -05:00
new Statistics(_remaps, Stopwatch, OutPath)
.DisplayStatistics(true);
2024-06-28 16:17:54 -04:00
return;
}
2024-12-31 04:48:12 -05:00
Logger.LogSync("\nRenaming...", ConsoleColor.Green);
var renameTasks = new List<Task>(remapModels.Count);
foreach (var remap in remapModels)
2024-06-13 17:55:06 -04:00
{
renameTasks.Add(
Task.Factory.StartNew(() =>
{
RenameHelper.RenameAll(types, remap);
})
);
2024-06-13 17:55:06 -04:00
}
2024-12-31 04:48:12 -05:00
while (!renameTasks.TrueForAll(t => t.Status == TaskStatus.RanToCompletion))
{
Logger.DrawProgressBar(renameTasks.Where(t => t.IsCompleted)!.Count(), tasks.Count - 1, 50);
}
Task.WaitAll(renameTasks.ToArray());
// Don't publicize and unseal until after the remapping, so we can use those as search parameters
2024-12-31 03:13:29 -05:00
if (Settings!.MappingSettings!.Publicize)
2024-06-13 17:55:06 -04:00
{
2024-12-31 04:48:12 -05:00
Logger.LogSync("\nPublicizing classes...", ConsoleColor.Green);
SPTPublicizer.PublicizeClasses(Module);
2024-06-13 17:55:06 -04:00
}
2024-06-11 19:18:48 -04:00
// We are done, write the assembly
WriteAssembly();
}
private bool Validate(List<RemapModel> remaps)
{
var duplicateGroups = remaps
.GroupBy(m => m.NewTypeName)
.Where(g => g.Count() > 1)
.ToList();
if (duplicateGroups.Count() > 1)
{
Logger.Log($"There were {duplicateGroups.Count()} duplicated sets of remaps.", ConsoleColor.Yellow);
foreach (var duplicate in duplicateGroups)
{
var duplicateNewTypeName = duplicate.Key;
Logger.Log($"Ambiguous NewTypeName: {duplicateNewTypeName} found. Cancelling Remap.", ConsoleColor.Red);
}
return false;
}
return true;
}
2024-06-13 01:53:43 -04:00
/// <summary>
/// First we filter our type collection based on simple search parameters (true/false/null)
/// where null is a third disabled state. Then we score the types based on the search parameters
2024-06-13 01:53:43 -04:00
/// </summary>
/// <param name="mapping">Mapping to score</param>
private void ScoreMapping(RemapModel mapping, IEnumerable<TypeDef> types)
2024-06-11 19:18:48 -04:00
{
var tokens = DataProvider.Settings?.Remapper?.TokensToMatch;
2024-06-21 14:11:06 -04:00
if (mapping.UseForceRename)
{
HandleDirectRename(mapping, ref types);
return;
}
// Filter down nested objects
if (mapping.SearchParams.IsNested is false or null)
2024-06-21 14:11:06 -04:00
{
2024-12-31 03:13:29 -05:00
types = types.Where(type => tokens!.Any(token => type.Name.StartsWith(token)));
2024-06-21 14:11:06 -04:00
}
// Run through a series of filters and report an error if all types are filtered out.
if (!FilterTypesByGeneric(mapping, ref types)) return;
if (!FilterTypesByMethods(mapping, ref types)) return;
if (!FilterTypesByFields(mapping, ref types)) return;
if (!FilterTypesByProps(mapping, ref types)) return;
if (!FilterTypesByEvents(mapping, ref types)) return;
if (!FilterTypesByNested(mapping, ref types)) return;
types = CtorTypeFilters.FilterByParameterCount(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.ConstructorParameterCount);
mapping.TypeCandidates.UnionWith(types);
return;
}
mapping.TypeCandidates.UnionWith(types);
}
2024-06-21 14:11:06 -04:00
private static bool FilterTypesByGeneric(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
types = GenericTypeFilters.FilterPublic(types, mapping.SearchParams);
2024-06-11 19:18:48 -04:00
if (!types.Any())
2024-06-11 19:18:48 -04:00
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsPublic);
mapping.TypeCandidates.UnionWith(types);
return false;
2024-06-11 19:18:48 -04:00
}
types = GenericTypeFilters.FilterAbstract(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsPublic);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterSealed(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsSealed);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterInterface(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsInterface);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterStruct(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsStruct);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterEnum(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsEnum);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterAttributes(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.HasAttribute);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterDerived(types, mapping.SearchParams);
2024-12-31 13:18:11 -05:00
if (types.Count() == 1) return true;
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.IsDerived);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = GenericTypeFilters.FilterByGenericParameters(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.HasGenericParameters);
mapping.TypeCandidates.UnionWith(types);
return false;
}
return true;
}
2024-06-11 23:07:59 -04:00
private static bool FilterTypesByMethods(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
types = MethodTypeFilters.FilterByInclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.MethodsInclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = MethodTypeFilters.FilterByExclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.MethodsExclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = MethodTypeFilters.FilterByCount(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.MethodsCount);
mapping.TypeCandidates.UnionWith(types);
return false;
}
2024-06-18 17:35:31 -04:00
return true;
}
private static bool FilterTypesByFields(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
types = FieldTypeFilters.FilterByInclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.FieldsInclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = FieldTypeFilters.FilterByExclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.FieldsExclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = FieldTypeFilters.FilterByCount(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.FieldsCount);
mapping.TypeCandidates.UnionWith(types);
return false;
}
2024-06-18 17:35:31 -04:00
return true;
}
private static bool FilterTypesByProps(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
types = PropertyTypeFilters.FilterByInclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.PropertiesInclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = PropertyTypeFilters.FilterByExclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.PropertiesExclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = PropertyTypeFilters.FilterByCount(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.PropertiesCount);
mapping.TypeCandidates.UnionWith(types);
return false;
}
return true;
}
2024-06-12 14:38:43 -04:00
private static bool FilterTypesByNested(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
types = NestedTypeFilters.FilterByInclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.NestedTypeInclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = NestedTypeFilters.FilterByExclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.NestedTypeExclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = NestedTypeFilters.FilterByCount(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.NestedTypeCount);
mapping.TypeCandidates.UnionWith(types);
return false;
}
return true;
2024-06-11 23:07:59 -04:00
}
private static bool FilterTypesByEvents(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
types = EventTypeFilters.FilterByInclude(types, mapping.SearchParams);
if (!types.Any())
{
AllTypesFilteredOutFor(mapping, ENoMatchReason.EventsInclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
types = EventTypeFilters.FilterByExclude(types, mapping.SearchParams);
if (!types.Any())
{
2024-11-05 14:25:54 -05:00
AllTypesFilteredOutFor(mapping, ENoMatchReason.EventsExclude);
mapping.TypeCandidates.UnionWith(types);
return false;
}
return true;
}
private void HandleDirectRename(RemapModel mapping, ref IEnumerable<TypeDef> types)
{
foreach (var type in types)
{
if (type.Name == mapping.OriginalTypeName)
{
mapping.TypePrimeCandidate = type;
mapping.OriginalTypeName = type.Name.String;
mapping.Succeeded = true;
_alreadyGivenNames.Add(mapping.OriginalTypeName);
return;
}
}
}
private void GenerateDynamicRemaps(string path, IEnumerable<TypeDef> types)
{
// HACK: Because this is written in net8 and the assembly is net472 we must resolve the type this way instead of
// filtering types directly using GetTypes() Otherwise, it causes serialization issues.
// This is also necessary because we can't access non-compile time constants with dnlib.
var templateMappingTypeDef = types.SingleOrDefault(t => t.FindField("TypeTable") != null);
if (templateMappingTypeDef is null)
{
Logger.Log("Could not find type for field TypeTable", ConsoleColor.Red);
return;
}
var assembly = Assembly.LoadFrom(path);
var templateMappingClass = assembly.Modules
.First()
.GetType(templateMappingTypeDef.Name);
if (templateMappingClass is null)
{
Logger.Log($"Could not resolve type for {templateMappingTypeDef.Name}", ConsoleColor.Red);
return;
}
var typeTable = (Dictionary<string, Type>)templateMappingClass
.GetField("TypeTable")
.GetValue(templateMappingClass);
2024-12-31 03:13:29 -05:00
BuildAssociationFromTable(typeTable!, "ItemClass");
var templateTypeTable = (Dictionary<string, Type>)templateMappingClass
.GetField("TemplateTypeTable")
.GetValue(templateMappingClass);
2024-12-31 03:13:29 -05:00
BuildAssociationFromTable(templateTypeTable!, "TemplateClass");
}
private void BuildAssociationFromTable(Dictionary<string, Type> table, string extName)
{
foreach (var type in table)
{
2024-12-31 13:46:44 -05:00
if (!DataProvider.ItemTemplates!.TryGetValue(type.Key, out var template) ||
!type.Value.Name.StartsWith("GClass"))
{
continue;
}
2024-12-31 13:46:44 -05:00
var remap = new RemapModel
{
OriginalTypeName = type.Value.Name,
NewTypeName = $"{template._name}{extName}",
UseForceRename = true
};
2024-12-31 13:46:44 -05:00
_remaps.Add(remap);
}
}
2024-06-13 01:53:43 -04:00
/// <summary>
/// Choose the best possible match from all remaps
/// </summary>
2024-06-11 19:18:48 -04:00
private void ChooseBestMatches()
{
foreach (var remap in _remaps)
2024-06-11 19:18:48 -04:00
{
ChooseBestMatch(remap);
2024-06-11 19:18:48 -04:00
}
}
2024-06-13 01:53:43 -04:00
/// <summary>
2024-06-28 16:17:54 -04:00
/// Choose best match from a collection of types on a remap
2024-06-13 01:53:43 -04:00
/// </summary>
2024-06-28 16:17:54 -04:00
/// <param name="remap"></param>
private void ChooseBestMatch(RemapModel remap)
2024-06-11 19:18:48 -04:00
{
if (remap.TypeCandidates.Count == 0 || remap.Succeeded) { return; }
2024-06-13 08:18:16 -04:00
var winner = remap.TypeCandidates.FirstOrDefault();
2024-08-09 23:42:34 -04:00
if (winner is null) { return; }
remap.TypePrimeCandidate = winner;
remap.OriginalTypeName = winner.Name.String;
2024-08-09 23:42:34 -04:00
if (_alreadyGivenNames.Contains(winner.FullName))
{
2024-06-28 18:38:03 -04:00
remap.NoMatchReasons.Add(ENoMatchReason.AmbiguousWithPreviousMatch);
remap.AmbiguousTypeMatch = winner.FullName;
remap.Succeeded = false;
return;
}
_alreadyGivenNames.Add(remap.OriginalTypeName);
remap.Succeeded = true;
2024-06-12 00:05:59 -04:00
remap.OriginalTypeName = winner.Name.String;
2024-06-11 19:18:48 -04:00
}
2024-06-13 01:53:43 -04:00
/// <summary>
/// Write the assembly back to disk and update the mapping file on disk
/// </summary>
2024-06-11 19:18:48 -04:00
private void WriteAssembly()
{
2024-12-31 03:13:29 -05:00
var moduleName = Module?.Name;
2024-08-23 13:24:16 +01:00
var dllName = "-cleaned-remapped.dll";
2024-12-31 03:13:29 -05:00
if (Settings!.MappingSettings!.Publicize)
2024-08-23 13:24:16 +01:00
{
dllName = "-cleaned-remapped-publicized.dll";
}
2024-12-31 03:13:29 -05:00
OutPath = Path.Combine(OutPath, moduleName?.Replace(".dll", dllName));
try
{
2024-12-31 03:13:29 -05:00
Module!.Write(OutPath);
}
catch (Exception e)
{
2024-12-31 13:46:44 -05:00
Logger.LogSync(e);
throw;
}
2024-06-11 19:18:48 -04:00
2024-12-31 13:46:44 -05:00
Logger.LogSync("\nCreating Hollow...", ConsoleColor.Green);
Hollow();
var hollowedDir = Path.GetDirectoryName(OutPath);
2024-12-31 03:13:29 -05:00
var hollowedPath = Path.Combine(hollowedDir!, "Assembly-CSharp-hollowed.dll");
try
{
Module.Write(hollowedPath);
}
catch (Exception e)
{
2024-12-31 13:46:44 -05:00
Logger.LogSync(e);
throw;
}
2024-12-31 03:13:29 -05:00
if (DataProvider.Settings?.Remapper?.MappingPath != string.Empty)
{
2024-12-31 03:13:29 -05:00
DataProvider.UpdateMapping(DataProvider.Settings!.Remapper!.MappingPath.Replace("mappings.", "mappings-new."), _remaps);
}
2024-06-13 17:05:34 -04:00
2024-12-31 13:18:11 -05:00
new Statistics(_remaps, Stopwatch, OutPath, hollowedPath)
.DisplayStatistics();
2024-06-14 14:18:17 -04:00
Stopwatch.Reset();
2024-06-28 16:17:54 -04:00
Module = null;
2024-06-19 22:33:08 -04:00
IsRunning = false;
OnComplete?.Invoke();
2024-06-11 19:18:48 -04:00
}
/// <summary>
/// Hollows out all logic from the dll
/// </summary>
private void Hollow()
{
2024-12-31 03:13:29 -05:00
foreach (var type in Module!.GetTypes())
{
foreach (var method in type.Methods.Where(m => m.HasBody))
{
if (!method.HasBody) continue;
method.Body = new CilBody();
method.Body.Instructions.Add(OpCodes.Ret.ToInstruction());
}
}
}
2024-12-31 13:18:11 -05:00
/// <summary>
/// This is used to log that all types for a given remap were filtered out.
/// </summary>
/// <param name="remap">remap model that failed</param>
/// <param name="noMatchReason">Reason for filtering</param>
private static void AllTypesFilteredOutFor(RemapModel remap, ENoMatchReason noMatchReason)
{
Logger.Log($"All types filtered out after `{noMatchReason}` filter for: `{remap.NewTypeName}`", ConsoleColor.Red);
}
2024-06-11 19:18:48 -04:00
}