mirror of
https://github.com/LukeFZ/Il2CppInspectorRedux.git
synced 2026-03-22 00:18:18 +05:00
implement (redux cli only) support for specifying a name translation map
This commit is contained in:
@@ -60,6 +60,10 @@
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Next\NameTranslation\" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
|
||||
<ItemGroup>
|
||||
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
using dnlib.DotNet;
|
||||
using Il2CppInspector.Reflection;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Il2CppInspector.Next.NameTranslation;
|
||||
|
||||
public readonly struct NameTranslationApplierContext
|
||||
{
|
||||
private readonly NameTranslationInfo _nameTranslationInfo;
|
||||
|
||||
private NameTranslationApplierContext(NameTranslationInfo nameTranslationInfo)
|
||||
{
|
||||
_nameTranslationInfo = nameTranslationInfo;
|
||||
}
|
||||
|
||||
public static void Process(Assembly assemblyDefinition, NameTranslationInfo nameTranslationInfo)
|
||||
{
|
||||
var ctx = new NameTranslationApplierContext(nameTranslationInfo);
|
||||
ctx.Process(assemblyDefinition);
|
||||
}
|
||||
|
||||
private string TranslateName(string name) =>
|
||||
_nameTranslationInfo.NameTranslation.GetValueOrDefault(name, name);
|
||||
|
||||
private bool TryTranslateName(string name, out string translatedName)
|
||||
{
|
||||
if (_nameTranslationInfo.NameTranslation.TryGetValue(name, out var translated))
|
||||
{
|
||||
translatedName = translated;
|
||||
return true;
|
||||
}
|
||||
|
||||
translatedName = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
private string TranslateNamespace(string className, string currentNamespace) =>
|
||||
_nameTranslationInfo.ClassNamespaces.GetValueOrDefault(className, currentNamespace);
|
||||
|
||||
private void Process(Assembly assemblyDefinition)
|
||||
{
|
||||
foreach (var module in assemblyDefinition.DefinedTypes)
|
||||
Process(module);
|
||||
}
|
||||
|
||||
private void Process(TypeInfo typeDefinition)
|
||||
{
|
||||
if (TryTranslateName(typeDefinition.Name, out var translatedName))
|
||||
{
|
||||
typeDefinition.Namespace = TranslateNamespace(typeDefinition.Name, typeDefinition.Namespace);
|
||||
typeDefinition.Name = translatedName;
|
||||
}
|
||||
|
||||
foreach (var method in typeDefinition.DeclaredMethods)
|
||||
Process(method);
|
||||
|
||||
foreach (var field in typeDefinition.DeclaredFields)
|
||||
Process(field);
|
||||
|
||||
foreach (var property in typeDefinition.DeclaredProperties)
|
||||
Process(property);
|
||||
|
||||
foreach (var evt in typeDefinition.DeclaredEvents)
|
||||
Process(evt);
|
||||
|
||||
foreach (var nestedType in typeDefinition.DeclaredNestedTypes)
|
||||
Process(nestedType);
|
||||
}
|
||||
|
||||
private void Process(MethodInfo methodDefinition)
|
||||
{
|
||||
methodDefinition.Name = TranslateName(methodDefinition.Name);
|
||||
|
||||
foreach (var parameter in methodDefinition.DeclaredParameters)
|
||||
Process(parameter);
|
||||
}
|
||||
|
||||
private void Process(FieldInfo fieldDefinition)
|
||||
{
|
||||
fieldDefinition.Name = TranslateName(fieldDefinition.Name);
|
||||
}
|
||||
|
||||
private void Process(PropertyInfo propertyDefinition)
|
||||
{
|
||||
propertyDefinition.Name = TranslateName(propertyDefinition.Name);
|
||||
}
|
||||
|
||||
private void Process(EventInfo eventDefinition)
|
||||
{
|
||||
eventDefinition.Name = TranslateName(eventDefinition.Name);
|
||||
}
|
||||
|
||||
private void Process(ParameterInfo parameterDefinition)
|
||||
{
|
||||
parameterDefinition.Name = TranslateName(parameterDefinition.Name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Il2CppInspector.Next.NameTranslation;
|
||||
|
||||
public sealed record NameTranslationInfo(
|
||||
FrozenDictionary<string, string> NameTranslation,
|
||||
FrozenDictionary<string, string> ClassNamespaces
|
||||
);
|
||||
@@ -0,0 +1,182 @@
|
||||
using dnlib.DotNet;
|
||||
using System.Collections.Frozen;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Il2CppInspector.Next.NameTranslation;
|
||||
|
||||
public ref struct NameTranslationParserContext
|
||||
{
|
||||
private enum CurrentSection
|
||||
{
|
||||
None,
|
||||
Classes,
|
||||
Methods,
|
||||
Fields,
|
||||
Properties,
|
||||
Events,
|
||||
Parameters
|
||||
}
|
||||
|
||||
private CurrentSection _currentSection;
|
||||
private bool _reverseOrder;
|
||||
|
||||
private readonly ReadOnlySpan<string> _nameTranslationLines;
|
||||
private readonly char _seperator;
|
||||
|
||||
private NameTranslationParserContext(ReadOnlySpan<string> nameTranslationLines, char seperator)
|
||||
{
|
||||
_currentSection = CurrentSection.None;
|
||||
_nameTranslationLines = nameTranslationLines;
|
||||
_seperator = seperator;
|
||||
}
|
||||
|
||||
public static NameTranslationInfo Parse(ReadOnlySpan<string> nameTranslationLines, char seperator = '⇨')
|
||||
{
|
||||
var ctx = new NameTranslationParserContext(nameTranslationLines, seperator);
|
||||
return ctx.Parse();
|
||||
}
|
||||
|
||||
private void HandleMetadataLine(string line)
|
||||
{
|
||||
switch (line)
|
||||
{
|
||||
case "#ReverseOrder":
|
||||
_reverseOrder = true;
|
||||
break;
|
||||
case "#Classes":
|
||||
_currentSection = CurrentSection.Classes;
|
||||
break;
|
||||
case "#Methods":
|
||||
_currentSection = CurrentSection.Methods;
|
||||
break;
|
||||
case "#Fields":
|
||||
_currentSection = CurrentSection.Fields;
|
||||
break;
|
||||
case "#Properties":
|
||||
_currentSection = CurrentSection.Properties;
|
||||
break;
|
||||
case "#Events":
|
||||
_currentSection = CurrentSection.Events;
|
||||
break;
|
||||
case "#Parameters":
|
||||
_currentSection = CurrentSection.Parameters;
|
||||
break;
|
||||
default:
|
||||
Debug.Assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private NameTranslationInfo Parse()
|
||||
{
|
||||
var nameTranslationMap = new Dictionary<string, string>(_nameTranslationLines.Length);
|
||||
var namespaceTranslationMap = new Dictionary<string, string>();
|
||||
|
||||
foreach (var line in _nameTranslationLines)
|
||||
{
|
||||
if (line.StartsWith('#'))
|
||||
{
|
||||
HandleMetadataLine(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
var split = line.Split(_seperator, 2);
|
||||
|
||||
var obfuscatedName = split[0];
|
||||
var deobfuscatedFullName = split[1].TrimEnd();
|
||||
if (!_reverseOrder)
|
||||
{
|
||||
(obfuscatedName, deobfuscatedFullName) = (deobfuscatedFullName, obfuscatedName);
|
||||
}
|
||||
|
||||
string deobfuscatedName;
|
||||
string? deobfuscatedNamespace;
|
||||
if (_currentSection != CurrentSection.Classes)
|
||||
{
|
||||
deobfuscatedNamespace = null;
|
||||
deobfuscatedName = ParseFullName(deobfuscatedFullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
(deobfuscatedName, deobfuscatedNamespace) = ParseFullClassName(deobfuscatedFullName);
|
||||
}
|
||||
|
||||
// This sometimes happens, just ignore it if so
|
||||
if (obfuscatedName == deobfuscatedName)
|
||||
continue;
|
||||
|
||||
if (nameTranslationMap.TryGetValue(obfuscatedName, out var alreadySeenName))
|
||||
{
|
||||
if (alreadySeenName != deobfuscatedName)
|
||||
throw new InvalidDataException(
|
||||
$"Name translation collision detected for name {obfuscatedName}: {alreadySeenName} vs. {deobfuscatedName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
nameTranslationMap[obfuscatedName] = deobfuscatedName;
|
||||
|
||||
if (deobfuscatedNamespace != null)
|
||||
namespaceTranslationMap[obfuscatedName] = deobfuscatedNamespace;
|
||||
}
|
||||
}
|
||||
|
||||
return new NameTranslationInfo(
|
||||
nameTranslationMap
|
||||
.ToFrozenDictionary(),
|
||||
namespaceTranslationMap
|
||||
.ToFrozenDictionary());
|
||||
}
|
||||
|
||||
private string ParseFullName(string fullName)
|
||||
=> _currentSection switch
|
||||
{
|
||||
CurrentSection.Classes => ParseFullClassName(fullName).Item1,
|
||||
CurrentSection.Methods or CurrentSection.Properties => ParseFullMemberWithArgumentsName(fullName),
|
||||
CurrentSection.Fields or CurrentSection.Events => ParseFullMemberName(fullName),
|
||||
CurrentSection.Parameters => ParseFullParameterName(fullName),
|
||||
_ => throw new UnreachableException()
|
||||
};
|
||||
|
||||
private static (string, string?) ParseFullClassName(string fullName)
|
||||
{
|
||||
// For the namespace, return everything up until the last part of the fully qualified class name
|
||||
var lastNamespaceIdx = fullName.LastIndexOf('.');
|
||||
|
||||
var ns = lastNamespaceIdx == -1
|
||||
? null
|
||||
: fullName[..lastNamespaceIdx];
|
||||
|
||||
// If we have a nested class, return that name
|
||||
var nestedClassIdx = fullName.LastIndexOf('/') + 1;
|
||||
if (nestedClassIdx != 0)
|
||||
return (fullName[nestedClassIdx..], ns);
|
||||
|
||||
// Otherwise, if we have a namespace, return the last part of the fully qualified name
|
||||
if (lastNamespaceIdx != -1)
|
||||
return (fullName[(lastNamespaceIdx + 1)..], ns);
|
||||
|
||||
// Otherwise the class has no namespace, return the full name
|
||||
return (fullName, ns);
|
||||
}
|
||||
|
||||
// Used for both fields and events
|
||||
private static string ParseFullMemberName(string fullName)
|
||||
{
|
||||
var memberNameStartIdx = fullName.LastIndexOf("::", StringComparison.Ordinal) + 2;
|
||||
return fullName[memberNameStartIdx..];
|
||||
}
|
||||
|
||||
private static string ParseFullMemberWithArgumentsName(string fullName)
|
||||
{
|
||||
var methodNameWithParameters = ParseFullMemberName(fullName);
|
||||
var parametersStartIdx = methodNameWithParameters.IndexOf('(');
|
||||
|
||||
return methodNameWithParameters[..parametersStartIdx];
|
||||
}
|
||||
|
||||
private static string ParseFullParameterName(string fullName)
|
||||
{
|
||||
var parameterNameStartIdx = fullName.LastIndexOf(' ') + 1;
|
||||
return fullName[parameterNameStartIdx..];
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ namespace Il2CppInspector.Reflection
|
||||
public int MetadataToken { get; }
|
||||
|
||||
// Name of parameter
|
||||
public string Name { get; }
|
||||
public string Name { get; set; }
|
||||
public string CSharpName => Constants.Keywords.Contains(Name) ? "@" + Name : Name.ToCIdentifier();
|
||||
|
||||
// Type of this parameter
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.NameTranslation;
|
||||
|
||||
namespace Il2CppInspector.Reflection
|
||||
{
|
||||
@@ -187,6 +188,19 @@ namespace Il2CppInspector.Reflection
|
||||
PluginHooks.PostProcessTypeModel(this);
|
||||
}
|
||||
|
||||
public void ApplyNameTranslationFromFile(string nameTranslationMapPath)
|
||||
{
|
||||
var lines = File.ReadAllLines(nameTranslationMapPath);
|
||||
ApplyNameTranslation(lines);
|
||||
}
|
||||
|
||||
public void ApplyNameTranslation(ReadOnlySpan<string> nameTranslationLines)
|
||||
{
|
||||
var info = NameTranslationParserContext.Parse(nameTranslationLines);
|
||||
foreach (var assembly in Assemblies)
|
||||
NameTranslationApplierContext.Process(assembly, info);
|
||||
}
|
||||
|
||||
// Get generic arguments from either a type or method instanceIndex from a MethodSpec
|
||||
public TypeInfo[] ResolveGenericArguments(Il2CppGenericInst inst) {
|
||||
|
||||
|
||||
@@ -72,6 +72,9 @@ internal sealed class ProcessCommand(PortProvider portProvider) : ManualCommand<
|
||||
|
||||
[CommandOption("--image-base")]
|
||||
public string? ImageBase { get; init; }
|
||||
|
||||
[CommandOption("--name-translation-map")]
|
||||
public string? NameTranslationMap { get; init; }
|
||||
}
|
||||
|
||||
protected override async Task<int> ExecuteAsync(CliClient client, Settings settings)
|
||||
@@ -79,17 +82,19 @@ internal sealed class ProcessCommand(PortProvider portProvider) : ManualCommand<
|
||||
var inspectorVersion = await client.GetInspectorVersion();
|
||||
AnsiConsole.MarkupLineInterpolated($"Using inspector [gray]{inspectorVersion}[/]");
|
||||
|
||||
var imageBase = 0uL;
|
||||
if (settings.ImageBase != null)
|
||||
{
|
||||
var imageBase = ulong.Parse(settings.ImageBase,
|
||||
imageBase = ulong.Parse(settings.ImageBase,
|
||||
settings.ImageBase.StartsWith("0x")
|
||||
? NumberStyles.HexNumber
|
||||
: NumberStyles.Integer);
|
||||
|
||||
AnsiConsole.MarkupLineInterpolated($"Setting image base to [white]0x{imageBase:x}[/]");
|
||||
await client.SetSettings(new InspectorSettings(imageBase));
|
||||
}
|
||||
|
||||
await client.SetSettings(new InspectorSettings(imageBase, settings.NameTranslationMap));
|
||||
|
||||
await client.SubmitInputFiles(settings.InputPaths.ToList());
|
||||
await client.WaitForLoadingToFinishAsync();
|
||||
if (!client.ImportCompleted)
|
||||
@@ -176,6 +181,9 @@ internal sealed class ProcessCommand(PortProvider portProvider) : ManualCommand<
|
||||
return ValidationResult.Error(
|
||||
$"Provided extracted IL2CPP files path {settings.ExtractIl2CppFilesPath} already exists as a file.");
|
||||
|
||||
if (settings.NameTranslationMap != null && !File.Exists(settings.NameTranslationMap))
|
||||
return ValidationResult.Error($"Provided name translation map {settings.NameTranslationMap} does not exist.");
|
||||
|
||||
if (settings is
|
||||
{
|
||||
CppScaffolding: false, CSharpStubs: false, DisassemblerMetadata: false, DummyDlls: false,
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public sealed record InspectorSettings(ulong ImageBase);
|
||||
public sealed record InspectorSettings(ulong ImageBase = 0, string? NameTranslationMapPath = null);
|
||||
@@ -23,6 +23,7 @@ public class UiContext
|
||||
private readonly List<UnityHeaders> _potentialUnityVersions = [];
|
||||
|
||||
private readonly LoadOptions _loadOptions = new();
|
||||
private InspectorSettings _settings = new();
|
||||
|
||||
private readonly List<(string FormatId, string OutputDirectory, Dictionary<string, string> Settings)> _queuedExports = [];
|
||||
|
||||
@@ -96,6 +97,12 @@ public class UiContext
|
||||
{
|
||||
var typeModel = new TypeModel(inspector);
|
||||
|
||||
if (_settings.NameTranslationMapPath != null)
|
||||
{
|
||||
await client.ShowLogMessage("Applying name translation map");
|
||||
typeModel.ApplyNameTranslationFromFile(_settings.NameTranslationMapPath);
|
||||
}
|
||||
|
||||
// Just create the app model, do not initialize it - this is done lazily depending on the exports
|
||||
_appModels.Add(new AppModel(typeModel, makeDefaultBuild: false));
|
||||
}
|
||||
@@ -127,6 +134,8 @@ public class UiContext
|
||||
{
|
||||
await using (await LoadingSession.Start(client))
|
||||
{
|
||||
_loadOptions.ImageBase = _settings.ImageBase;
|
||||
|
||||
var streams = Inspector.GetStreamsFromPackage(inputFiles);
|
||||
if (streams != null)
|
||||
{
|
||||
@@ -160,6 +169,7 @@ public class UiContext
|
||||
else if (_binary == null && PathHeuristics.IsBinaryPath(inputFile))
|
||||
{
|
||||
stream.Position = 0;
|
||||
|
||||
_loadOptions.BinaryFilePath = inputFile;
|
||||
|
||||
if (await TryLoadBinaryFromStreamAsync(client, stream))
|
||||
@@ -251,7 +261,7 @@ public class UiContext
|
||||
|
||||
public Task SetSettingsAsync(UiClient client, InspectorSettings settings)
|
||||
{
|
||||
_loadOptions.ImageBase = settings.ImageBase;
|
||||
_settings = settings;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user