2024-06-26 14:45:54 -04:00
using dnlib.DotNet ;
using dnlib.DotNet.Emit ;
2024-06-18 20:42:58 -04:00
using ReCodeIt.CrossCompiler ;
2024-06-14 19:06:21 -04:00
using ReCodeIt.Models ;
using ReCodeIt.ReMapper.Search ;
using ReCodeIt.Utils ;
2024-06-26 14:45:54 -04:00
using ReCodeItLib.Remapper.Search ;
2024-06-13 17:05:34 -04:00
using System.Diagnostics ;
2024-06-28 17:13:47 -04:00
using ReCodeIt.Enums ;
2024-06-11 19:18:48 -04:00
2024-06-14 19:06:21 -04:00
namespace ReCodeIt.ReMapper ;
2024-06-11 19:18:48 -04:00
2024-06-14 19:06:21 -04:00
public class ReCodeItRemapper
2024-06-11 19:18:48 -04:00
{
2024-06-18 17:35:31 -04:00
public ReCodeItRemapper ( ReCodeItCrossCompiler compiler )
{
_compiler = compiler ;
}
public ReCodeItRemapper ( )
{ }
2024-06-28 16:17:54 -04:00
private ModuleDefMD ? Module { get ; set ; }
2024-06-26 14:45:54 -04:00
2024-06-18 17:35:31 -04:00
private readonly ReCodeItCrossCompiler _compiler ;
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 14:34:24 -04:00
2024-06-14 18:20:12 -04:00
public event OnCompleteHandler ? OnComplete ;
2024-06-14 14:34:24 -04:00
2024-06-14 18:20:12 -04:00
private static readonly Stopwatch Stopwatch = new ( ) ;
2024-06-13 17:05:34 -04:00
2024-06-17 17:29:26 -04:00
private RemapperSettings Settings = > DataProvider . Settings . Remapper ;
2024-06-18 17:35:31 -04:00
private string OutPath { get ; set ; } = string . Empty ;
private bool CrossMapMode { get ; set ; } = false ;
2024-06-19 04:10:46 -04:00
private string AssemblyPath { get ; set ; }
2024-06-26 14:45:54 -04:00
private List < RemapModel > _remaps = [ ] ;
2024-06-28 17:13:47 -04:00
private List < string > _alreadyGivenNames = [ ] ;
2024-06-13 01:53:43 -04:00
/// <summary>
/// Start the remapping process
/// </summary>
2024-06-18 23:58:08 -04:00
public void InitializeRemap (
List < RemapModel > remapModels ,
string assemblyPath ,
string outPath ,
2024-06-28 16:17:54 -04:00
bool crossMapMode = false ,
bool validate = false )
2024-06-11 19:18:48 -04:00
{
2024-06-26 14:45:54 -04:00
_remaps = [ ] ;
_remaps = remapModels ;
Module = DataProvider . LoadModule ( assemblyPath ) ;
2024-06-18 17:35:31 -04:00
2024-06-19 04:10:46 -04:00
AssemblyPath = assemblyPath ;
2024-06-18 17:35:31 -04:00
CrossMapMode = crossMapMode ;
OutPath = outPath ;
2024-06-16 19:10:39 -04:00
2024-06-28 17:13:47 -04:00
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-06-26 14:45:54 -04:00
var types = Module . GetTypes ( ) ;
2024-06-28 17:13:47 -04:00
2024-06-26 14:45:54 -04:00
var tasks = new List < Task > ( remapModels . Count ) ;
2024-06-18 23:58:08 -04:00
foreach ( var remap in remapModels )
2024-06-11 19:18:48 -04:00
{
2024-06-26 14:45:54 -04:00
tasks . Add (
Task . Factory . StartNew ( ( ) = >
{
Logger . Log ( $"\nFinding best match for {remap.NewTypeName}..." , ConsoleColor . Gray ) ;
ScoreMapping ( remap , types ) ;
} )
) ;
2024-06-11 19:18:48 -04:00
}
2024-06-26 14:45:54 -04:00
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 )
{
DisplayEndBanner ( validate : true ) ;
return ;
}
2024-06-26 14:45:54 -04:00
var renameTasks = new List < Task > ( remapModels . Count ) ;
foreach ( var remap in remapModels )
2024-06-13 17:55:06 -04:00
{
2024-06-26 14:45:54 -04:00
renameTasks . Add (
Task . Factory . StartNew ( ( ) = >
{
RenameHelper . RenameAll ( types , remap ) ;
} )
) ;
2024-06-13 17:55:06 -04:00
}
2024-06-26 14:45:54 -04:00
Task . WaitAll ( renameTasks . ToArray ( ) ) ;
2024-06-13 17:55:06 -04:00
2024-06-26 14:45:54 -04:00
// Don't publicize and unseal until after the remapping, so we can use those as search parameters
if ( Settings . MappingSettings . Publicize )
2024-06-13 17:55:06 -04:00
{
2024-06-26 14:45:54 -04:00
Logger . Log ( "Publicizing classes..." , ConsoleColor . Yellow ) ;
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 ( ) ;
2024-06-19 05:58:45 -04:00
if ( CrossMapMode )
{
ProjectManager . SaveCrossCompilerProjectModel ( _compiler . ActiveProject ) ;
}
2024-06-11 19:18:48 -04:00
}
2024-06-28 17:13:47 -04:00
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>
2024-06-26 14:45:54 -04:00
/// 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>
2024-06-26 14:45:54 -04:00
private void ScoreMapping ( RemapModel mapping , IEnumerable < TypeDef > types )
2024-06-11 19:18:48 -04:00
{
2024-06-21 14:11:06 -04:00
var tokens = DataProvider . Settings . AutoMapper . TokensToMatch ;
2024-06-26 14:45:54 -04:00
if ( mapping . SearchParams . IsNested is false or null )
2024-06-21 14:11:06 -04:00
{
2024-06-26 14:45:54 -04:00
types = types . Where ( type = > tokens . Any ( token = > type . Name . StartsWith ( token ) ) ) ;
2024-06-21 14:11:06 -04:00
}
2024-06-26 14:45:54 -04:00
types = GenericTypeFilters . FilterPublic ( types , mapping . SearchParams ) ;
2024-06-11 19:18:48 -04:00
2024-06-26 14:45:54 -04:00
if ( ! types . Any ( ) )
2024-06-11 19:18:48 -04:00
{
2024-06-26 14:45:54 -04:00
Logger . Log ( $"All types filtered out after public filter for: {mapping.NewTypeName}" , ConsoleColor . Red ) ;
2024-06-11 19:18:48 -04:00
}
2024-06-26 14:45:54 -04:00
types = GenericTypeFilters . FilterAbstract ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterSealed ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterInterface ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterStruct ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterEnum ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterAttributes ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterDerived ( types , mapping . SearchParams ) ;
types = GenericTypeFilters . FilterByGenericParameters ( types , mapping . SearchParams ) ;
2024-06-11 23:07:59 -04:00
2024-06-26 14:45:54 -04:00
types = MethodTypeFilters . FilterByInclude ( types , mapping . SearchParams ) ;
types = MethodTypeFilters . FilterByExclude ( types , mapping . SearchParams ) ;
types = MethodTypeFilters . FilterByCount ( types , mapping . SearchParams ) ;
2024-06-18 17:35:31 -04:00
2024-06-26 14:45:54 -04:00
types = FieldTypeFilters . FilterByInclude ( types , mapping . SearchParams ) ;
types = FieldTypeFilters . FilterByExclude ( types , mapping . SearchParams ) ;
types = FieldTypeFilters . FilterByCount ( types , mapping . SearchParams ) ;
2024-06-18 17:35:31 -04:00
2024-06-26 14:45:54 -04:00
types = PropertyTypeFilters . FilterByInclude ( types , mapping . SearchParams ) ;
types = PropertyTypeFilters . FilterByExclude ( types , mapping . SearchParams ) ;
types = PropertyTypeFilters . FilterByCount ( types , mapping . SearchParams ) ;
2024-06-12 20:16:12 -04:00
2024-06-26 14:45:54 -04:00
types = CtorTypeFilters . FilterByParameterCount ( types , mapping . SearchParams ) ;
2024-06-12 14:38:43 -04:00
2024-06-26 14:45:54 -04:00
types = NestedTypeFilters . FilterByInclude ( types , mapping . SearchParams ) ;
types = NestedTypeFilters . FilterByExclude ( types , mapping . SearchParams ) ;
types = NestedTypeFilters . FilterByCount ( types , mapping . SearchParams ) ;
2024-06-12 14:38:43 -04:00
2024-06-26 14:45:54 -04:00
mapping . TypeCandidates . UnionWith ( types ) ;
2024-06-11 23:07:59 -04:00
}
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 ( )
{
2024-06-26 14:45:54 -04:00
foreach ( var remap in _remaps )
2024-06-11 19:18:48 -04:00
{
2024-06-26 14:45:54 -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>
2024-06-26 14:45:54 -04:00
private void ChooseBestMatch ( RemapModel remap )
2024-06-11 19:18:48 -04:00
{
2024-06-26 14:45:54 -04:00
if ( remap . TypeCandidates . Count = = 0 ) { return ; }
2024-06-13 08:18:16 -04:00
2024-06-26 14:45:54 -04:00
var winner = remap . TypeCandidates . FirstOrDefault ( ) ;
remap . TypePrimeCandidate = winner ;
remap . OriginalTypeName = winner . Name . String ;
2024-06-28 17:13:47 -04:00
2024-06-26 14:45:54 -04:00
if ( winner is null ) { return ; }
2024-06-11 19:18:48 -04:00
2024-06-28 17:13:47 -04:00
if ( _alreadyGivenNames . Contains ( winner . FullName ) )
{
2024-06-28 18:38:03 -04:00
remap . NoMatchReasons . Add ( ENoMatchReason . AmbiguousWithPreviousMatch ) ;
remap . AmbiguousTypeMatch = winner . FullName ;
2024-06-28 17:13:47 -04:00
remap . Succeeded = false ;
return ;
}
_alreadyGivenNames . Add ( winner . FullName ) ;
2024-06-26 14:45:54 -04:00
remap . Succeeded = true ;
2024-06-12 00:05:59 -04:00
2024-06-26 14:45:54 -04:00
remap . OriginalTypeName = winner . Name . String ;
2024-06-18 17:35:31 -04:00
if ( CrossMapMode )
{ // Store the original types for caching
2024-06-26 14:45:54 -04:00
//_compiler.ActiveProject.ChangedTypes.Add(highestScore.ProposedNewName, highestScore.Definition.Name);
2024-06-18 17:35:31 -04:00
}
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-06-26 14:45:54 -04:00
var moduleName = Module . Name ;
2024-06-22 12:12:18 -04:00
moduleName = moduleName . Replace ( ".dll" , "-Remapped.dll" ) ;
2024-06-22 01:27:34 -04:00
2024-06-22 12:12:18 -04:00
OutPath = Path . Combine ( OutPath , moduleName ) ;
2024-06-22 01:27:34 -04:00
2024-06-26 14:45:54 -04:00
try
{
Module . Write ( OutPath ) ;
}
catch ( Exception e )
{
Logger . Log ( e ) ;
throw ;
}
2024-06-11 19:18:48 -04:00
2024-06-22 12:12:18 -04:00
Logger . Log ( "Creating Hollow..." , ConsoleColor . Yellow ) ;
2024-06-22 01:27:34 -04:00
Hollow ( ) ;
var hollowedDir = Path . GetDirectoryName ( OutPath ) ;
var hollowedPath = Path . Combine ( hollowedDir , "Hollowed.dll" ) ;
2024-06-26 14:45:54 -04:00
Module . Write ( hollowedPath ) ;
2024-06-22 01:27:34 -04:00
2024-06-26 14:45:54 -04:00
DisplayEndBanner ( hollowedPath ) ;
if ( DataProvider . Settings . Remapper . MappingPath ! = string . Empty )
{
DataProvider . UpdateMapping ( DataProvider . Settings . Remapper . MappingPath , _remaps ) ;
}
2024-06-13 17:05:34 -04:00
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
}
2024-06-22 01:27:34 -04:00
/// <summary>
/// Hollows out all logic from the dll
/// </summary>
private void Hollow ( )
{
2024-06-26 14:45:54 -04:00
foreach ( var type in Module . GetTypes ( ) )
2024-06-22 01:27:34 -04:00
{
foreach ( var method in type . Methods . Where ( m = > m . HasBody ) )
{
2024-06-26 14:45:54 -04:00
if ( ! method . HasBody ) continue ;
method . Body = new CilBody ( ) ;
method . Body . Instructions . Add ( OpCodes . Ret . ToInstruction ( ) ) ;
}
}
}
2024-06-28 16:17:54 -04:00
private void DisplayEndBanner ( string hollowedPath = "" , bool validate = false )
2024-06-26 14:45:54 -04:00
{
var failures = 0 ;
var changes = 0 ;
2024-06-28 18:38:03 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
foreach ( var remap in _remaps )
{
if ( remap . Succeeded is false ) { continue ; }
var original = remap . OriginalTypeName ;
var proposed = remap . NewTypeName ;
Logger . Log ( $"Renamed {original} to {proposed}" , ConsoleColor . Green ) ;
2024-06-26 14:45:54 -04:00
2024-06-28 18:38:03 -04:00
DisplayAlternativeMatches ( remap ) ;
}
2024-06-26 14:45:54 -04:00
foreach ( var remap in _remaps )
{
2024-06-28 18:38:03 -04:00
if ( remap . Succeeded is false & & remap . NoMatchReasons . Contains ( ENoMatchReason . AmbiguousWithPreviousMatch ) )
{
Logger . Log ( "----------------------------------------------------------------------" , ConsoleColor . Red ) ;
Logger . Log ( "Ambiguous match with a previous match during matching. Skipping remap." , ConsoleColor . Red ) ;
Logger . Log ( $"New Type Name: {remap.NewTypeName}" , ConsoleColor . Red ) ;
Logger . Log ( $"{remap.AmbiguousTypeMatch} already assigned to a previous match." , ConsoleColor . Red ) ;
Logger . Log ( "----------------------------------------------------------------------" , ConsoleColor . Red ) ;
}
else if ( remap . Succeeded is false )
2024-06-26 14:45:54 -04:00
{
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Red ) ;
Logger . Log ( $"Renaming {remap.NewTypeName} failed with reason(s)" , ConsoleColor . Red ) ;
foreach ( var reason in remap . NoMatchReasons )
{
Logger . Log ( $"Reason: {reason}" , ConsoleColor . Red ) ;
}
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Red ) ;
failures + + ;
continue ;
}
2024-06-28 18:38:03 -04:00
2024-06-26 14:45:54 -04:00
changes + + ;
}
2024-06-28 18:38:03 -04:00
2024-06-26 14:45:54 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
Logger . Log ( $"Result renamed {changes} Types. Failed to rename {failures} Types" , ConsoleColor . Green ) ;
2024-06-28 18:38:03 -04:00
2024-06-28 16:17:54 -04:00
if ( ! validate )
{
Logger . Log ( $"Assembly written to `{OutPath}`" , ConsoleColor . Green ) ;
Logger . Log ( $"Hollowed written to `{hollowedPath}`" , ConsoleColor . Green ) ;
Logger . Log ( $"Remap took {Stopwatch.Elapsed.TotalSeconds:F1} seconds" , ConsoleColor . Green ) ;
}
2024-06-26 14:45:54 -04:00
}
private void DisplayAlternativeMatches ( RemapModel remap )
{
if ( remap . TypeCandidates . Count ( ) > 1 )
{
Logger . Log ( $"Warning! There were {remap.TypeCandidates.Count()} possible matches for {remap.NewTypeName}. Consider adding more search parameters, Only showing the first 5." , ConsoleColor . Yellow ) ;
foreach ( var type in remap . TypeCandidates . Skip ( 1 ) . Take ( 5 ) )
{
Logger . Log ( $"{type.Name}" , ConsoleColor . Yellow ) ;
2024-06-22 01:27:34 -04:00
}
}
}
2024-06-11 19:18:48 -04:00
}