Implement new GUI and CLI, fix misc. smaller issues (#22)
* Initial commit of new UI c# component * Initial commit of new UI frontend component * target WinExe to hide console window in release mode, move ui exe into resources * force single file publishing and add initial gh workflow for publishing ui * fix workflow errors * update dependencies and remove cxxdemangler, as it was outdated * fix c# single file output due to invalid output path * smaller tweaks, hack around loops in cpp type layouting * process other queued exports even if one fails and show error message * add basic support for processing LC_DYLD_CHAINED_FIXUPS * ELF loading should not use the file offset for loading the dynamic section * fix symbol table loading in some modified elfs * add "start export" button on format selection screen, clear all toasts after selecting an export format * embed ui executable directly into c# assembly * only build tauri component in c# release builds * add il2cpp file (binary, metadata) export to advanced tab * fix and enable binary ninja fake string segment support * add support for metadata * unify logic for getting element type index * fix new ui not allowing script exports other than ida * new ui: clear out loaded binary if no IL2CPP images could be loaded * fix toAddr calls in ghidra script target * remove dependency on a section being named .text in loaded pe files * tweak symbol reading a bit and remove sht relocation reading * add initial support for required forward references in il2cpp types, also fix issues with type names clashing with il2cpp api types * reduce clang errors for header file, fix better array size struct, emit required forward definitions in header * expose forward definitions in AppModel, fix issue with method-only used types not being emitted * remove debug log line * fix spelling mistakes in gui outputs * fix il2cpp_array_size_t not being an actual type for later method definitions * change the default port for new ui dev to 5000 * show current version and hash in new ui footer * seperate redux ui impl into FrontendCore project * make inspector version a server api, split up output subtypes and tweak some option names * add redux CLI based on redux GUI output formats * replace all Console.WriteLine calls in core inspector with AnsiConsole calls * add workflow for new cli and add back old gui workflow * disable aot publish and enable single file for redux cli
This commit is contained in:
54
Il2CppInspector.Redux.FrontendCore/Extensions.cs
Normal file
54
Il2CppInspector.Redux.FrontendCore/Extensions.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
internal static bool GetAsBooleanOrDefault(this Dictionary<string, string> dict, string key, bool defaultValue)
|
||||
{
|
||||
if (dict.TryGetValue(key, out var value) && bool.TryParse(value, out var boolResult))
|
||||
return boolResult;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
internal static T GetAsEnumOrDefault<T>(this Dictionary<string, string> dict, string key, T defaultValue)
|
||||
where T : struct, Enum
|
||||
{
|
||||
if (dict.TryGetValue(key, out var value) && Enum.TryParse<T>(value, true, out var enumResult))
|
||||
return enumResult;
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
internal static string? GetAssemblyVersion(this Assembly assembly)
|
||||
=> assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
|
||||
|
||||
public static WebApplication MapFrontendCore(this WebApplication app)
|
||||
{
|
||||
app.MapHub<Il2CppHub>("/il2cpp");
|
||||
return app;
|
||||
}
|
||||
|
||||
public static IServiceCollection AddFrontendCore(this IServiceCollection services)
|
||||
{
|
||||
services.AddSignalR(config =>
|
||||
{
|
||||
#if DEBUG
|
||||
config.EnableDetailedErrors = true;
|
||||
#endif
|
||||
});
|
||||
|
||||
return services.AddCors(options =>
|
||||
{
|
||||
options.AddDefaultPolicy(policy =>
|
||||
{
|
||||
policy.SetIsOriginAllowed(origin =>
|
||||
origin.StartsWith("http://localhost") || origin.StartsWith("http://tauri.localhost"))
|
||||
.AllowAnyHeader()
|
||||
.WithMethods("GET", "POST")
|
||||
.AllowCredentials();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
[JsonSerializable(typeof(string))]
|
||||
[JsonSerializable(typeof(List<string>))]
|
||||
[JsonSerializable(typeof(Dictionary<string, string>))]
|
||||
public partial class FrontendCoreJsonSerializerContext : JsonSerializerContext;
|
||||
58
Il2CppInspector.Redux.FrontendCore/Il2CppHub.cs
Normal file
58
Il2CppInspector.Redux.FrontendCore/Il2CppHub.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public class Il2CppHub : Hub
|
||||
{
|
||||
private const string ContextKey = "context";
|
||||
|
||||
private UiContext State
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Context.Items.TryGetValue(ContextKey, out var context)
|
||||
|| context is not UiContext ctx)
|
||||
{
|
||||
Context.Items[ContextKey] = ctx = new UiContext();
|
||||
}
|
||||
|
||||
return ctx;
|
||||
}
|
||||
}
|
||||
|
||||
private UiClient Client => new(Clients.Caller);
|
||||
|
||||
public async Task OnUiLaunched()
|
||||
{
|
||||
await State.Initialize(Client);
|
||||
}
|
||||
|
||||
public async Task SubmitInputFiles(List<string> inputFiles)
|
||||
{
|
||||
await State.LoadInputFiles(Client, inputFiles);
|
||||
}
|
||||
|
||||
public async Task QueueExport(string exportTypeId, string outputDirectory, Dictionary<string, string> settings)
|
||||
{
|
||||
await State.QueueExport(Client, exportTypeId, outputDirectory, settings);
|
||||
}
|
||||
|
||||
public async Task StartExport()
|
||||
{
|
||||
await State.StartExport(Client);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<string>> GetPotentialUnityVersions()
|
||||
{
|
||||
return await State.GetPotentialUnityVersions();
|
||||
}
|
||||
|
||||
public async Task ExportIl2CppFiles(string outputDirectory)
|
||||
{
|
||||
await State.ExportIl2CppFiles(Client, outputDirectory);
|
||||
}
|
||||
public async Task<string> GetInspectorVersion()
|
||||
{
|
||||
return await UiContext.GetInspectorVersion();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<OutputType>Library</OutputType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Bin2Object\Bin2Object\Bin2Object.csproj" />
|
||||
<ProjectReference Include="..\Il2CppInspector.Common\Il2CppInspector.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
23
Il2CppInspector.Redux.FrontendCore/LoadingSession.cs
Normal file
23
Il2CppInspector.Redux.FrontendCore/LoadingSession.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public class LoadingSession : IAsyncDisposable
|
||||
{
|
||||
private readonly UiClient _client;
|
||||
|
||||
private LoadingSession(UiClient client)
|
||||
{
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public static async Task<LoadingSession> Start(UiClient client)
|
||||
{
|
||||
await client.BeginLoading();
|
||||
return new LoadingSession(client);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await _client.FinishLoading();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
10
Il2CppInspector.Redux.FrontendCore/Outputs/CSharpLayout.cs
Normal file
10
Il2CppInspector.Redux.FrontendCore/Outputs/CSharpLayout.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public enum CSharpLayout
|
||||
{
|
||||
SingleFile,
|
||||
Namespace,
|
||||
Assembly,
|
||||
Class,
|
||||
Tree
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Outputs;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public class CSharpStubOutput : IOutputFormatProvider
|
||||
{
|
||||
public static string Id => "cs";
|
||||
|
||||
private class Settings(Dictionary<string, string> settings)
|
||||
{
|
||||
public readonly CSharpLayout Layout = settings.GetAsEnumOrDefault("layout", CSharpLayout.SingleFile);
|
||||
public readonly bool FlattenHierarchy = settings.GetAsBooleanOrDefault("flattenhierarchy", false);
|
||||
public readonly TypeSortingMode SortingMode = settings.GetAsEnumOrDefault("sortingmode", TypeSortingMode.Alphabetical);
|
||||
public readonly bool SuppressMetadata = settings.GetAsBooleanOrDefault("suppressmetadata", false);
|
||||
public readonly bool MustCompile = settings.GetAsBooleanOrDefault("mustcompile", false);
|
||||
public readonly bool SeperateAssemblyAttributes = settings.GetAsBooleanOrDefault("seperateassemblyattributes", true);
|
||||
}
|
||||
|
||||
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
|
||||
{
|
||||
var settings = new Settings(settingsDict);
|
||||
|
||||
var writer = new CSharpCodeStubs(model.TypeModel)
|
||||
{
|
||||
SuppressMetadata = settings.SuppressMetadata,
|
||||
MustCompile = settings.MustCompile
|
||||
};
|
||||
|
||||
await client.ShowLogMessage("Writing C# type definitions");
|
||||
|
||||
var outputPathFile = Path.Join(outputPath, "il2cpp.cs");
|
||||
|
||||
switch (settings.Layout, settings.SortingMode)
|
||||
{
|
||||
case (CSharpLayout.SingleFile, TypeSortingMode.TypeDefinitionIndex):
|
||||
writer.WriteSingleFile(outputPathFile, info => info.Index);
|
||||
break;
|
||||
case (CSharpLayout.SingleFile, TypeSortingMode.Alphabetical):
|
||||
writer.WriteSingleFile(outputPathFile, info => info.Name);
|
||||
break;
|
||||
|
||||
case (CSharpLayout.Namespace, TypeSortingMode.TypeDefinitionIndex):
|
||||
writer.WriteFilesByNamespace(outputPath, info => info.Index, settings.FlattenHierarchy);
|
||||
break;
|
||||
case (CSharpLayout.Namespace, TypeSortingMode.Alphabetical):
|
||||
writer.WriteFilesByNamespace(outputPath, info => info.Name, settings.FlattenHierarchy);
|
||||
break;
|
||||
|
||||
case (CSharpLayout.Assembly, TypeSortingMode.TypeDefinitionIndex):
|
||||
writer.WriteFilesByAssembly(outputPath, info => info.Index, settings.SeperateAssemblyAttributes);
|
||||
break;
|
||||
case (CSharpLayout.Assembly, TypeSortingMode.Alphabetical):
|
||||
writer.WriteFilesByAssembly(outputPath, info => info.Name, settings.SeperateAssemblyAttributes);
|
||||
break;
|
||||
|
||||
case (CSharpLayout.Class, _):
|
||||
writer.WriteFilesByClass(outputPath, settings.FlattenHierarchy);
|
||||
break;
|
||||
|
||||
case (CSharpLayout.Tree, _):
|
||||
writer.WriteFilesByClassTree(outputPath, settings.SeperateAssemblyAttributes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using Il2CppInspector.Cpp;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Outputs;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public class CppScaffoldingOutput : IOutputFormatProvider
|
||||
{
|
||||
public static string Id => "cppscaffolding";
|
||||
|
||||
private class Settings(Dictionary<string, string> settings)
|
||||
{
|
||||
public readonly string UnityVersion = settings.GetValueOrDefault("unityversion", "");
|
||||
public readonly CppCompilerType Compiler = settings.GetAsEnumOrDefault("compiler", CppCompilerType.GCC);
|
||||
}
|
||||
|
||||
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
|
||||
{
|
||||
var settings = new Settings(settingsDict);
|
||||
|
||||
await client.ShowLogMessage($"Building application model for Unity {settings.UnityVersion}/{settings.Compiler}");
|
||||
model.Build(new UnityVersion(settings.UnityVersion), settings.Compiler);
|
||||
|
||||
await client.ShowLogMessage("Generating C++ scaffolding");
|
||||
var scaffolding = new CppScaffolding(model);
|
||||
|
||||
await client.ShowLogMessage("Writing C++ scaffolding");
|
||||
scaffolding.Write(outputPath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using Il2CppInspector.Cpp;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Outputs;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public class DisassemblerMetadataOutput : IOutputFormatProvider
|
||||
{
|
||||
public static string Id => "disassemblermetadata";
|
||||
|
||||
private class Settings(Dictionary<string, string> dict)
|
||||
{
|
||||
public readonly DisassemblerType Disassembler = dict.GetAsEnumOrDefault("disassembler", DisassemblerType.IDA);
|
||||
public readonly string UnityVersion = dict.GetValueOrDefault("unityversion", "");
|
||||
}
|
||||
|
||||
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
|
||||
{
|
||||
var settings = new Settings(settingsDict);
|
||||
|
||||
await client.ShowLogMessage($"Building application model for Unity {settings.UnityVersion}/{CppCompilerType.GCC}");
|
||||
model.Build(new UnityVersion(settings.UnityVersion), CppCompilerType.GCC);
|
||||
|
||||
var headerPath = Path.Join(outputPath, "il2cpp.h");
|
||||
{
|
||||
await client.ShowLogMessage("Generating C++ types");
|
||||
var cppScaffolding = new CppScaffolding(model, useBetterArraySize: true);
|
||||
|
||||
await client.ShowLogMessage("Writing C++ types");
|
||||
cppScaffolding.WriteTypes(headerPath);
|
||||
}
|
||||
|
||||
var metadataPath = Path.Join(outputPath, "il2cpp.json");
|
||||
{
|
||||
await client.ShowLogMessage("Generating disassembler metadata");
|
||||
var jsonMetadata = new JSONMetadata(model);
|
||||
|
||||
await client.ShowLogMessage("Writing disassembler metadata");
|
||||
jsonMetadata.Write(metadataPath);
|
||||
}
|
||||
|
||||
if (settings.Disassembler != DisassemblerType.None)
|
||||
{
|
||||
var scriptPath = Path.Join(outputPath, "il2cpp.py");
|
||||
await client.ShowLogMessage($"Generating python script for {settings.Disassembler}");
|
||||
var script = new PythonScript(model);
|
||||
|
||||
await client.ShowLogMessage($"Writing python script for {settings.Disassembler}");
|
||||
script.WriteScriptToFile(scriptPath, settings.Disassembler.ToString(), headerPath, metadataPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public enum DisassemblerType
|
||||
{
|
||||
IDA,
|
||||
Ghidra,
|
||||
BinaryNinja,
|
||||
None
|
||||
}
|
||||
27
Il2CppInspector.Redux.FrontendCore/Outputs/DummyDllOutput.cs
Normal file
27
Il2CppInspector.Redux.FrontendCore/Outputs/DummyDllOutput.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Outputs;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public class DummyDllOutput : IOutputFormatProvider
|
||||
{
|
||||
public static string Id => "dummydlls";
|
||||
|
||||
private class Settings(Dictionary<string, string> dict)
|
||||
{
|
||||
public readonly bool SuppressMetadata = dict.GetAsBooleanOrDefault("suppressmetadata", false);
|
||||
}
|
||||
|
||||
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
|
||||
{
|
||||
var outputSettings = new Settings(settingsDict);
|
||||
|
||||
await client.ShowLogMessage("Generating .NET dummy assemblies");
|
||||
var shims = new AssemblyShims(model.TypeModel)
|
||||
{
|
||||
SuppressMetadata = outputSettings.SuppressMetadata
|
||||
};
|
||||
|
||||
shims.Write(outputPath, client.EventHandler);
|
||||
}
|
||||
}
|
||||
14
Il2CppInspector.Redux.FrontendCore/Outputs/IOutputFormat.cs
Normal file
14
Il2CppInspector.Redux.FrontendCore/Outputs/IOutputFormat.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using Il2CppInspector.Model;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public interface IOutputFormat
|
||||
{
|
||||
public Task Export(AppModel model, UiClient client, string outputPath,
|
||||
Dictionary<string, string> settingsDict);
|
||||
}
|
||||
|
||||
public interface IOutputFormatProvider : IOutputFormat
|
||||
{
|
||||
public static abstract string Id { get; }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public static class OutputFormatRegistry
|
||||
{
|
||||
public static IEnumerable<string> AvailableOutputFormats => OutputFormats.Keys;
|
||||
|
||||
private static readonly Dictionary<string, IOutputFormat> OutputFormats = [];
|
||||
|
||||
public static void RegisterOutputFormat<T>() where T : IOutputFormatProvider, new()
|
||||
{
|
||||
if (OutputFormats.ContainsKey(T.Id))
|
||||
throw new InvalidOperationException("An output format with this id was already registered.");
|
||||
|
||||
OutputFormats[T.Id] = new T();
|
||||
}
|
||||
|
||||
public static IOutputFormat GetOutputFormat(string id)
|
||||
{
|
||||
if (!OutputFormats.TryGetValue(id, out var format))
|
||||
throw new ArgumentException($"Failed to find output format for id {id}", nameof(id));
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
private static void RegisterBuiltinOutputFormats()
|
||||
{
|
||||
RegisterOutputFormat<CSharpStubOutput>();
|
||||
RegisterOutputFormat<VsSolutionOutput>();
|
||||
RegisterOutputFormat<DummyDllOutput>();
|
||||
RegisterOutputFormat<DisassemblerMetadataOutput>();
|
||||
RegisterOutputFormat<CppScaffoldingOutput>();
|
||||
}
|
||||
|
||||
static OutputFormatRegistry()
|
||||
{
|
||||
RegisterBuiltinOutputFormats();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public enum TypeSortingMode
|
||||
{
|
||||
Alphabetical,
|
||||
TypeDefinitionIndex
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Outputs;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
|
||||
public class VsSolutionOutput : IOutputFormatProvider
|
||||
{
|
||||
public static string Id => "vssolution";
|
||||
|
||||
private class Settings(Dictionary<string, string> settings)
|
||||
{
|
||||
public readonly string UnityPath = settings.GetValueOrDefault("unitypath", "");
|
||||
public readonly string UnityAssembliesPath = settings.GetValueOrDefault("unityassembliespath", "");
|
||||
}
|
||||
|
||||
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
|
||||
{
|
||||
var settings = new Settings(settingsDict);
|
||||
|
||||
var writer = new CSharpCodeStubs(model.TypeModel)
|
||||
{
|
||||
MustCompile = true,
|
||||
SuppressMetadata = true
|
||||
};
|
||||
|
||||
await client.ShowLogMessage("Writing Visual Studio solution");
|
||||
writer.WriteSolution(outputPath, settings.UnityPath, settings.UnityAssembliesPath);
|
||||
}
|
||||
}
|
||||
54
Il2CppInspector.Redux.FrontendCore/PathHeuristics.cs
Normal file
54
Il2CppInspector.Redux.FrontendCore/PathHeuristics.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public static class PathHeuristics
|
||||
{
|
||||
private static readonly string[] AllowedMetadataExtensionComponents =
|
||||
[
|
||||
"dat", "dec"
|
||||
];
|
||||
|
||||
private static readonly string[] AllowedMetadataNameComponents =
|
||||
[
|
||||
"metadata"
|
||||
];
|
||||
|
||||
private static readonly string[] AllowedBinaryPathComponents =
|
||||
[
|
||||
"GameAssembly",
|
||||
"il2cpp",
|
||||
"UnityFramework"
|
||||
];
|
||||
|
||||
private static readonly string[] AllowedBinaryExtensionComponents =
|
||||
[
|
||||
"dll", "so", "exe", "bin", "prx", "sprx", "dylib"
|
||||
];
|
||||
|
||||
public static bool IsMetadataPath(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
if (AllowedMetadataExtensionComponents.Any(extension.Contains))
|
||||
return true;
|
||||
|
||||
var filename = Path.GetFileNameWithoutExtension(path);
|
||||
if (AllowedMetadataNameComponents.Any(filename.Contains))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsBinaryPath(string path)
|
||||
{
|
||||
var extension = Path.GetExtension(path);
|
||||
|
||||
// empty to allow macho binaries which do not have an extension
|
||||
if (extension == "" || AllowedBinaryExtensionComponents.Any(extension.Contains))
|
||||
return true;
|
||||
|
||||
var filename = Path.GetFileNameWithoutExtension(path);
|
||||
if (AllowedBinaryPathComponents.Any(filename.Contains))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Il2CppInspector.Redux.FrontendCore": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": false,
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"applicationUrl": "https://localhost:43298;http://localhost:43299"
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Il2CppInspector.Redux.FrontendCore/UiClient.cs
Normal file
44
Il2CppInspector.Redux.FrontendCore/UiClient.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public class UiClient(ISingleClientProxy client)
|
||||
{
|
||||
private EventHandler<string>? _handler;
|
||||
|
||||
public EventHandler<string> EventHandler
|
||||
{
|
||||
get
|
||||
{
|
||||
_handler ??= (_, status) =>
|
||||
{
|
||||
#pragma warning disable CS4014
|
||||
ShowLogMessage(status);
|
||||
#pragma warning restore CS4014
|
||||
};
|
||||
|
||||
return _handler;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ShowLogMessage(string message, CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(ShowLogMessage), message, cancellationToken);
|
||||
|
||||
public async Task BeginLoading(CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(BeginLoading), cancellationToken);
|
||||
|
||||
public async Task FinishLoading(CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(FinishLoading), cancellationToken);
|
||||
|
||||
public async Task ShowInfoToast(string message, CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(ShowInfoToast), message, cancellationToken);
|
||||
|
||||
public async Task ShowSuccessToast(string message, CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(ShowSuccessToast), message, cancellationToken);
|
||||
|
||||
public async Task ShowErrorToast(string message, CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(ShowErrorToast), message, cancellationToken);
|
||||
|
||||
public async Task OnImportCompleted(CancellationToken cancellationToken = default)
|
||||
=> await client.SendAsync(nameof(OnImportCompleted), cancellationToken);
|
||||
}
|
||||
251
Il2CppInspector.Redux.FrontendCore/UiContext.cs
Normal file
251
Il2CppInspector.Redux.FrontendCore/UiContext.cs
Normal file
@@ -0,0 +1,251 @@
|
||||
using System.Diagnostics;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
using Il2CppInspector.Reflection;
|
||||
using Inspector = Il2CppInspector.Il2CppInspector;
|
||||
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public class UiContext
|
||||
{
|
||||
private const string BugReportSuffix =
|
||||
"""
|
||||
|
||||
|
||||
If you believe this is a bug in Il2CppInspectorRedux, please use the CLI version to generate the complete output and paste it when filing a bug report.
|
||||
Do not send a screenshot of this error!
|
||||
""";
|
||||
|
||||
private Metadata? _metadata;
|
||||
private IFileFormatStream? _binary;
|
||||
private readonly List<AppModel> _appModels = [];
|
||||
private readonly List<UnityHeaders> _potentialUnityVersions = [];
|
||||
|
||||
private readonly LoadOptions _loadOptions = new();
|
||||
|
||||
private readonly List<(string FormatId, string OutputDirectory, Dictionary<string, string> Settings)> _queuedExports = [];
|
||||
|
||||
private async Task<bool> TryLoadMetadataFromStream(UiClient client, MemoryStream stream)
|
||||
{
|
||||
try
|
||||
{
|
||||
_metadata = Metadata.FromStream(stream, client.EventHandler);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await client.ShowErrorToast($"{e.Message}{BugReportSuffix}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> TryLoadBinaryFromStream(UiClient client, MemoryStream stream)
|
||||
{
|
||||
await client.ShowLogMessage("Processing binary");
|
||||
|
||||
try
|
||||
{
|
||||
var file = FileFormatStream.Load(stream, _loadOptions, client.EventHandler)
|
||||
?? throw new InvalidOperationException("Failed to determine binary file format.");
|
||||
|
||||
if (file.NumImages == 0)
|
||||
throw new InvalidOperationException("Failed to find any binary images in the file");
|
||||
|
||||
_binary = file;
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await client.ShowErrorToast($"{e.Message}{BugReportSuffix}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> TryInitializeInspector(UiClient client)
|
||||
{
|
||||
Debug.Assert(_binary != null);
|
||||
Debug.Assert(_metadata != null);
|
||||
|
||||
_appModels.Clear();
|
||||
|
||||
var inspectors = Inspector.LoadFromStream(_binary, _metadata, client.EventHandler);
|
||||
|
||||
if (inspectors.Count == 0)
|
||||
{
|
||||
await client.ShowErrorToast(
|
||||
"""
|
||||
Failed to auto-detect any IL2CPP binary images in the provided files.
|
||||
This may mean the binary file is packed, encrypted or obfuscated, that the file
|
||||
is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data.
|
||||
Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!
|
||||
""");
|
||||
|
||||
_binary = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var inspector in inspectors)
|
||||
{
|
||||
await client.ShowLogMessage(
|
||||
$"Building .NET type model for {inspector.BinaryImage.Format}/{inspector.BinaryImage.Arch} image");
|
||||
|
||||
try
|
||||
{
|
||||
var typeModel = new TypeModel(inspector);
|
||||
|
||||
// Just create the app model, do not initialize it - this is done lazily depending on the exports
|
||||
_appModels.Add(new AppModel(typeModel, makeDefaultBuild: false));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
await client.ShowErrorToast($"Failed to build type model: {e.Message}{BugReportSuffix}");
|
||||
|
||||
// Clear out failed metadata and binary so subsequent loads do not use any stale data.
|
||||
_metadata = null;
|
||||
_binary = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_potentialUnityVersions.Clear();
|
||||
_potentialUnityVersions.AddRange(UnityHeaders.GuessHeadersForBinary(_appModels[0].Package.Binary));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task Initialize(UiClient client, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await client.ShowSuccessToast("SignalR initialized!", cancellationToken);
|
||||
}
|
||||
|
||||
public async Task LoadInputFiles(UiClient client, List<string> inputFiles,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await using (await LoadingSession.Start(client))
|
||||
{
|
||||
var streams = Inspector.GetStreamsFromPackage(inputFiles);
|
||||
if (streams != null)
|
||||
{
|
||||
// The input files contained a package that provides the metadata and binary.
|
||||
// Use these instead of parsing all files individually.
|
||||
if (!await TryLoadMetadataFromStream(client, streams.Value.Metadata))
|
||||
return;
|
||||
|
||||
if (!await TryLoadBinaryFromStream(client, streams.Value.Binary))
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var inputFile in inputFiles)
|
||||
{
|
||||
if (_metadata != null && _binary != null)
|
||||
break;
|
||||
|
||||
await client.ShowLogMessage($"Processing {inputFile}", cancellationToken);
|
||||
|
||||
var data = await File.ReadAllBytesAsync(inputFile, cancellationToken);
|
||||
var stream = new MemoryStream(data);
|
||||
|
||||
if ( _metadata == null && PathHeuristics.IsMetadataPath(inputFile))
|
||||
{
|
||||
if (await TryLoadMetadataFromStream(client, stream))
|
||||
{
|
||||
await client.ShowSuccessToast($"Loaded metadata (v{_metadata!.Version}) from {inputFile}", cancellationToken);
|
||||
}
|
||||
}
|
||||
else if (_binary == null && PathHeuristics.IsBinaryPath(inputFile))
|
||||
{
|
||||
stream.Position = 0;
|
||||
_loadOptions.BinaryFilePath = inputFile;
|
||||
|
||||
if (await TryLoadBinaryFromStream(client, stream))
|
||||
{
|
||||
await client.ShowSuccessToast($"Loaded binary from {inputFile}", cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_metadata != null && _binary != null)
|
||||
{
|
||||
if (await TryInitializeInspector(client))
|
||||
{
|
||||
await client.ShowSuccessToast($"Successfully loaded IL2CPP (v{_appModels[0].Package.Version}) data!", cancellationToken);
|
||||
await client.OnImportCompleted(cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task QueueExport(UiClient client, string exportFormatId, string outputDirectory,
|
||||
Dictionary<string, string> settings, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_queuedExports.Add((exportFormatId, outputDirectory, settings));
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task StartExport(UiClient client, CancellationToken cancellationToken = default)
|
||||
{
|
||||
// todo: support different app model selection (when loading packages)
|
||||
Debug.Assert(_appModels.Count > 0);
|
||||
|
||||
await using (await LoadingSession.Start(client))
|
||||
{
|
||||
var model = _appModels[0];
|
||||
|
||||
foreach (var (formatId, outputDirectory, settings) in _queuedExports)
|
||||
{
|
||||
try
|
||||
{
|
||||
var outputFormat = OutputFormatRegistry.GetOutputFormat(formatId);
|
||||
await outputFormat.Export(model, client, outputDirectory, settings);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await client.ShowErrorToast($"Export for format {formatId} failed: {ex}",
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
_queuedExports.Clear();
|
||||
}
|
||||
|
||||
await client.ShowSuccessToast("Export finished", cancellationToken);
|
||||
}
|
||||
|
||||
public Task<List<string>> GetPotentialUnityVersions()
|
||||
{
|
||||
return Task.FromResult(_potentialUnityVersions.Select(x => x.VersionRange.Min.ToString()).ToList());
|
||||
}
|
||||
|
||||
public async Task ExportIl2CppFiles(UiClient client, string outputDirectory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Debug.Assert(_appModels.Count > 0);
|
||||
var pkg = _appModels[0].Package;
|
||||
|
||||
await using (await LoadingSession.Start(client))
|
||||
{
|
||||
await Task.Run(async () =>
|
||||
{
|
||||
Directory.CreateDirectory(outputDirectory);
|
||||
|
||||
await client.ShowLogMessage("Extracting IL2CPP binary", cancellationToken);
|
||||
pkg.SaveBinaryToFile(Path.Join(outputDirectory, pkg.BinaryImage.DefaultFilename));
|
||||
|
||||
await client.ShowLogMessage("Extracting IL2CPP metadata", cancellationToken);
|
||||
pkg.SaveMetadataToFile(Path.Join(outputDirectory, "global-metadata.dat"));
|
||||
|
||||
await client.ShowSuccessToast("Successfully extracted IL2CPP files.", cancellationToken);
|
||||
}, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<string> GetInspectorVersion()
|
||||
{
|
||||
return Task.FromResult(typeof(UiContext).Assembly.GetAssemblyVersion() ?? "<unknown>");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user