2024-06-14 19:06:21 -04:00
using Mono.Cecil ;
2024-06-22 01:27:34 -04:00
using Mono.Cecil.Cil ;
using Mono.Cecil.Rocks ;
2024-06-18 20:42:58 -04:00
using ReCodeIt.CrossCompiler ;
2024-06-14 19:06:21 -04:00
using ReCodeIt.Enums ;
using ReCodeIt.Models ;
using ReCodeIt.ReMapper.Search ;
using ReCodeIt.Utils ;
2024-06-13 17:05:34 -04:00
using System.Diagnostics ;
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 ( )
{ }
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-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 ,
bool crossMapMode = false )
2024-06-11 19:18:48 -04:00
{
2024-06-18 17:35:31 -04:00
DataProvider . LoadAssemblyDefinition ( assemblyPath ) ;
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-14 14:18:17 -04:00
IsRunning = true ;
2024-06-11 19:18:48 -04:00
DisplayBasicModuleInformation ( ) ;
2024-06-13 17:05:34 -04:00
Stopwatch . Start ( ) ;
2024-06-18 23:58:08 -04:00
foreach ( var remap in remapModels )
2024-06-11 19:18:48 -04:00
{
2024-06-12 18:59:08 -04:00
Logger . Log ( $"Finding best match for {remap.NewTypeName}..." , ConsoleColor . Gray ) ;
2024-06-11 19:18:48 -04:00
2024-06-14 16:18:43 -04:00
ScoreMapping ( remap ) ;
2024-06-11 19:18:48 -04:00
}
ChooseBestMatches ( ) ;
2024-06-22 01:27:34 -04:00
// Don't publicize and unseal until after the remapping, so we can use those as search parameters
2024-06-19 23:14:51 -04:00
if ( Settings . MappingSettings . Publicize )
2024-06-13 17:55:06 -04:00
{
2024-06-22 01:27:34 -04:00
//Publicizer.Publicize();
Logger . Log ( "Publicizing classes..." , ConsoleColor . Green ) ;
SPTPublicizer . PublicizeClasses ( DataProvider . ModuleDefinition ) ;
2024-06-13 17:55:06 -04:00
}
2024-06-19 23:14:51 -04:00
if ( Settings . MappingSettings . Unseal )
2024-06-13 17:55:06 -04:00
{
2024-06-22 01:27:34 -04:00
//Publicizer.Unseal();
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-13 01:53:43 -04:00
/// <summary>
/// Display information about the module we are remapping
/// </summary>
2024-06-12 14:38:43 -04:00
private void DisplayBasicModuleInformation ( )
{
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Yellow ) ;
Logger . Log ( $"Starting remap..." , ConsoleColor . Yellow ) ;
Logger . Log ( $"Module contains {DataProvider.ModuleDefinition.Types.Count} Types" , ConsoleColor . Yellow ) ;
2024-06-17 18:19:17 -04:00
Logger . Log ( $"Publicize: {Settings.MappingSettings.Publicize}" , ConsoleColor . Yellow ) ;
Logger . Log ( $"Unseal: {Settings.MappingSettings.Unseal}" , ConsoleColor . Yellow ) ;
2024-06-12 14:38:43 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Yellow ) ;
}
2024-06-13 01:53:43 -04:00
/// <summary>
/// Loop over all types in the assembly and score them
/// </summary>
/// <param name="mapping">Mapping to score</param>
2024-06-14 16:18:43 -04:00
public void ScoreMapping ( RemapModel mapping )
2024-06-11 19:18:48 -04:00
{
foreach ( var type in DataProvider . ModuleDefinition . Types )
{
2024-06-14 16:18:43 -04:00
FindMatch ( type , mapping ) ;
2024-06-11 19:18:48 -04:00
}
}
2024-06-13 01:53:43 -04:00
/// <summary>
2024-06-13 17:31:38 -04:00
/// Find a match result
2024-06-13 01:53:43 -04:00
/// </summary>
2024-06-16 00:19:02 -04:00
/// <param name="type">OriginalTypeRef to score</param>
2024-06-13 01:53:43 -04:00
/// <param name="remap">Remap to check against</param>
/// <param name="parentTypeName"></param>
2024-06-13 17:31:38 -04:00
/// <returns>EMatchResult</returns>
2024-06-14 16:18:43 -04:00
private void FindMatch ( TypeDefinition type , RemapModel remap )
2024-06-11 19:18:48 -04:00
{
2024-06-11 23:07:59 -04:00
// Handle Direct Remaps by strict naming first bypasses everything else
if ( remap . UseForceRename )
{
HandleByDirectName ( type , remap ) ;
2024-06-14 16:18:43 -04:00
return ;
2024-06-11 23:07:59 -04:00
}
2024-06-21 14:11:06 -04:00
var tokens = DataProvider . Settings . AutoMapper . TokensToMatch ;
2024-06-22 02:28:36 -04:00
bool ignore = tokens
. Where ( token = > ! tokens
. Any ( token = > type . Name . StartsWith ( token ) ) ) . Any ( ) ;
if ( ignore & & remap . SearchParams . IsNested is null )
2024-06-21 14:11:06 -04:00
{
return ;
}
2024-06-11 19:18:48 -04:00
foreach ( var nestedType in type . NestedTypes )
{
2024-06-13 17:37:14 -04:00
FindMatch ( nestedType , remap ) ;
2024-06-11 19:18:48 -04:00
}
var score = new ScoringModel
{
ProposedNewName = remap . NewTypeName ,
2024-06-13 01:46:51 -04:00
ReMap = remap ,
2024-06-12 00:05:59 -04:00
Definition = type ,
2024-06-11 19:18:48 -04:00
} ;
2024-06-21 21:46:10 -04:00
var matches = new List < EMatchResult >
2024-06-13 17:05:34 -04:00
{
2024-06-21 21:46:10 -04:00
type . MatchConstructors ( remap . SearchParams , score ) ,
type . MatchMethods ( remap . SearchParams , score ) ,
type . MatchFields ( remap . SearchParams , score ) ,
type . MatchProperties ( remap . SearchParams , score ) ,
type . MatchNestedTypes ( remap . SearchParams , score ) ,
type . MatchIsPublic ( remap . SearchParams , score ) ,
type . MatchIsInterface ( remap . SearchParams , score ) ,
2024-06-13 17:05:34 -04:00
type . MatchIsAbstract ( remap . SearchParams , score ) ,
2024-06-21 21:46:10 -04:00
type . MatchIsSealed ( remap . SearchParams , score ) ,
2024-06-13 17:05:34 -04:00
type . MatchIsEnum ( remap . SearchParams , score ) ,
type . MatchIsNested ( remap . SearchParams , score ) ,
type . MatchIsDerived ( remap . SearchParams , score ) ,
type . MatchHasGenericParameters ( remap . SearchParams , score ) ,
type . MatchHasAttribute ( remap . SearchParams , score ) ,
} ;
2024-06-13 04:56:08 -04:00
2024-06-13 17:05:34 -04:00
var NoMatch = matches . Where ( x = > x . Equals ( EMatchResult . NoMatch ) ) . FirstOrDefault ( ) ;
2024-06-11 19:18:48 -04:00
2024-06-13 17:05:34 -04:00
if ( NoMatch = = EMatchResult . NoMatch )
2024-06-11 19:18:48 -04:00
{
2024-06-14 16:18:43 -04:00
remap . FailureReason = score . FailureReason ;
return ;
2024-06-11 19:18:48 -04:00
}
2024-06-21 05:18:23 -04:00
var match = matches
. Where ( x = > x . Equals ( EMatchResult . Match ) )
. Where ( x = > ! x . Equals ( EMatchResult . Disabled ) )
. Any ( ) ;
2024-06-11 19:18:48 -04:00
2024-06-13 17:05:34 -04:00
if ( match )
2024-06-11 19:18:48 -04:00
{
2024-06-13 17:05:34 -04:00
// Set the original type name to be used later
2024-06-22 02:28:36 -04:00
score . ReMap . OriginalTypeName = type . FullName ;
remap . OriginalTypeName = type . FullName ;
2024-06-13 17:05:34 -04:00
remap . Succeeded = true ;
remap . FailureReason = EFailureReason . None ;
score . AddScoreToResult ( ) ;
2024-06-11 19:18:48 -04:00
}
}
2024-06-12 00:05:59 -04:00
private void HandleByDirectName ( TypeDefinition type , RemapModel remap )
2024-06-11 23:07:59 -04:00
{
2024-06-12 00:05:59 -04:00
if ( type . Name ! = remap . OriginalTypeName ) { return ; }
2024-06-11 23:07:59 -04:00
var oldName = type . Name ;
2024-06-12 20:16:12 -04:00
remap . OriginalTypeName = type . Name ;
2024-06-13 01:46:51 -04:00
remap . FailureReason = EFailureReason . None ;
remap . Succeeded = true ;
2024-06-18 17:35:31 -04:00
if ( CrossMapMode )
{
// Store the original types for caching
2024-06-19 05:58:45 -04:00
_compiler . ActiveProject . ChangedTypes . Add ( remap . NewTypeName , type . Name ) ;
2024-06-18 17:35:31 -04:00
}
2024-06-12 22:31:41 -04:00
type . Name = remap . NewTypeName ;
2024-06-12 20:16:12 -04:00
2024-06-12 14:38:43 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
2024-06-11 23:07:59 -04:00
Logger . Log ( $"Renamed {oldName} to {type.Name} directly" , ConsoleColor . Green ) ;
2024-06-12 14:38:43 -04:00
2024-06-16 01:48:48 -04:00
RenameHelper . RenameAllDirect ( remap , type ) ;
2024-06-12 14:38:43 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
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-11 23:07:59 -04:00
foreach ( var score in DataProvider . ScoringModels )
2024-06-11 19:18:48 -04:00
{
2024-06-13 01:53:43 -04:00
ChooseBestMatch ( score . Value ) ;
2024-06-11 19:18:48 -04:00
}
2024-06-13 01:46:51 -04:00
var failures = 0 ;
var changes = 0 ;
foreach ( var remap in DataProvider . Remaps )
{
if ( remap . Succeeded is false )
{
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Red ) ;
Logger . Log ( $"Renaming {remap.NewTypeName} failed with reason {remap.FailureReason}" , ConsoleColor . Red ) ;
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Red ) ;
failures + + ;
2024-06-18 17:35:31 -04:00
continue ;
2024-06-13 01:46:51 -04:00
}
changes + + ;
}
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Yellow ) ;
Logger . Log ( $"Result: Remapped {changes} Types. Failed to remap {failures} Types" , ConsoleColor . Yellow ) ;
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Yellow ) ;
2024-06-11 19:18:48 -04:00
}
2024-06-13 01:53:43 -04:00
/// <summary>
/// Choose best match from a collection of scores, then start the renaming process
/// </summary>
/// <param name="scores">Scores to rate</param>
private void ChooseBestMatch ( HashSet < ScoringModel > scores )
2024-06-11 19:18:48 -04:00
{
2024-06-13 01:46:51 -04:00
if ( scores . Count = = 0 ) { return ; }
2024-06-11 19:18:48 -04:00
2024-06-13 08:18:16 -04:00
var filteredScores = scores
2024-06-21 05:18:23 -04:00
. Where ( score = > score . Score > 0 )
. OrderByDescending ( score = > score . Score )
. Take ( 5 ) ;
2024-06-13 08:18:16 -04:00
var highestScore = filteredScores . FirstOrDefault ( ) ;
2024-06-11 19:18:48 -04:00
2024-06-11 23:07:59 -04:00
if ( highestScore is null ) { return ; }
2024-06-11 19:18:48 -04:00
2024-06-11 23:07:59 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
2024-06-22 02:28:36 -04:00
Logger . Log ( $"Renaming {highestScore.Definition.FullName} to {highestScore.ProposedNewName}" , ConsoleColor . Green ) ;
2024-06-12 15:47:11 -04:00
Logger . Log ( $"Scored: {highestScore.Score} points" , ConsoleColor . Green ) ;
2024-06-21 21:46:10 -04:00
if ( filteredScores . Count ( ) > 1 & & filteredScores . Skip ( 1 ) . Any ( score = > score . Score = = highestScore . Score ) )
2024-06-12 15:47:11 -04:00
{
2024-06-21 05:18:23 -04:00
Logger . Log ( $"Warning! There were {filteredScores.Count()} possible matches. Considering adding more search parameters, Only showing first 5." , ConsoleColor . Yellow ) ;
2024-06-12 15:47:11 -04:00
2024-06-20 23:33:55 -04:00
foreach ( var score in filteredScores . Skip ( 1 ) . Take ( 5 ) )
2024-06-12 15:47:11 -04:00
{
Logger . Log ( $"{score.Definition.Name} - Score [{score.Score}]" , ConsoleColor . Yellow ) ;
}
}
2024-06-12 00:05:59 -04:00
2024-06-22 02:28:36 -04:00
highestScore . ReMap . OriginalTypeName = highestScore . Definition . Name ;
2024-06-18 17:35:31 -04:00
if ( CrossMapMode )
{ // Store the original types for caching
2024-06-19 05:58:45 -04:00
_compiler . ActiveProject . ChangedTypes . Add ( highestScore . ProposedNewName , highestScore . Definition . Name ) ;
2024-06-18 17:35:31 -04:00
}
2024-06-12 20:40:10 -04:00
2024-06-12 14:38:43 -04:00
// Rename type and all associated type members
2024-06-21 17:01:07 -04:00
2024-06-16 01:48:48 -04:00
RenameHelper . RenameAll ( highestScore ) ;
2024-06-11 19:18:48 -04:00
2024-06-11 23:07:59 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
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-22 01:27:34 -04:00
if ( ! OutPath . EndsWith ( ".dll" ) )
{
var moduleName = DataProvider . AssemblyDefinition . MainModule . Name ;
moduleName = moduleName . Replace ( ".dll" , "-Remapped.dll" ) ;
OutPath = Path . Combine ( OutPath , moduleName ) ;
}
2024-06-21 17:01:07 -04:00
var path = DataProvider . WriteAssemblyDefinition ( OutPath ) ;
2024-06-11 19:18:48 -04:00
2024-06-22 01:27:34 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
Hollow ( ) ;
var hollowedDir = Path . GetDirectoryName ( OutPath ) ;
var hollowedPath = Path . Combine ( hollowedDir , "Hollowed.dll" ) ;
DataProvider . WriteAssemblyDefinition ( hollowedPath ) ;
2024-06-11 23:07:59 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
2024-06-16 03:43:00 -04:00
Logger . Log ( $"Complete: Assembly written to `{path}`" , ConsoleColor . Green ) ;
2024-06-22 01:27:34 -04:00
Logger . Log ( $"Complete: Hollowed written to `{hollowedPath}`" , ConsoleColor . Green ) ;
2024-06-12 20:40:10 -04:00
Logger . Log ( "Original type names updated on mapping file." , ConsoleColor . Green ) ;
2024-06-16 03:43:00 -04:00
Logger . Log ( $"Remap took {Stopwatch.Elapsed.TotalSeconds:F1} seconds" , ConsoleColor . Green ) ;
2024-06-11 23:07:59 -04:00
Logger . Log ( "-----------------------------------------------" , ConsoleColor . Green ) ;
2024-06-13 17:05:34 -04:00
2024-06-14 14:18:17 -04:00
DataProvider . ScoringModels = [ ] ;
Stopwatch . Reset ( ) ;
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 ( )
{
foreach ( var type in DataProvider . ModuleDefinition . GetAllTypes ( ) )
{
foreach ( var method in type . Methods . Where ( m = > m . HasBody ) )
{
var ilProcessor = method . Body . GetILProcessor ( ) ;
// Remove existing instructions
ilProcessor . Clear ( ) ;
if ( method . ReturnType . FullName ! = "System.Void" )
{
// Return appropriate default value based on return type
if ( method . ReturnType . IsValueType )
{
// Load 0 onto the stack (works for most value types)
ilProcessor . Emit ( OpCodes . Ldc_I4_0 ) ;
// Convert to Int64 if needed
if ( method . ReturnType . FullName = = "System.Int64" )
ilProcessor . Emit ( OpCodes . Conv_I8 ) ;
}
else
{
// Load null for reference types
ilProcessor . Emit ( OpCodes . Ldnull ) ;
}
}
// Add a return instruction
ilProcessor . Emit ( OpCodes . Ret ) ;
}
}
}
2024-06-11 19:18:48 -04:00
}