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
114
.github/workflows/build.yml
vendored
@@ -3,7 +3,113 @@ name: Il2CppInspectorRedux Build
|
||||
on: [push, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
build-gui:
|
||||
build-redux-gui: # this already includes stuff only relevant for linux/macos for when the gui is released on those platforms
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
run_install: false
|
||||
|
||||
- name: Setup Node.JS
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||
|
||||
- name: Setup Tauri dependencies (ubuntu only)
|
||||
if: matrix.platform == 'ubuntu-22.04'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
working-directory: ./Il2CppInspector.Redux.GUI.UI
|
||||
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-redux-gui-${{ hashFiles('**/packages.lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-redux-gui-
|
||||
|
||||
- name: Restore NuGet packages
|
||||
run: dotnet restore -r win-x64 ./Il2CppInspector.Redux.GUI
|
||||
|
||||
# note: we embed the exe directly into the c# component, and it it is built alongside it
|
||||
# in another msbuild target.
|
||||
- name: Build GUI
|
||||
run: dotnet publish ./Il2CppInspector.Redux.GUI/Il2CppInspector.Redux.GUI.csproj -r win-x64 --no-self-contained
|
||||
|
||||
- name: Copy components to output directory
|
||||
run: |
|
||||
mkdir ./build_output
|
||||
cp ./Il2CppInspector.Redux.GUI/bin/Release/net9.0/win-x64/publish/Il2CppInspector.Redux.GUI.exe ./build_output/
|
||||
|
||||
- name: Upload GUI Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Il2CppInspectorRedux.GUI
|
||||
path: build_output
|
||||
|
||||
build-redux-cli:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
dotnet-version: [ '9.0.x' ]
|
||||
rid: ['win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup .NET SDK
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
|
||||
|
||||
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
|
||||
uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI
|
||||
|
||||
- name: Build & Publish
|
||||
run: dotnet publish -c Release --no-self-contained --no-restore -o ./${{ matrix.rid }} -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI/Il2CppInspector.Redux.CLI.csproj
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
|
||||
path: ./${{ matrix.rid }}
|
||||
|
||||
build-old-gui:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
@@ -32,10 +138,10 @@ jobs:
|
||||
- name: Upload GUI Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Il2CppInspectorRedux.GUI
|
||||
name: Il2CppInspectorRedux.Old.GUI
|
||||
path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish
|
||||
|
||||
build-cli:
|
||||
build-old-cli:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -73,5 +179,5 @@ jobs:
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
|
||||
name: Il2CppInspectorRedux.Old.CLI-${{ matrix.rid }}
|
||||
path: ./${{ matrix.rid }}
|
||||
|
||||
3
.gitignore
vendored
@@ -268,3 +268,6 @@ paket-files/
|
||||
/Il2CppInspector.sln.DotSettings
|
||||
/Il2CppTests/samples
|
||||
/Il2CppTests/TestCpp
|
||||
|
||||
# for dotnet-tools.json
|
||||
.config
|
||||
@@ -5,19 +5,17 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using Il2CppInspector.Model;
|
||||
using Il2CppInspector.Reflection;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
namespace Il2CppInspector.Cpp;
|
||||
|
||||
// Class for generating C header declarations from Reflection objects (TypeInfo, etc.)
|
||||
public class CppDeclarationGenerator
|
||||
{
|
||||
// Class for generating C header declarations from Reflection objects (TypeInfo, etc.)
|
||||
public class CppDeclarationGenerator
|
||||
{
|
||||
private readonly AppModel appModel;
|
||||
|
||||
private TypeModel model => appModel.TypeModel;
|
||||
@@ -33,7 +31,7 @@ namespace Il2CppInspector.Cpp
|
||||
// Different C++ compilers lay out C++ class structures differently,
|
||||
// meaning that the compiler must be known in order to generate class type structures
|
||||
// with the correct layout.
|
||||
public CppCompilerType InheritanceStyle;
|
||||
public readonly CppCompilerType InheritanceStyle;
|
||||
|
||||
public CppDeclarationGenerator(AppModel appModel) {
|
||||
this.appModel = appModel;
|
||||
@@ -46,7 +44,8 @@ namespace Il2CppInspector.Cpp
|
||||
}
|
||||
|
||||
// C type declaration used to name variables of the given C# type
|
||||
private static Dictionary<string, string> primitiveTypeMap = new Dictionary<string, string> {
|
||||
private static readonly Dictionary<string, string> primitiveTypeMap = new()
|
||||
{
|
||||
["Boolean"] = "bool",
|
||||
["Byte"] = "uint8_t",
|
||||
["SByte"] = "int8_t",
|
||||
@@ -68,24 +67,28 @@ namespace Il2CppInspector.Cpp
|
||||
if (ti.IsByRef || ti.IsPointer) {
|
||||
return AsCType(ti.ElementType).AsPointer(WordSize);
|
||||
}
|
||||
|
||||
if (ti.IsValueType) {
|
||||
if (ti.IsPrimitive && primitiveTypeMap.ContainsKey(ti.Name)) {
|
||||
return types.GetType(primitiveTypeMap[ti.Name]);
|
||||
if (ti.IsPrimitive && primitiveTypeMap.TryGetValue(ti.Name, out var value)) {
|
||||
return types.GetType(value);
|
||||
}
|
||||
|
||||
return types.GetType(TypeNamer.GetName(ti));
|
||||
}
|
||||
|
||||
if (ti.IsEnum) {
|
||||
return types.GetType(TypeNamer.GetName(ti));
|
||||
}
|
||||
|
||||
return types.GetType(TypeNamer.GetName(ti) + " *");
|
||||
}
|
||||
|
||||
// Resets the cache of visited types and pending types to output, but preserve any names we have already generated
|
||||
public void Reset() {
|
||||
VisitedFieldStructs.Clear();
|
||||
VisitedTypes.Clear();
|
||||
TodoFieldStructs.Clear();
|
||||
TodoTypeStructs.Clear();
|
||||
_visitedFieldStructs.Clear();
|
||||
_visitedTypes.Clear();
|
||||
_todoFieldStructs.Clear();
|
||||
_todoTypeStructs.Clear();
|
||||
}
|
||||
|
||||
#region Field Struct Generation
|
||||
@@ -96,37 +99,123 @@ namespace Il2CppInspector.Cpp
|
||||
*/
|
||||
|
||||
// A cache of field structures that have already been generated, to eliminate duplicate definitions
|
||||
private readonly HashSet<TypeInfo> VisitedFieldStructs = new HashSet<TypeInfo>();
|
||||
private readonly HashSet<TypeInfo> _visitedFieldStructs = [];
|
||||
|
||||
// A queue of field structures that need to be generated.
|
||||
private readonly List<TypeInfo> TodoFieldStructs = new List<TypeInfo>();
|
||||
private readonly List<TypeInfo> _todoFieldStructs = [];
|
||||
|
||||
// Walk over dependencies of the given type, to figure out what field structures it depends on
|
||||
private void VisitFieldStructs(TypeInfo ti) {
|
||||
if (VisitedFieldStructs.Contains(ti))
|
||||
return;
|
||||
if (ti.IsByRef || ti.ContainsGenericParameters)
|
||||
return;
|
||||
VisitedFieldStructs.Add(ti);
|
||||
private readonly HashSet<TypeInfo> _requiredForwardDefinitionsForFields = [];
|
||||
|
||||
if (ti.BaseType != null)
|
||||
VisitFieldStructs(ti.BaseType);
|
||||
private readonly HashSet<TypeInfo> _currentVisitedFieldStructs = [];
|
||||
private readonly HashSet<TypeInfo> _currentTodoFieldStructs = [];
|
||||
private readonly HashSet<TypeInfo> _currentRequiredForwardDefinitions = [];
|
||||
private readonly HashSet<TypeInfo> _currentlyVisitingFieldStructs = [];
|
||||
|
||||
if (ti.IsArray)
|
||||
VisitFieldStructs(ti.ElementType);
|
||||
|
||||
if (ti.IsEnum)
|
||||
VisitFieldStructs(ti.GetEnumUnderlyingType());
|
||||
|
||||
foreach (var fi in ti.DeclaredFields.Where(fi => !fi.IsStatic && !fi.IsLiteral))
|
||||
private class CircularReferenceException(TypeInfo circularType, TypeInfo parentType) : Exception("Circular reference detected")
|
||||
{
|
||||
if (fi.FieldType.IsEnum || fi.FieldType.IsValueType)
|
||||
VisitFieldStructs(fi.FieldType);
|
||||
else if (fi.FieldType.HasElementType)
|
||||
VisitFieldStructs(fi.FieldType.ElementType);
|
||||
public TypeInfo CircularReferencedType { get; } = circularType;
|
||||
public TypeInfo ParentType { get; } = parentType;
|
||||
}
|
||||
|
||||
TodoFieldStructs.Add(ti);
|
||||
// Walk over dependencies of the given type, to figure out what field structures it depends on
|
||||
private void VisitFieldStructsInner(TypeInfo ti)
|
||||
{
|
||||
if (_visitedFieldStructs.Contains(ti) || _currentVisitedFieldStructs.Contains(ti))
|
||||
return;
|
||||
|
||||
if (ti.IsByRef || ti.ContainsGenericParameters)
|
||||
return;
|
||||
|
||||
_currentVisitedFieldStructs.Add(ti);
|
||||
_currentlyVisitingFieldStructs.Add(ti);
|
||||
|
||||
if (ti.BaseType != null)
|
||||
VisitFieldStructsInner(ti.BaseType);
|
||||
|
||||
if (ti.IsArray)
|
||||
VisitFieldStructsInner(ti.ElementType);
|
||||
|
||||
if (ti.IsEnum)
|
||||
VisitFieldStructsInner(ti.GetEnumUnderlyingType());
|
||||
|
||||
foreach (var fi in ti.DeclaredFields.Where(fi => !fi.IsStatic && !fi.IsLiteral))
|
||||
ProcessTypeField(fi);
|
||||
|
||||
_currentTodoFieldStructs.Add(ti);
|
||||
_currentlyVisitingFieldStructs.Remove(ti);
|
||||
|
||||
return;
|
||||
|
||||
void ProcessTypeField(FieldInfo fi)
|
||||
{
|
||||
if (fi.FieldType.IsEnum || fi.FieldType.IsValueType)
|
||||
{
|
||||
VisitFieldStructsInner(fi.FieldType);
|
||||
}
|
||||
else if (fi.FieldType.HasElementType)
|
||||
{
|
||||
var elementType = fi.FieldType.ElementType;
|
||||
if (!fi.FieldType.IsPointer || !_currentRequiredForwardDefinitions.Contains(elementType))
|
||||
{
|
||||
VisitFieldStructsInner(elementType);
|
||||
|
||||
if (elementType.IsValueType
|
||||
&& elementType != ti
|
||||
&& _currentlyVisitingFieldStructs.Contains(elementType)
|
||||
&& !_currentRequiredForwardDefinitions.Contains(elementType))
|
||||
{
|
||||
// this is now an issue: there is a loop, and we need to resolve it
|
||||
// if the field type is a pointer, we can make a forward declaration and be done with it
|
||||
// otherwise, we cannot generate these types
|
||||
if (!fi.FieldType.IsPointer)
|
||||
Debugger.Break();
|
||||
|
||||
throw new CircularReferenceException(elementType, ti);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearCurrentFieldStructVisitState()
|
||||
{
|
||||
_currentTodoFieldStructs.Clear();
|
||||
_currentVisitedFieldStructs.Clear();
|
||||
_currentlyVisitingFieldStructs.Clear();
|
||||
}
|
||||
|
||||
private void VisitFieldStructs(TypeInfo ti)
|
||||
{
|
||||
ClearCurrentFieldStructVisitState();
|
||||
|
||||
var requiredTypesToVisit = new Stack<TypeInfo>([ti]);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var typeToVisit in requiredTypesToVisit)
|
||||
VisitFieldStructsInner(typeToVisit);
|
||||
}
|
||||
catch (CircularReferenceException ex)
|
||||
{
|
||||
ClearCurrentFieldStructVisitState();
|
||||
|
||||
_currentRequiredForwardDefinitions.Add(ex.CircularReferencedType);
|
||||
requiredTypesToVisit.Push(ex.ParentType);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
_todoFieldStructs.AddRange(_currentTodoFieldStructs);
|
||||
foreach (var visitedType in _currentVisitedFieldStructs)
|
||||
_visitedFieldStructs.Add(visitedType);
|
||||
|
||||
foreach (var requiredType in _currentRequiredForwardDefinitions)
|
||||
_requiredForwardDefinitionsForFields.Add(requiredType);
|
||||
}
|
||||
|
||||
// Generate the fields for the base class of all objects (Il2CppObject)
|
||||
@@ -228,6 +317,14 @@ namespace Il2CppInspector.Cpp
|
||||
ns.ReserveName("_");
|
||||
fieldType = types.Struct(name + "__Fields");
|
||||
var baseFieldType = types[TypeNamer.GetName(ti.BaseType) + "__Fields"];
|
||||
|
||||
if (baseFieldType == null)
|
||||
{
|
||||
// if we end up here, there is a loop in the type generation.
|
||||
// this is not currently supported, so we throw an exception.
|
||||
throw new InvalidOperationException($"Failed to generate type for {ti}");
|
||||
}
|
||||
|
||||
fieldType.AddField("_", baseFieldType);
|
||||
GenerateFieldList(fieldType, ns, ti);
|
||||
}
|
||||
@@ -253,8 +350,8 @@ namespace Il2CppInspector.Cpp
|
||||
|
||||
// "Flush" the list of visited types, generating C structures for each one
|
||||
private List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, CppComplexType fieldsType)> GenerateVisitedFieldStructs() {
|
||||
var structs = new List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, CppComplexType fieldsType)>(TodoTypeStructs.Count);
|
||||
foreach (var ti in TodoFieldStructs) {
|
||||
var structs = new List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, CppComplexType fieldsType)>(_todoTypeStructs.Count);
|
||||
foreach (var ti in _todoFieldStructs) {
|
||||
if (ti.IsEnum || ti.IsValueType) {
|
||||
var (valueType, boxedType) = GenerateValueFieldStruct(ti);
|
||||
structs.Add((ti, valueType, boxedType, null));
|
||||
@@ -264,7 +361,7 @@ namespace Il2CppInspector.Cpp
|
||||
structs.Add((ti, null, objectOrArrayType, fieldsType));
|
||||
}
|
||||
}
|
||||
TodoFieldStructs.Clear();
|
||||
_todoFieldStructs.Clear();
|
||||
return structs;
|
||||
}
|
||||
#endregion
|
||||
@@ -272,7 +369,7 @@ namespace Il2CppInspector.Cpp
|
||||
#region Class Struct Generation
|
||||
|
||||
// Concrete implementations for abstract classes, for use in looking up VTable signatures and names
|
||||
private readonly Dictionary<TypeInfo, TypeInfo> ConcreteImplementations = new Dictionary<TypeInfo, TypeInfo>();
|
||||
private readonly Dictionary<TypeInfo, TypeInfo> _concreteImplementations = new();
|
||||
/// <summary>
|
||||
/// VTables for abstract types have "null" in place of abstract functions.
|
||||
/// This function searches for concrete implementations so that we can properly
|
||||
@@ -284,8 +381,9 @@ namespace Il2CppInspector.Cpp
|
||||
continue;
|
||||
var baseType = ti.BaseType;
|
||||
while (baseType != null) {
|
||||
if (baseType.IsAbstract && !ConcreteImplementations.ContainsKey(baseType))
|
||||
ConcreteImplementations[baseType] = ti;
|
||||
if (baseType.IsAbstract)
|
||||
_concreteImplementations.TryAdd(baseType, ti);
|
||||
|
||||
baseType = baseType.BaseType;
|
||||
}
|
||||
}
|
||||
@@ -307,37 +405,42 @@ namespace Il2CppInspector.Cpp
|
||||
* care which concrete implementation we put in this table! The name
|
||||
* and signature will always match that of the abstract type.
|
||||
*/
|
||||
if (ti.IsAbstract && ConcreteImplementations.ContainsKey(ti)) {
|
||||
if (ti.IsAbstract && _concreteImplementations.TryGetValue(ti, out var implementation)) {
|
||||
var impl = implementation.GetVTable();
|
||||
|
||||
res = (MethodBase[])res.Clone();
|
||||
MethodBase[] impl = ConcreteImplementations[ti].GetVTable();
|
||||
for (int i = 0; i < res.Length; i++) {
|
||||
if (res[i] == null)
|
||||
res[i] = impl[i];
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
res[i] ??= impl[i];
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private readonly HashSet<TypeInfo> VisitedTypes = new HashSet<TypeInfo>();
|
||||
private readonly List<TypeInfo> TodoTypeStructs = new List<TypeInfo>();
|
||||
private readonly HashSet<TypeInfo> _visitedTypes = [];
|
||||
private readonly List<TypeInfo> _todoTypeStructs = [];
|
||||
|
||||
/// <summary>
|
||||
/// Include the given type into this generator. This will add the given type and all types it depends on.
|
||||
/// Call GenerateRemainingTypeDeclarations to produce the actual type declarations afterwards.
|
||||
/// </summary>
|
||||
/// <param name="ti"></param>
|
||||
public void IncludeType(TypeInfo ti) {
|
||||
if (VisitedTypes.Contains(ti))
|
||||
public void IncludeType(TypeInfo ti)
|
||||
{
|
||||
if (_visitedTypes.Contains(ti))
|
||||
return;
|
||||
|
||||
if (ti.ContainsGenericParameters)
|
||||
return;
|
||||
VisitedTypes.Add(ti);
|
||||
|
||||
if (ti.IsArray) {
|
||||
_visitedTypes.Add(ti);
|
||||
|
||||
if (ti.IsArray || ti.HasElementType)
|
||||
{
|
||||
IncludeType(ti.ElementType);
|
||||
} else if (ti.HasElementType) {
|
||||
IncludeType(ti.ElementType);
|
||||
} else if (ti.IsEnum) {
|
||||
}
|
||||
else if (ti.IsEnum)
|
||||
{
|
||||
IncludeType(ti.GetEnumUnderlyingType());
|
||||
}
|
||||
|
||||
@@ -354,10 +457,12 @@ namespace Il2CppInspector.Cpp
|
||||
IncludeType(fi.FieldType);
|
||||
|
||||
foreach (var mi in GetFilledVTable(ti))
|
||||
if (mi != null && !mi.ContainsGenericParameters)
|
||||
{
|
||||
if (mi is { ContainsGenericParameters: false })
|
||||
IncludeMethod(mi);
|
||||
}
|
||||
|
||||
TodoTypeStructs.Add(ti);
|
||||
_todoTypeStructs.Add(ti);
|
||||
}
|
||||
|
||||
// Generate the C structure for virtual function calls in a given type (the VTable)
|
||||
@@ -437,17 +542,38 @@ namespace Il2CppInspector.Cpp
|
||||
/// <returns>A string containing C type declarations</returns>
|
||||
public List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, CppComplexType fieldsType,
|
||||
CppComplexType vtableType, CppComplexType staticsType)> GenerateRemainingTypeDeclarations() {
|
||||
try
|
||||
{
|
||||
var decl = GenerateVisitedFieldStructs().Select(s =>
|
||||
(s.ilType, s.valueType, s.referenceType, s.fieldsType, (CppComplexType) null, (CppComplexType) null)).ToList();
|
||||
(s.ilType, s.valueType, s.referenceType, s.fieldsType, (CppComplexType)null,
|
||||
(CppComplexType)null))
|
||||
.ToList();
|
||||
|
||||
foreach (var ti in TodoTypeStructs) {
|
||||
foreach (var ti in _todoTypeStructs)
|
||||
{
|
||||
var (cls, statics, vtable) = GenerateTypeStruct(ti);
|
||||
decl.Add((ti, null, cls, null, vtable, statics));
|
||||
}
|
||||
TodoTypeStructs.Clear();
|
||||
|
||||
return decl;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_todoTypeStructs.Clear();
|
||||
_todoFieldStructs.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public List<CppType> GenerateRequiredForwardDefinitions()
|
||||
=> _requiredForwardDefinitionsForFields
|
||||
.Select(x => new CppForwardDefinitionType(TypeNamer.GetName(x)))
|
||||
.Cast<CppType>()
|
||||
.ToList();
|
||||
|
||||
#endregion
|
||||
|
||||
#region Method Generation
|
||||
@@ -613,5 +739,4 @@ namespace Il2CppInspector.Cpp
|
||||
public CppNamespace GlobalsNamespace { get; private set; }
|
||||
public CppNamespace.Namer<MethodBase> GlobalNamer { get; private set; }
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
// A field in a C++ type
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
/// <summary>
|
||||
@@ -65,9 +62,9 @@ namespace Il2CppInspector.Cpp
|
||||
// Uniquely name an object within the parent namespace
|
||||
public string GetName(T t) {
|
||||
// If we've named this particular object before, just return that name
|
||||
string name;
|
||||
if (names.TryGetValue(t, out name))
|
||||
if (names.TryGetValue(t, out var name))
|
||||
return name;
|
||||
|
||||
// Obtain the mangled name for the object
|
||||
name = keyFunc(t);
|
||||
// Check if the mangled name has been given to another object - if it has,
|
||||
|
||||
@@ -456,4 +456,14 @@ namespace Il2CppInspector.Cpp
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class CppForwardDefinitionType : CppType
|
||||
{
|
||||
public CppForwardDefinitionType(string name) : base(name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override string ToString(string format = "") => $"struct {Name};";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
@@ -23,7 +19,7 @@ namespace Il2CppInspector.Cpp
|
||||
public Dictionary<string, CppType> Types { get; }
|
||||
|
||||
// All of the literal typedef aliases
|
||||
public Dictionary<string, CppType> TypedefAliases { get; } = new Dictionary<string, CppType>();
|
||||
public Dictionary<string, CppType> TypedefAliases { get; } = [];
|
||||
|
||||
public CppType this[string s] => Types.ContainsKey(s)? Types[s] :
|
||||
TypedefAliases.ContainsKey(s)? TypedefAliases[s].AsAlias(s) : null;
|
||||
@@ -34,7 +30,8 @@ namespace Il2CppInspector.Cpp
|
||||
// Architecture width in bits (32/64) - to determine pointer sizes
|
||||
public int WordSize { get; }
|
||||
|
||||
private Dictionary<string, ComplexValueType> complexTypeMap = new Dictionary<string, ComplexValueType> {
|
||||
private Dictionary<string, ComplexValueType> complexTypeMap = new()
|
||||
{
|
||||
["struct"] = ComplexValueType.Struct,
|
||||
["union"] = ComplexValueType.Union,
|
||||
["enum"] = ComplexValueType.Enum
|
||||
@@ -44,22 +41,23 @@ namespace Il2CppInspector.Cpp
|
||||
private string currentGroup = string.Empty;
|
||||
public void SetGroup(string group) => currentGroup = group;
|
||||
|
||||
private static readonly List<CppType> primitiveTypes = new List<CppType> {
|
||||
new CppType("uint8_t", 8),
|
||||
new CppType("uint16_t", 16),
|
||||
new CppType("uint32_t", 32),
|
||||
new CppType("uint64_t", 64),
|
||||
new CppType("int8_t", 8),
|
||||
new CppType("int16_t", 16),
|
||||
new CppType("int32_t", 32),
|
||||
new CppType("int64_t", 64),
|
||||
new CppType("char", 8),
|
||||
new CppType("int", 32),
|
||||
new CppType("float", 32),
|
||||
new CppType("double", 64),
|
||||
new CppType("bool", 8),
|
||||
new CppType("void", 0)
|
||||
};
|
||||
private static readonly List<CppType> primitiveTypes =
|
||||
[
|
||||
new("uint8_t", 8),
|
||||
new("uint16_t", 16),
|
||||
new("uint32_t", 32),
|
||||
new("uint64_t", 64),
|
||||
new("int8_t", 8),
|
||||
new("int16_t", 16),
|
||||
new("int32_t", 32),
|
||||
new("int64_t", 64),
|
||||
new("char", 8),
|
||||
new("int", 32),
|
||||
new("float", 32),
|
||||
new("double", 64),
|
||||
new("bool", 8),
|
||||
new("void", 0)
|
||||
];
|
||||
|
||||
public CppTypeCollection(int wordSize) {
|
||||
if (wordSize != 32 && wordSize != 64)
|
||||
@@ -538,15 +536,18 @@ namespace Il2CppInspector.Cpp
|
||||
public CppComplexType Struct(string name = "", int alignmentBytes = 0) {
|
||||
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
|
||||
return (CppComplexType) cppType;
|
||||
|
||||
var type = new CppComplexType(ComplexValueType.Struct) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Add(type);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public CppComplexType Union(string name = "", int alignmentBytes = 0) {
|
||||
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
|
||||
return (CppComplexType) cppType;
|
||||
|
||||
var type = new CppComplexType(ComplexValueType.Union) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Add(type);
|
||||
@@ -554,9 +555,13 @@ namespace Il2CppInspector.Cpp
|
||||
}
|
||||
|
||||
public CppEnumType Enum(CppType underlyingType, string name = "") {
|
||||
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
|
||||
return (CppEnumType)cppType;
|
||||
|
||||
var type = new CppEnumType(underlyingType) {Name = name, Group = currentGroup};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Add(type);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -585,11 +590,17 @@ namespace Il2CppInspector.Cpp
|
||||
cppTypes.AddFromDeclarationText(apis);
|
||||
|
||||
// Don't allow any of the header type names or primitive type names to be re-used
|
||||
foreach (var type in cppTypes.Types.Values)
|
||||
declGen?.TypeNamespace.TryReserveName(type.Name);
|
||||
foreach (var type in cppTypes.Types.Keys)
|
||||
{
|
||||
declGen?.TypeNamespace.TryReserveName(type);
|
||||
declGen?.GlobalsNamespace.TryReserveName(type);
|
||||
}
|
||||
|
||||
foreach (var typedef in cppTypes.TypedefAliases.Values)
|
||||
declGen?.GlobalsNamespace.TryReserveName(typedef.Name);
|
||||
foreach (var typedef in cppTypes.TypedefAliases.Keys)
|
||||
{
|
||||
declGen?.TypeNamespace.TryReserveName(typedef);
|
||||
declGen?.GlobalsNamespace.TryReserveName(typedef);
|
||||
}
|
||||
|
||||
cppTypes.SetGroup("");
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Il2CppInspector.Next;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Next;
|
||||
using VersionedSerialization;
|
||||
|
||||
namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
@@ -140,7 +141,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
// No il2cpp exports? Just return the earliest version from the header range
|
||||
// The API version may be incorrect but should be a subset of the real API and won't cause C++ compile errors
|
||||
if (!exports.Any()) {
|
||||
Console.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project");
|
||||
AnsiConsole.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project");
|
||||
|
||||
return typeHeaders.Select(t => new UnityHeaders(t,
|
||||
apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList();
|
||||
@@ -161,7 +162,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
|
||||
if (apiMatches.Any()) {
|
||||
// Intersect all API ranges with all header ranges to produce final list of possible ranges
|
||||
Console.WriteLine("IL2CPP API discovery was successful");
|
||||
AnsiConsole.WriteLine("IL2CPP API discovery was successful");
|
||||
|
||||
return typeHeaders.SelectMany(
|
||||
t => apiMatches.Where(a => t.VersionRange.Intersect(a.VersionRange) != null)
|
||||
@@ -170,7 +171,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
|
||||
// None of the possible API versions match the binary
|
||||
// Select the oldest API version from the group - C++ project compilation will fail
|
||||
Console.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial");
|
||||
AnsiConsole.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial");
|
||||
|
||||
return typeHeaders.Select(t => new UnityHeaders(t,
|
||||
apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -51,7 +52,7 @@ namespace Il2CppInspector
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
IFileFormatStream loaded = null;
|
||||
|
||||
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -51,7 +52,7 @@ namespace Il2CppInspector
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
IFileFormatStream loaded = null;
|
||||
|
||||
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -160,7 +161,7 @@ namespace Il2CppInspector
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
|
||||
AnsiConsole.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
|
||||
preferPHT = true;
|
||||
SHT = [];
|
||||
}
|
||||
@@ -170,12 +171,12 @@ namespace Il2CppInspector
|
||||
// These can happen as a result of conversions from other formats to ELF,
|
||||
// or if the SHT has been deliberately stripped
|
||||
if (!SHT.Any()) {
|
||||
Console.WriteLine("ELF binary has no SHT - reverting to PHT");
|
||||
AnsiConsole.WriteLine("ELF binary has no SHT - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
else if (SHT.All(s => conv.ULong(s.sh_addr) == 0ul)) {
|
||||
Console.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
|
||||
AnsiConsole.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ namespace Il2CppInspector
|
||||
|
||||
// If the first file offset of the first PHT is zero, assume a dumped image
|
||||
if (PHT.Any(t => conv.ULong(t.p_vaddr) == 0ul)) {
|
||||
Console.WriteLine("ELF binary appears to be a dumped memory image");
|
||||
AnsiConsole.WriteLine("ELF binary appears to be a dumped memory image");
|
||||
isMemoryImage = true;
|
||||
}
|
||||
preferPHT = true;
|
||||
@@ -202,7 +203,7 @@ namespace Il2CppInspector
|
||||
else {
|
||||
var shtOverlap = shtShouldBeOrdered.Aggregate((x, y) => x <= y? y : ulong.MaxValue) == ulong.MaxValue;
|
||||
if (shtOverlap) {
|
||||
Console.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
|
||||
AnsiConsole.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
}
|
||||
@@ -223,7 +224,20 @@ namespace Il2CppInspector
|
||||
|
||||
// Get dynamic table if it exists (must be done after rebasing)
|
||||
if (GetProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC)
|
||||
DynamicTable = ReadArray<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
|
||||
{
|
||||
// Important: do not use p_offset here!
|
||||
// Only load sections should be loaded, which should also include the memory region that contains the dynamic section.
|
||||
// This just provides the virtual address of the section.
|
||||
// Some binaries may use the offset here to point to a fake version of the dynamic section,
|
||||
// making relocation resolution and subsequent analysis fail.
|
||||
// Reference for Android:
|
||||
// phdr_table_get_dynamic_section, https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker_phdr.cpp
|
||||
|
||||
var dynamicAddr = conv.ULong(PT_DYNAMIC.p_vaddr);
|
||||
var dynamicSize = (int)(conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>)));
|
||||
|
||||
DynamicTable = ReadMappedArray<elf_dynamic<TWord>>(dynamicAddr, dynamicSize);
|
||||
}
|
||||
|
||||
// Get offset of code section
|
||||
var codeSegment = PHT.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X);
|
||||
@@ -254,21 +268,6 @@ namespace Il2CppInspector
|
||||
|
||||
StatusUpdate("Finding relocations");
|
||||
|
||||
// Two types: add value from offset in image, and add value from specified addend
|
||||
foreach (var relSection in GetSections(Elf.SHT_REL)) {
|
||||
reverseMapExclusions.Add(((uint) conv.Int(relSection.sh_offset), (uint) (conv.Int(relSection.sh_offset) + conv.Int(relSection.sh_size) - 1)));
|
||||
rels.UnionWith(
|
||||
from rel in ReadArray<elf_rel<TWord>>(conv.Long(relSection.sh_offset), conv.Int(conv.Div(relSection.sh_size, relSection.sh_entsize)))
|
||||
select new ElfReloc(rel, SHT[relSection.sh_link].sh_offset));
|
||||
}
|
||||
|
||||
foreach (var relaSection in GetSections(Elf.SHT_RELA)) {
|
||||
reverseMapExclusions.Add(((uint) conv.Int(relaSection.sh_offset), (uint) (conv.Int(relaSection.sh_offset) + conv.Int(relaSection.sh_size) - 1)));
|
||||
rels.UnionWith(
|
||||
from rela in ReadArray<elf_rela<TWord>>(conv.Long(relaSection.sh_offset), conv.Int(conv.Div(relaSection.sh_size, relaSection.sh_entsize)))
|
||||
select new ElfReloc(rela, SHT[relaSection.sh_link].sh_offset));
|
||||
}
|
||||
|
||||
// Relocations in dynamic section
|
||||
if (GetDynamicEntry(Elf.DT_REL) is elf_dynamic<TWord> dt_rel) {
|
||||
var dt_rel_count = conv.Int(conv.Div(GetDynamicEntry(Elf.DT_RELSZ).d_un, GetDynamicEntry(Elf.DT_RELENT).d_un));
|
||||
@@ -291,7 +290,7 @@ namespace Il2CppInspector
|
||||
}
|
||||
|
||||
// Process relocations
|
||||
var relsz = Sizeof(typeof(TSym));
|
||||
var relsz = (uint)Sizeof(typeof(TSym));
|
||||
|
||||
var currentRel = 0;
|
||||
var totalRel = rels.Count();
|
||||
@@ -301,7 +300,26 @@ namespace Il2CppInspector
|
||||
if (currentRel % 1000 == 0)
|
||||
StatusUpdate($"Processing relocations ({currentRel * 100 / totalRel:F0}%)");
|
||||
|
||||
var symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz).st_value; // S
|
||||
TWord symValue;
|
||||
|
||||
try
|
||||
{
|
||||
// man this really needs a full overhaul
|
||||
symValue = ReadMappedObject<TSym>(conv.ULong(rel.SymbolTable) + conv.ULong(rel.SymbolIndex) * relsz)
|
||||
.st_value; // S
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
try
|
||||
{
|
||||
symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz)
|
||||
.st_value; // S
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore relocations into memory addresses not mapped from the image
|
||||
try {
|
||||
@@ -344,7 +362,7 @@ namespace Il2CppInspector
|
||||
WriteWord(result.newValue);
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"Processed {rels.Count} relocations");
|
||||
AnsiConsole.WriteLine($"Processed {rels.Count} relocations");
|
||||
|
||||
// Build symbol and export tables
|
||||
processSymbols();
|
||||
@@ -388,7 +406,8 @@ namespace Il2CppInspector
|
||||
WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
|
||||
}
|
||||
|
||||
private void processSymbols() {
|
||||
private void processSymbols()
|
||||
{
|
||||
StatusUpdate("Processing symbols");
|
||||
|
||||
// Three possible symbol tables in ELF files
|
||||
@@ -436,7 +455,15 @@ namespace Il2CppInspector
|
||||
symbolTable.Clear();
|
||||
var exportTable = new Dictionary<string, Export>();
|
||||
|
||||
foreach (var pTab in pTables) {
|
||||
var alreadyProcessed = new List<(TWord offset, TWord count)>();
|
||||
|
||||
foreach (var pTab in pTables)
|
||||
{
|
||||
if (alreadyProcessed.Any(x =>
|
||||
conv.ULong(x.offset) == conv.ULong(pTab.offset)))
|
||||
continue;
|
||||
|
||||
alreadyProcessed.Add((pTab.offset, pTab.count));
|
||||
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
|
||||
|
||||
foreach (var symbol in symbol_table)
|
||||
@@ -463,7 +490,7 @@ namespace Il2CppInspector
|
||||
var symbolItem = new Symbol {Name = name, Type = type, VirtualAddress = conv.ULong(symbol.st_value) };
|
||||
symbolTable.TryAdd(name, symbolItem);
|
||||
if (symbol.st_shndx != (ushort) Elf.SHN_UNDEF)
|
||||
exportTable.TryAdd(name, new Export {Name = symbolItem.DemangledName, VirtualAddress = conv.ULong(symbol.st_value)});
|
||||
exportTable.TryAdd(name, new Export {Name = symbolItem.Name, VirtualAddress = conv.ULong(symbol.st_value)});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace Il2CppInspector
|
||||
try {
|
||||
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
|
||||
null, new[] { typeof(BinaryObjectStream), typeof(LoadOptions), typeof(EventHandler<string>) }, null)
|
||||
.Invoke(null, new object[] { binaryObjectStream, loadOptions, statusCallback }) is IFileFormatStream loaded) {
|
||||
.Invoke(null, [binaryObjectStream, loadOptions, statusCallback]) is IFileFormatStream loaded) {
|
||||
|
||||
loaded.IsModified |= preProcessResult.IsStreamModified;
|
||||
return loaded;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -31,6 +32,7 @@ namespace Il2CppInspector
|
||||
LC_DYLD_INFO_ONLY = 0x80000022,
|
||||
LC_FUNCTION_STARTS = 0x26,
|
||||
LC_ENCRYPTION_INFO_64 = 0x2C,
|
||||
LC_DYLD_CHAINED_FIXUPS = 0x80000034,
|
||||
|
||||
CPU_TYPE_X86 = 7,
|
||||
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
|
||||
@@ -172,4 +174,47 @@ namespace Il2CppInspector
|
||||
public bool r_extern => ((r_data >> 27) & 1) == 1;
|
||||
public uint r_type => r_data >> 28;
|
||||
}
|
||||
|
||||
[VersionedStruct]
|
||||
public partial struct MachODyldChainedFixupsHeader
|
||||
{
|
||||
public uint FixupsVersion;
|
||||
public uint StartsOffset;
|
||||
public uint ImportsOffset;
|
||||
public uint SymbolsOffset;
|
||||
public uint ImportsCount;
|
||||
public uint ImportsFormat;
|
||||
public uint SymbolsFormat;
|
||||
}
|
||||
|
||||
[VersionedStruct]
|
||||
public partial struct MachODyldChainedStartsInSegment
|
||||
{
|
||||
public const ushort DYLD_CHAINED_PTR_START_NONE = 0xffff;
|
||||
|
||||
public uint StructSize;
|
||||
public ushort PageSize;
|
||||
public ushort PointerFormat;
|
||||
public ulong SegmentOffset;
|
||||
public uint MaxValidPointer;
|
||||
public ushort PageCount;
|
||||
}
|
||||
|
||||
public enum MachODyldChainedPtr
|
||||
{
|
||||
DYLD_CHAINED_PTR_64 = 2,
|
||||
DYLD_CHAINED_PTR_64_OFFSET = 6,
|
||||
}
|
||||
|
||||
[VersionedStruct]
|
||||
public partial struct MachODyldChainedPtr64Rebase
|
||||
{
|
||||
private ulong _value;
|
||||
|
||||
public ulong Target => _value & 0xfffffffff;
|
||||
public ulong High8 => (_value >> 36) & 0xff;
|
||||
public ulong Reserved => (_value >> (36 + 8)) & 0x7f;
|
||||
public ulong Next => (_value >> (36 + 8 + 7)) & 0xfff;
|
||||
public bool Bind => ((_value >> (36 + 8 + 7 + 12)) & 0x1) == 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -172,15 +173,21 @@ namespace Il2CppInspector
|
||||
if (encryptionInfo.CryptID != 0)
|
||||
throw new NotImplementedException("This Mach-O executable is encrypted with FairPlay DRM and cannot be processed. Please provide a decrypted version of the executable.");
|
||||
break;
|
||||
|
||||
case MachO.LC_DYLD_CHAINED_FIXUPS:
|
||||
var chainedFixupsInfo = ReadObject<MachOLinkEditDataCommand>();
|
||||
ApplyChainedFixups(chainedFixupsInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
// There might be other data after the load command so always use the specified total size to step forwards
|
||||
Position = startPos + loadCommand.Size;
|
||||
}
|
||||
|
||||
// Note: Some binaries do not have __mod_init_func, but instead just __init_offset with offsets to the init functions. This check is disabled.
|
||||
// Must find __mod_init_func
|
||||
if (funcTab == null)
|
||||
return false;
|
||||
//if (funcTab == null)
|
||||
// return false;
|
||||
|
||||
// Process relocations
|
||||
foreach (var section in machoSections) {
|
||||
@@ -188,7 +195,7 @@ namespace Il2CppInspector
|
||||
|
||||
// TODO: Implement Mach-O relocations
|
||||
if (rels.Any()) {
|
||||
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
|
||||
AnsiConsole.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +289,7 @@ namespace Il2CppInspector
|
||||
: SymbolType.Unknown;
|
||||
|
||||
if (type == SymbolType.Unknown) {
|
||||
Console.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} " + CxxDemangler.CxxDemangler.Demangle(name));
|
||||
AnsiConsole.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} {name}");
|
||||
}
|
||||
|
||||
// Ignore duplicates
|
||||
@@ -290,7 +297,82 @@ namespace Il2CppInspector
|
||||
}
|
||||
}
|
||||
|
||||
public override uint[] GetFunctionTable() => ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
|
||||
private void ApplyChainedFixups(in MachOLinkEditDataCommand info)
|
||||
{
|
||||
var chainedFixupsHeader = ReadVersionedObject<MachODyldChainedFixupsHeader>(info.Offset);
|
||||
if (chainedFixupsHeader.FixupsVersion != 0)
|
||||
{
|
||||
AnsiConsole.WriteLine($"Unsupported chained fixups version: {chainedFixupsHeader.FixupsVersion}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chainedFixupsHeader.ImportsFormat != 1 /* DYLD_CHAINED_IMPORT */)
|
||||
{
|
||||
AnsiConsole.WriteLine($"Unsupported chained fixups import format: {chainedFixupsHeader.ImportsFormat}");
|
||||
return;
|
||||
}
|
||||
|
||||
//var importsBase = info.Offset + chainedFixupsHeader.ImportsOffset;
|
||||
//var imports = ReadPrimitiveArray<uint>(importsBase,
|
||||
// chainedFixupsHeader.ImportsCount);
|
||||
|
||||
//var symbolsBase = info.Offset + chainedFixupsHeader.SymbolsOffset; // todo: apparently this supports zlib
|
||||
|
||||
var startsBase = info.Offset + chainedFixupsHeader.StartsOffset;
|
||||
var segmentCount = ReadPrimitive<uint>(startsBase);
|
||||
var segmentStartOffsets = ReadPrimitiveArray<uint>(startsBase + 4, segmentCount);
|
||||
|
||||
foreach (var startOffset in segmentStartOffsets)
|
||||
{
|
||||
if (startOffset == 0)
|
||||
continue;
|
||||
|
||||
var startsInfo = ReadVersionedObject<MachODyldChainedStartsInSegment>(startsBase + startOffset);
|
||||
if (startsInfo.SegmentOffset == 0)
|
||||
continue;
|
||||
|
||||
var pointerFormat = (MachODyldChainedPtr)startsInfo.PointerFormat;
|
||||
|
||||
var pages = ReadPrimitiveArray<ushort>(
|
||||
startsBase + startOffset + MachODyldChainedStartsInSegment.Size(), startsInfo.PageCount);
|
||||
|
||||
for (var i = 0; i < pages.Length; i++)
|
||||
{
|
||||
var page = pages[i];
|
||||
if (page == MachODyldChainedStartsInSegment.DYLD_CHAINED_PTR_START_NONE)
|
||||
continue;
|
||||
|
||||
var chainOffset = startsInfo.SegmentOffset + (ulong)(i * startsInfo.PageSize) + page;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var currentEntry = ReadVersionedObject<MachODyldChainedPtr64Rebase>((long)chainOffset);
|
||||
|
||||
var fixedValue = 0ul;
|
||||
|
||||
if (!currentEntry.Bind) // todo: bind
|
||||
{
|
||||
fixedValue = pointerFormat switch
|
||||
{
|
||||
MachODyldChainedPtr.DYLD_CHAINED_PTR_64
|
||||
or MachODyldChainedPtr.DYLD_CHAINED_PTR_64_OFFSET
|
||||
=> currentEntry.High8 << 56 | currentEntry.Target,
|
||||
_ => fixedValue
|
||||
};
|
||||
|
||||
Write((long)chainOffset, fixedValue);
|
||||
}
|
||||
|
||||
if (currentEntry.Next == 0)
|
||||
break;
|
||||
|
||||
chainOffset += currentEntry.Next * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint[] GetFunctionTable() => funcTab == null ? [] : ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
|
||||
|
||||
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -102,7 +103,7 @@ namespace Il2CppInspector
|
||||
// Unpacking must be done starting here, one byte after the end of the headers
|
||||
// Packed or previously packed with Themida? This is purely for information
|
||||
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _)
|
||||
Console.WriteLine("Themida protection detected");
|
||||
AnsiConsole.WriteLine("Themida protection detected");
|
||||
|
||||
// Packed with anything (including Themida)?
|
||||
mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null;
|
||||
@@ -114,27 +115,32 @@ namespace Il2CppInspector
|
||||
section.Name = wantedSectionTypes[section.Characteristics];
|
||||
|
||||
// Get base of code
|
||||
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
|
||||
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections
|
||||
.FirstOrDefault(x => x.Characteristics.HasFlag(PE.IMAGE_SCN_MEM_EXECUTE))?.PointerToRawData ?? 0;
|
||||
|
||||
// Confirm that .rdata section begins at same place as IAT
|
||||
var rData = sections.First(x => x.Name == ".rdata");
|
||||
mightBePacked |= rData.VirtualAddress != IATStart;
|
||||
|
||||
var rData = sections.FirstOrDefault(x => x.Name == ".rdata");
|
||||
mightBePacked |= rData == null || rData.VirtualAddress != IATStart;
|
||||
if (rData != null)
|
||||
{
|
||||
// Calculate start of function pointer table
|
||||
pFuncTable = rData.PointerToRawData + IATSize;
|
||||
|
||||
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
|
||||
Position = pFuncTable;
|
||||
if (pe is PEOptHeader32) {
|
||||
if (pe is PEOptHeader32)
|
||||
{
|
||||
while (ReadUInt32() != 0)
|
||||
pFuncTable += 4;
|
||||
pFuncTable += 4;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
while (ReadUInt64() != 0)
|
||||
pFuncTable += 8;
|
||||
pFuncTable += 8;
|
||||
}
|
||||
}
|
||||
|
||||
// In the fist go round, we signal that this is at least a valid PE file; we don't try to unpack yet
|
||||
return true;
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -100,7 +101,7 @@ namespace Il2CppInspector
|
||||
// Get the entire remaining chunk, or to the end of the file if it doesn't contain the end of the chunk
|
||||
var length = (uint) Math.Min(chunk.Memory.End - memoryNext, source.Length);
|
||||
|
||||
Console.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}");
|
||||
AnsiConsole.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}");
|
||||
|
||||
// Can't use Stream.CopyTo as it doesn't support length parameter
|
||||
var buffer = new byte[length];
|
||||
|
||||
@@ -20,7 +20,5 @@ namespace Il2CppInspector
|
||||
public ulong VirtualAddress { get; set; }
|
||||
public string Name { get; set; }
|
||||
public SymbolType Type { get; set; }
|
||||
|
||||
public string DemangledName => CxxDemangler.CxxDemangler.Demangle(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
*/
|
||||
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using Spectre.Console;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using VersionedSerialization;
|
||||
|
||||
namespace Il2CppInspector
|
||||
@@ -191,7 +192,7 @@ namespace Il2CppInspector
|
||||
var symbols = Image.GetSymbolTable();
|
||||
|
||||
if (symbols.Any()) {
|
||||
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
|
||||
AnsiConsole.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
|
||||
|
||||
symbols.TryGetValue("g_CodeRegistration", out var code);
|
||||
symbols.TryGetValue("g_MetadataRegistration", out var metadata);
|
||||
@@ -202,13 +203,13 @@ namespace Il2CppInspector
|
||||
symbols.TryGetValue("_g_MetadataRegistration", out metadata);
|
||||
|
||||
if (code != null && metadata != null) {
|
||||
Console.WriteLine("Required structures acquired from symbol lookup");
|
||||
AnsiConsole.WriteLine("Required structures acquired from symbol lookup");
|
||||
return (code.VirtualAddress, metadata.VirtualAddress);
|
||||
} else {
|
||||
Console.WriteLine("No matches in symbol table");
|
||||
AnsiConsole.WriteLine("No matches in symbol table");
|
||||
}
|
||||
} else if (symbols != null) {
|
||||
Console.WriteLine("No symbol table present in binary file");
|
||||
AnsiConsole.WriteLine("No symbol table present in binary file");
|
||||
} else {
|
||||
Console.WriteLine("Symbol table search not implemented for this binary format");
|
||||
}
|
||||
@@ -227,12 +228,12 @@ namespace Il2CppInspector
|
||||
var (code, metadata) = ConsiderCode(Image, loc);
|
||||
if (code != 0) {
|
||||
RegistrationFunctionPointer = loc + Image.GlobalOffset;
|
||||
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
|
||||
AnsiConsole.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
|
||||
return (code, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("No matches via code heuristics");
|
||||
AnsiConsole.WriteLine("No matches via code heuristics");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -244,11 +245,11 @@ namespace Il2CppInspector
|
||||
|
||||
var (codePtr, metadataPtr) = ImageScan(Metadata);
|
||||
if (codePtr == 0) {
|
||||
Console.WriteLine("No matches via data heuristics");
|
||||
AnsiConsole.WriteLine("No matches via data heuristics");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine("Required structures acquired from data heuristics");
|
||||
AnsiConsole.WriteLine("Required structures acquired from data heuristics");
|
||||
return (codePtr, metadataPtr);
|
||||
}
|
||||
|
||||
@@ -274,8 +275,8 @@ namespace Il2CppInspector
|
||||
|
||||
var pointerSize = Image.Bits == 32 ? 4u : 8u;
|
||||
|
||||
Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
|
||||
Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
|
||||
AnsiConsole.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
|
||||
AnsiConsole.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
|
||||
|
||||
// Root structures from which we find everything else
|
||||
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
|
||||
@@ -337,7 +338,15 @@ namespace Il2CppInspector
|
||||
}
|
||||
|
||||
// Read method invoker pointer indices - one per method
|
||||
MethodInvokerIndices.Add(module, Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int) module.MethodPointerCount));
|
||||
try
|
||||
{
|
||||
MethodInvokerIndices.Add(module,
|
||||
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
MethodInvokerIndices.Add(module, [..new int[(int)module.MethodPointerCount]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,12 +407,21 @@ namespace Il2CppInspector
|
||||
var type = TypeReferences[i];
|
||||
if (type.Type.IsTypeDefinitionEnum())
|
||||
{
|
||||
if (type.Data.Type.PointerValue >= baseDefinitionPtr)
|
||||
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize;
|
||||
|
||||
Debug.Assert(Metadata!.Types.Length > type.Data.KlassIndex);
|
||||
}
|
||||
else if (type.Type.IsGenericParameterEnum())
|
||||
{
|
||||
if (type.Data.Type.PointerValue >= baseGenericPtr)
|
||||
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize;
|
||||
|
||||
Debug.Assert(Metadata!.GenericParameters.Length > type.Data.KlassIndex);
|
||||
}
|
||||
|
||||
Debug.Assert((long)type.Data.Value >= 0);
|
||||
|
||||
builder.Add(type);
|
||||
}
|
||||
TypeReferences = builder.MoveToImmutable();
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
*/
|
||||
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using Il2CppInspector.Utils;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -14,8 +17,6 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using VersionedSerialization;
|
||||
|
||||
namespace Il2CppInspector
|
||||
@@ -410,7 +411,7 @@ namespace Il2CppInspector
|
||||
if (metadataFile != null) {
|
||||
// Extract the metadata file to memory
|
||||
if (!silent)
|
||||
Console.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
|
||||
|
||||
metadataMemoryStream = new MemoryStream();
|
||||
using var metadataStream = metadataFile.Open();
|
||||
@@ -428,7 +429,7 @@ namespace Il2CppInspector
|
||||
// IPAs will only have one binary (which may or may not be a UB covering multiple architectures)
|
||||
if (ipaBinaryFolder != null) {
|
||||
if (!silent)
|
||||
Console.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
|
||||
|
||||
// Extract the binary file or package to memory
|
||||
binaryMemoryStream = new MemoryStream();
|
||||
@@ -531,7 +532,7 @@ namespace Il2CppInspector
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine("Detected metadata version " + metadata.Version);
|
||||
AnsiConsole.WriteLine("Detected metadata version " + metadata.Version);
|
||||
|
||||
// Load the il2cpp code file (try all available file formats)
|
||||
IFileFormatStream stream;
|
||||
@@ -559,16 +560,16 @@ namespace Il2CppInspector
|
||||
|
||||
var processors = new List<Il2CppInspector>();
|
||||
foreach (var image in stream.Images) {
|
||||
Console.WriteLine("Container format: " + image.Format);
|
||||
Console.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness);
|
||||
Console.WriteLine("Architecture word size: {0}-bit", image.Bits);
|
||||
Console.WriteLine("Instruction set: " + image.Arch);
|
||||
Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);
|
||||
AnsiConsole.WriteLine("Container format: " + image.Format);
|
||||
AnsiConsole.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness);
|
||||
AnsiConsole.WriteLine("Architecture word size: {0}-bit", image.Bits);
|
||||
AnsiConsole.WriteLine("Instruction set: " + image.Arch);
|
||||
AnsiConsole.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);
|
||||
|
||||
// Architecture-agnostic load attempt
|
||||
try {
|
||||
if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) {
|
||||
Console.WriteLine("IL2CPP binary version " + image.Version);
|
||||
AnsiConsole.WriteLine("IL2CPP binary version " + image.Version);
|
||||
|
||||
processors.Add(new Il2CppInspector(binary, metadata));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Il2CppInspector
|
||||
// Set object versioning for Bin2Object from metadata version
|
||||
Version = new StructVersion(Header.Version);
|
||||
|
||||
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) {
|
||||
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V350) {
|
||||
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version}).");
|
||||
}
|
||||
|
||||
@@ -215,9 +215,26 @@ namespace Il2CppInspector
|
||||
else {
|
||||
var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>());
|
||||
|
||||
if (Version >= MetadataVersions.V350)
|
||||
{
|
||||
StringLiterals = new string[stringLiteralList.Length - 1];
|
||||
for (var i = 0; i < stringLiteralList.Length; i++)
|
||||
{
|
||||
var currentStringDataIndex = stringLiteralList[i].DataIndex;
|
||||
var nextStringDataIndex = stringLiteralList[i + 1].DataIndex;
|
||||
var stringLength = nextStringDataIndex - currentStringDataIndex;
|
||||
|
||||
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + currentStringDataIndex, stringLength);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
StringLiterals = new string[stringLiteralList.Length];
|
||||
for (var i = 0; i < stringLiteralList.Length; i++)
|
||||
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Post-processing hook
|
||||
|
||||
@@ -41,10 +41,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dnlib" Version="4.4.0" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
|
||||
<NoWarn>NU1605</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.50.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Il2CppInspector.Cpp;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Reflection;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Il2CppInspector.Model
|
||||
{
|
||||
@@ -40,6 +41,9 @@ namespace Il2CppInspector.Model
|
||||
// The types are ordered to enable the production of code output without forward dependencies
|
||||
public List<CppType> DependencyOrderedCppTypes { get; private set; }
|
||||
|
||||
// Required forward definition types for the C++ type definitions
|
||||
public List<CppType> RequiredForwardDefinitions { get; private set; } = [];
|
||||
|
||||
// Composite mapping of all the .NET methods in the IL2CPP binary
|
||||
public MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod> Methods { get; } = new MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod>();
|
||||
|
||||
@@ -149,11 +153,11 @@ namespace Il2CppInspector.Model
|
||||
UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last();
|
||||
UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min;
|
||||
|
||||
Console.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})");
|
||||
AnsiConsole.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})");
|
||||
|
||||
// Check for matching metadata and binary versions
|
||||
if (UnityHeaders.MetadataVersion != Image.Version) {
|
||||
Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
|
||||
AnsiConsole.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
|
||||
$" does not match metadata version {Image.Version}.");
|
||||
}
|
||||
|
||||
@@ -236,8 +240,18 @@ namespace Il2CppInspector.Model
|
||||
break;
|
||||
case MetadataUsageType.MethodDef or MetadataUsageType.MethodRef:
|
||||
var method = TypeModel.GetMetadataUsageMethod(usage);
|
||||
|
||||
declarationGenerator.IncludeMethod(method);
|
||||
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||
var definitions = declarationGenerator.GenerateRemainingTypeDeclarations();
|
||||
if (definitions == null)
|
||||
{
|
||||
// if we end up here, type generation has failed
|
||||
// todo: this try/catch is a massive hack to sidestep the original issue of generation failing,
|
||||
// todo: this needs to be improved.
|
||||
break;
|
||||
}
|
||||
|
||||
AddTypes(definitions);
|
||||
|
||||
// Any method here SHOULD already be in the Methods list
|
||||
// but we have seen one example where this is not the case for a MethodDef
|
||||
@@ -247,6 +261,7 @@ namespace Il2CppInspector.Model
|
||||
Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) { Group = Group });
|
||||
}
|
||||
Methods[method].MethodInfoPtrAddress = address;
|
||||
|
||||
break;
|
||||
|
||||
// FieldInfo is used for array initializers.
|
||||
@@ -294,6 +309,8 @@ namespace Il2CppInspector.Model
|
||||
declarationGenerator.IncludeType(type);
|
||||
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||
|
||||
RequiredForwardDefinitions = declarationGenerator.GenerateRequiredForwardDefinitions();
|
||||
|
||||
// Restore stdout
|
||||
Console.SetOut(stdout);
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ public partial record struct Il2CppCodeRegistration
|
||||
[NativeInteger]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
|
||||
[VersionCondition(GreaterThan = "35.0")]
|
||||
public uint UnresolvedIndirectCallCount; // UnresolvedVirtualCallCount pre 29.1
|
||||
|
||||
[VersionCondition(GreaterThan = "22.0")]
|
||||
@@ -84,10 +85,12 @@ public partial record struct Il2CppCodeRegistration
|
||||
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
|
||||
[VersionCondition(GreaterThan = "35.0")]
|
||||
public Pointer<Il2CppMethodPointer> UnresolvedInstanceCallWrappers;
|
||||
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
|
||||
[VersionCondition(GreaterThan = "35.0")]
|
||||
public Pointer<Il2CppMethodPointer> UnresolvedStaticCallPointers;
|
||||
|
||||
[NativeInteger]
|
||||
|
||||
@@ -18,7 +18,7 @@ public partial record struct Il2CppMethodDefinition
|
||||
public TypeDefinitionIndex DeclaringType { get; private set; }
|
||||
public TypeIndex ReturnType { get; private set; }
|
||||
|
||||
[VersionCondition(EqualTo = "31.0")]
|
||||
[VersionCondition(GreaterThan = "31.0")]
|
||||
public uint ReturnParameterToken { get; private set; }
|
||||
|
||||
public ParameterIndex ParameterStart { get; private set; }
|
||||
|
||||
@@ -6,6 +6,7 @@ using StringLiteralIndex = int;
|
||||
[VersionedStruct]
|
||||
public partial record struct Il2CppStringLiteral
|
||||
{
|
||||
[VersionCondition(LessThan = "31.0")]
|
||||
public uint Length { get; private set; }
|
||||
public StringLiteralIndex DataIndex { get; private set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using VersionedSerialization;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
namespace Il2CppInspector.Next.Metadata;
|
||||
@@ -32,6 +33,8 @@ public partial record struct Il2CppTypeDefinition
|
||||
|
||||
public TypeIndex DeclaringTypeIndex { get; private set; }
|
||||
public TypeIndex ParentIndex { get; private set; }
|
||||
|
||||
[VersionCondition(LessThan = "31.0")]
|
||||
public TypeIndex ElementTypeIndex { get; private set; }
|
||||
|
||||
[VersionCondition(LessThan = "24.1")]
|
||||
@@ -80,4 +83,9 @@ public partial record struct Il2CppTypeDefinition
|
||||
public uint Token { get; private set; }
|
||||
|
||||
public readonly bool IsValid => NameIndex != 0;
|
||||
|
||||
public int GetEnumElementTypeIndex(StructVersion version)
|
||||
=> version >= MetadataVersions.V350
|
||||
? ParentIndex
|
||||
: ElementTypeIndex;
|
||||
}
|
||||
@@ -28,4 +28,7 @@ public static class MetadataVersions
|
||||
|
||||
// No tag - 29.0/31.0
|
||||
public static readonly string Tag2022 = "2022"; // 29.1/31.1
|
||||
|
||||
// Unity 6000.3.0a2
|
||||
public static readonly StructVersion V350 = new(35);
|
||||
}
|
||||
@@ -430,7 +430,7 @@ namespace Il2CppInspector.Outputs
|
||||
if (method.VirtualAddress.HasValue) {
|
||||
var args = new List<(string,object)> {
|
||||
("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
|
||||
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))),
|
||||
("Offset", $"0x{model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start):X}"),
|
||||
("VA", method.VirtualAddress.Value.Start.ToAddressString())
|
||||
};
|
||||
if (method.Definition.Slot != ushort.MaxValue)
|
||||
@@ -470,7 +470,7 @@ namespace Il2CppInspector.Outputs
|
||||
return def.AddAttribute(module, attributeAttribute,
|
||||
("Name", ca.AttributeType.Name),
|
||||
("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
|
||||
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start)))
|
||||
("Offset", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,13 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
// Write primitive type definitions for when we're not including other headers
|
||||
writeCode($"""
|
||||
#define IS_LIBCLANG_DECOMPILER (defined(_IDACLANG_) || defined(_BINARYNINJA_))
|
||||
#define IS_DECOMPILER (defined(_GHIDRA_) || defined(_IDA_) || IS_LIBCLANG_DECOMPILER)
|
||||
#if defined(_IDACLANG_) || defined(_BINARYNINJA_)
|
||||
#define IS_LIBCLANG_DECOMPILER
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDA_) || defined(IS_LIBCLANG_DECOMPILER)
|
||||
#define IS_DECOMPILER
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDA_)
|
||||
typedef unsigned __int8 uint8_t;
|
||||
@@ -58,7 +63,7 @@ namespace Il2CppInspector.Outputs
|
||||
typedef __int64 int64_t;
|
||||
#endif
|
||||
|
||||
#if IS_LIBCLANG_DECOMPILER
|
||||
#if defined(IS_LIBCLANG_DECOMPILER)
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
@@ -67,21 +72,26 @@ namespace Il2CppInspector.Outputs
|
||||
typedef short int16_t;
|
||||
typedef int int32_t;
|
||||
typedef long int64_t;
|
||||
|
||||
#ifdef linux
|
||||
#undef linux
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || IS_LIBCLANG_DECOMPILER
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(IS_LIBCLANG_DECOMPILER)
|
||||
typedef int{_model.Package.BinaryImage.Bits}_t intptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t uintptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t size_t;
|
||||
#endif
|
||||
|
||||
#if !IS_DECOMPILER
|
||||
#ifndef IS_DECOMPILER
|
||||
#define _CPLUSPLUS_
|
||||
#endif
|
||||
""");
|
||||
|
||||
if (_useBetterArraySize)
|
||||
writeCode("#define actual_il2cpp_array_size_t il2cpp_array_size_t");
|
||||
writeCode("#define il2cpp_array_size_t actual_il2cpp_array_size_t");
|
||||
|
||||
writeSectionHeader("IL2CPP internal types");
|
||||
writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits));
|
||||
@@ -94,9 +104,7 @@ namespace Il2CppInspector.Outputs
|
||||
{
|
||||
int32_t size;
|
||||
actual_il2cpp_array_size_t value;
|
||||
} better_il2cpp_array_size_t;
|
||||
|
||||
#define better_il2cpp_array_size_t il2cpp_array_size_t
|
||||
} il2cpp_array_size_t;
|
||||
""");
|
||||
|
||||
if (_model.TargetCompiler == CppCompilerType.MSVC)
|
||||
@@ -115,17 +123,20 @@ namespace Il2CppInspector.Outputs
|
||||
}
|
||||
|
||||
// C does not support namespaces
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("#ifndef IS_DECOMPILER");
|
||||
writeCode("namespace app {");
|
||||
writeCode("#endif");
|
||||
writeLine("");
|
||||
|
||||
writeForwardDefinitions();
|
||||
|
||||
writeTypesForGroup("Required forward definitions", "required_forward_definitions");
|
||||
writeTypesForGroup("Application types from method calls", "types_from_methods");
|
||||
writeTypesForGroup("Application types from generic methods", "types_from_generic_methods");
|
||||
writeTypesForGroup("Application types from usages", "types_from_usages");
|
||||
writeTypesForGroup("Application unused value types", "unused_concrete_types");
|
||||
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("#ifndef IS_DECOMPILER");
|
||||
writeCode("}");
|
||||
writeCode("#endif");
|
||||
}
|
||||
@@ -306,20 +317,33 @@ namespace Il2CppInspector.Outputs
|
||||
writeLine("");
|
||||
}
|
||||
|
||||
private void writeTypesForGroup(string header, string group) {
|
||||
private void writeForwardDefinitions()
|
||||
{
|
||||
writeSectionHeader("Required forward definitions");
|
||||
foreach (var cppType in _model.RequiredForwardDefinitions)
|
||||
writeCode(cppType.ToString());
|
||||
}
|
||||
|
||||
private void writeTypesForGroup(string header, string group)
|
||||
{
|
||||
writeSectionHeader(header);
|
||||
foreach (var cppType in _model.GetDependencyOrderedCppTypeGroup(group))
|
||||
if (cppType is CppEnumType) {
|
||||
{
|
||||
if (cppType is CppEnumType)
|
||||
{
|
||||
// Ghidra can't process C++ enum base types
|
||||
writeCode("#if defined(_CPLUSPLUS_)");
|
||||
writeCode(cppType.ToString());
|
||||
writeCode("#else");
|
||||
writeCode(cppType.ToString("c"));
|
||||
writeCode("#endif");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
writeCode(cppType.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCode(string text) {
|
||||
if (_model.TargetCompiler == CppCompilerType.MSVC)
|
||||
|
||||
@@ -217,7 +217,6 @@ namespace Il2CppInspector.Outputs
|
||||
foreach (var symbol in symbols) {
|
||||
writeObject(() => {
|
||||
writeName(symbol.VirtualAddress, symbol.Name);
|
||||
writer.WriteString("demangledName", symbol.DemangledName);
|
||||
writer.WriteString("type", symbol.Type.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
from binaryninja import *
|
||||
from binaryninja import (
|
||||
BinaryView,
|
||||
Component,
|
||||
Type,
|
||||
PointerType,
|
||||
TypeParser,
|
||||
Platform,
|
||||
Endianness,
|
||||
ArrayType,
|
||||
BackgroundTaskThread,
|
||||
demangle_gnu3,
|
||||
get_qualified_name,
|
||||
SegmentFlag,
|
||||
SectionSemantics,
|
||||
)
|
||||
from binaryninja.log import log_error
|
||||
|
||||
#try:
|
||||
# try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
@@ -10,16 +25,14 @@ from binaryninja import *
|
||||
# from datetime import datetime
|
||||
# from typing import Literal
|
||||
# bv: BinaryView = None # type: ignore
|
||||
#except:
|
||||
# except:
|
||||
# pass
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
# this is implemented,
|
||||
# however the write API does not seem to work properly here (possibly a bug),
|
||||
# so this is disabled for now
|
||||
supports_fake_string_segment: bool = False
|
||||
supports_fake_string_segment: bool = True
|
||||
|
||||
_status: BaseStatusHandler
|
||||
|
||||
@@ -32,20 +45,16 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
_address_size: int
|
||||
_endianness: Literal["little", "big"]
|
||||
|
||||
TYPE_PARSER_OPTIONS = [
|
||||
"--target=x86_64-pc-linux",
|
||||
"-x", "c++",
|
||||
"-D_BINARYNINJA_=1"
|
||||
]
|
||||
TYPE_PARSER_OPTIONS = ["--target=x86_64-pc-linux", "-x", "c++", "-D_BINARYNINJA_=1"]
|
||||
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
|
||||
def _get_or_create_type(self, type: str) -> Type:
|
||||
if type.startswith("struct "):
|
||||
type = type[len("struct "):]
|
||||
type = type[len("struct ") :]
|
||||
elif type.startswith("class "):
|
||||
type = type[len("class "):]
|
||||
type = type[len("class ") :]
|
||||
|
||||
if type in self._type_cache:
|
||||
return self._type_cache[type]
|
||||
@@ -66,9 +75,11 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
parsed_types, errors = TypeParser.default.parse_types_from_source(
|
||||
types,
|
||||
filename if filename else "types.hpp",
|
||||
self._view.platform if self._view.platform is not None else Platform["windows-x86_64"],
|
||||
self._view.platform
|
||||
if self._view.platform is not None
|
||||
else Platform["windows-x86_64"],
|
||||
self._view,
|
||||
self.TYPE_PARSER_OPTIONS
|
||||
self.TYPE_PARSER_OPTIONS,
|
||||
)
|
||||
|
||||
if parsed_types is None:
|
||||
@@ -90,7 +101,9 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
self._function_type_cache = {}
|
||||
|
||||
self._address_size = self._view.address_size
|
||||
self._endianness = "little" if self._view.endianness == Endianness.LittleEndian else "big"
|
||||
self._endianness = (
|
||||
"little" if self._view.endianness == Endianness.LittleEndian else "big"
|
||||
)
|
||||
|
||||
self._status.update_step("Parsing header")
|
||||
|
||||
@@ -105,7 +118,9 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
self._status.update_progress(1)
|
||||
return True
|
||||
|
||||
self._view.define_user_types([(x.name, x.type) for x in parsed_types.types], import_progress_func)
|
||||
self._view.define_user_types(
|
||||
[(x.name, x.type) for x in parsed_types.types], import_progress_func
|
||||
)
|
||||
|
||||
def on_finish(self):
|
||||
self._view.commit_undo_actions(self._undo_id)
|
||||
@@ -143,8 +158,8 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
if type in self._function_type_cache:
|
||||
function.type = self._function_type_cache[type] # type: ignore
|
||||
else:
|
||||
#log_info(f"skipping function type setting for {address}, {type}")
|
||||
#pass
|
||||
# log_info(f"skipping function type setting for {address}, {type}")
|
||||
# pass
|
||||
function.type = type.replace("this", "`this`")
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
@@ -176,7 +191,7 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
function.name = get_qualified_name(demangled)
|
||||
#function.type = type - this does not work due to the generated types not being namespaced. :(
|
||||
# function.type = type - this does not work due to the generated types not being namespaced. :(
|
||||
else:
|
||||
function.name = name
|
||||
|
||||
@@ -230,10 +245,19 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
def create_fake_segment(self, name: str, size: int) -> int:
|
||||
last_end_addr = self._view.mapped_address_ranges[-1].end
|
||||
if last_end_addr % 0x1000 != 0:
|
||||
last_end_addr += (0x1000 - (last_end_addr % 0x1000))
|
||||
last_end_addr += 0x1000 - (last_end_addr % 0x1000)
|
||||
|
||||
self._view.memory_map.add_memory_region(
|
||||
f"mem_{name}",
|
||||
last_end_addr,
|
||||
bytes(size),
|
||||
SegmentFlag.SegmentContainsData | SegmentFlag.SegmentReadable,
|
||||
)
|
||||
|
||||
self._view.add_user_section(
|
||||
name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics
|
||||
)
|
||||
|
||||
self._view.add_user_segment(last_end_addr, size, 0, 0, SegmentFlag.SegmentContainsData)
|
||||
self._view.add_user_section(name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics)
|
||||
return last_end_addr
|
||||
|
||||
def write_string(self, address: int, value: str) -> int:
|
||||
@@ -255,7 +279,8 @@ class BinaryNinjaStatusHandler(BaseStatusHandler):
|
||||
self.last_updated_time = datetime.min
|
||||
self._thread = thread
|
||||
|
||||
def initialize(self): pass
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
def update(self):
|
||||
if self.was_cancelled():
|
||||
@@ -271,7 +296,7 @@ class BinaryNinjaStatusHandler(BaseStatusHandler):
|
||||
total_time = current_time - self.start_time
|
||||
self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})"
|
||||
|
||||
def update_step(self, step, max_items = 0):
|
||||
def update_step(self, step, max_items=0):
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
@@ -279,15 +304,17 @@ class BinaryNinjaStatusHandler(BaseStatusHandler):
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
|
||||
def update_progress(self, new_progress = 1):
|
||||
def update_progress(self, new_progress=1):
|
||||
self.current_items += new_progress
|
||||
self.update()
|
||||
|
||||
def was_cancelled(self): return False
|
||||
def was_cancelled(self):
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
|
||||
# Entry point
|
||||
class Il2CppTask(BackgroundTaskThread):
|
||||
def __init__(self):
|
||||
@@ -299,4 +326,5 @@ class Il2CppTask(BackgroundTaskThread):
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
|
||||
|
||||
Il2CppTask().start()
|
||||
@@ -6,6 +6,7 @@ from ghidra.program.model.symbol import SourceType
|
||||
from ghidra.program.model.symbol import RefType
|
||||
from ghidra.app.cmd.label import DemanglerCmd
|
||||
from ghidra.app.services import DataTypeManagerService
|
||||
from java.lang import Long
|
||||
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
@@ -21,6 +22,9 @@ from ghidra.app.services import DataTypeManagerService
|
||||
class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
supports_fake_string_segment = False
|
||||
|
||||
def _to_address(self, value):
|
||||
return toAddr(Long(value))
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return getSourceFile().getParentFile().toString()
|
||||
|
||||
@@ -39,7 +43,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
|
||||
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
|
||||
if currentProgram.getExecutableFormat().endswith('(ELF)'):
|
||||
currentProgram.setImageBase(toAddr(0), True)
|
||||
currentProgram.setImageBase(self._to_address(0), True)
|
||||
|
||||
# Don't trigger decompiler
|
||||
setAnalysisOption(currentProgram, "Call Convention ID", "false")
|
||||
@@ -48,7 +52,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
pass
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
# Don't override existing functions
|
||||
fn = getFunctionAt(address)
|
||||
if fn is None:
|
||||
@@ -61,7 +65,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
|
||||
t = getDataTypes(type)[0]
|
||||
a = ArrayDataType(t, count, t.getLength())
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
removeDataAt(address)
|
||||
createData(address, a)
|
||||
|
||||
@@ -71,7 +75,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
|
||||
try:
|
||||
t = getDataTypes(type)[0]
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
removeDataAt(address)
|
||||
createData(address, t)
|
||||
except:
|
||||
@@ -79,16 +83,16 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
typeSig = CParserUtils.parseSignature(DataTypeManagerService@None, currentProgram, type)
|
||||
ApplyFunctionSignatureCmd(toAddr(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
|
||||
ApplyFunctionSignatureCmd(self._to_address(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
setEOLComment(toAddr(address), cmt)
|
||||
setEOLComment(self._to_address(address), cmt)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
setPlateComment(toAddr(address), cmt)
|
||||
setPlateComment(self._to_address(address), cmt)
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
|
||||
if len(name) > 2000:
|
||||
print("Name length exceeds 2000 characters, skipping (%s)" % name)
|
||||
@@ -107,7 +111,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
return self.set_data_name(address, name)
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self.xrefs.addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
self.xrefs.addMemoryReference(self._to_address(from_address), self._to_address(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
# Code declarations are not supported in Ghidra
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Il2CppInspector.PluginAPI;
|
||||
// This is the ONLY line to update when the API version changes
|
||||
using Il2CppInspector.PluginAPI.V100;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -12,11 +17,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Il2CppInspector.PluginAPI;
|
||||
|
||||
// This is the ONLY line to update when the API version changes
|
||||
using Il2CppInspector.PluginAPI.V100;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
|
||||
@@ -784,7 +785,9 @@ namespace Il2CppInspector.Reflection
|
||||
// Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum)
|
||||
if (Definition.Bitfield.EnumType) {
|
||||
IsEnum = true;
|
||||
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.ElementTypeIndex);
|
||||
|
||||
var enumUnderlyingTypeIndex = Definition.GetEnumElementTypeIndex(Assembly.Model.Package.Version);
|
||||
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, enumUnderlyingTypeIndex);
|
||||
}
|
||||
|
||||
// Pass-by-reference type
|
||||
|
||||
@@ -165,10 +165,9 @@ namespace Il2CppInspector.Reflection
|
||||
// Generic type definitions have an invoker index of -1
|
||||
foreach (var method in MethodsByDefinitionIndex) {
|
||||
var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition);
|
||||
if (index != -1) {
|
||||
if (MethodInvokers[index] == null)
|
||||
MethodInvokers[index] = new MethodInvoker(method, index);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
MethodInvokers[index] ??= new MethodInvoker(method, index);
|
||||
method.Invoker = MethodInvokers[index];
|
||||
}
|
||||
}
|
||||
@@ -176,12 +175,13 @@ namespace Il2CppInspector.Reflection
|
||||
// Create method invokers sourced from generic method invoker indices
|
||||
foreach (var spec in GenericMethods.Keys) {
|
||||
if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) {
|
||||
if (MethodInvokers[index] == null)
|
||||
MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
MethodInvokers[index] ??= new MethodInvoker(GenericMethods[spec], index);
|
||||
GenericMethods[spec].Invoker = MethodInvokers[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-processing hook
|
||||
PluginHooks.PostProcessTypeModel(this);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Il2CppInspector.Utils;
|
||||
|
||||
@@ -122,7 +123,7 @@ public static class BlobReader
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Console.WriteLine($"Found invalid compressed int at metadata address 0x{address:x8}. Reading as normal int.");
|
||||
AnsiConsole.WriteLine($"Found invalid compressed int at metadata address 0x{address:x8}. Reading as normal int.");
|
||||
return blob.ReadInt32(address);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ public static class BlobReader
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Console.WriteLine($"Found invalid compressed uint at metadata address 0x{address:x8}. Reading as normal uint.");
|
||||
AnsiConsole.WriteLine($"Found invalid compressed uint at metadata address 0x{address:x8}. Reading as normal uint.");
|
||||
return blob.ReadUInt32(address);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +164,9 @@ public static class BlobReader
|
||||
var typeHandle = inspector.TypeReferences[typeIndex].Data.KlassIndex;
|
||||
enumType = inspector.TypeDefinitions[typeHandle];
|
||||
|
||||
var elementTypeHandle = inspector.TypeReferences[enumType.ElementTypeIndex].Data.KlassIndex;
|
||||
var elementTypeIndex = enumType.GetEnumElementTypeIndex(inspector.Version);
|
||||
|
||||
var elementTypeHandle = inspector.TypeReferences[elementTypeIndex].Data.KlassIndex;
|
||||
var elementType = inspector.TypeDefinitions[elementTypeHandle];
|
||||
typeEnum = inspector.TypeReferences[elementType.ByValTypeIndex].Type;
|
||||
}
|
||||
|
||||
136
Il2CppInspector.Redux.CLI/CliClient.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System.Threading.Channels;
|
||||
using Il2CppInspector.Redux.FrontendCore;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI;
|
||||
|
||||
public class CliClient : IDisposable
|
||||
{
|
||||
public bool ImportCompleted { get; private set; }
|
||||
|
||||
private volatile int _finishedLoadingCount = 0;
|
||||
|
||||
private readonly HubConnection _connection;
|
||||
private readonly List<IDisposable> _commandListeners = [];
|
||||
|
||||
private Channel<string>? _logMessageChannel;
|
||||
|
||||
public CliClient(HubConnection connection)
|
||||
{
|
||||
_connection = connection;
|
||||
|
||||
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowLogMessage), ShowLogMessage));
|
||||
_commandListeners.Add(_connection.On(nameof(UiClient.BeginLoading), BeginLoading));
|
||||
_commandListeners.Add(_connection.On(nameof(UiClient.FinishLoading), FinishLoading));
|
||||
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowInfoToast), ShowInfoToast));
|
||||
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowSuccessToast), ShowSuccessToast));
|
||||
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowErrorToast), ShowErrorToast));
|
||||
_commandListeners.Add(_connection.On(nameof(UiClient.OnImportCompleted), OnImportCompleted));
|
||||
}
|
||||
|
||||
public async ValueTask OnUiLaunched(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _connection.InvokeAsync(nameof(Il2CppHub.OnUiLaunched), cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask SubmitInputFiles(List<string> inputFiles, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _connection.InvokeAsync(nameof(Il2CppHub.SubmitInputFiles), inputFiles, cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask QueueExport(string exportTypeId, string outputDirectory, Dictionary<string, string> settings,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _connection.InvokeAsync(nameof(Il2CppHub.QueueExport), exportTypeId, outputDirectory, settings, cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask StartExport(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _connection.InvokeAsync(nameof(Il2CppHub.StartExport), cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask<List<string>> GetPotentialUnityVersions(CancellationToken cancellationToken = default)
|
||||
=> await _connection.InvokeAsync<List<string>>(nameof(Il2CppHub.GetPotentialUnityVersions), cancellationToken);
|
||||
|
||||
public async ValueTask ExportIl2CppFiles(string outputDirectory, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _connection.InvokeAsync(nameof(Il2CppHub.ExportIl2CppFiles), outputDirectory, cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask<string> GetInspectorVersion(CancellationToken cancellationToken = default)
|
||||
=> await _connection.InvokeAsync<string>(nameof(Il2CppHub.GetInspectorVersion), cancellationToken);
|
||||
|
||||
public async ValueTask WaitForLoadingToFinishAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
var currentLoadingCount = _finishedLoadingCount;
|
||||
while (_finishedLoadingCount == currentLoadingCount)
|
||||
await Task.Delay(10, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ShowLogMessage(string message)
|
||||
{
|
||||
if (_logMessageChannel == null)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[white bold]{message}[/]");
|
||||
return;
|
||||
}
|
||||
|
||||
await _logMessageChannel.Writer.WriteAsync(message);
|
||||
}
|
||||
|
||||
private void BeginLoading()
|
||||
{
|
||||
_logMessageChannel = Channel.CreateUnbounded<string>(new UnboundedChannelOptions
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = true,
|
||||
AllowSynchronousContinuations = true
|
||||
});
|
||||
|
||||
AnsiConsole.Status()
|
||||
.Spinner(Spinner.Known.Triangle)
|
||||
.StartAsync("Loading", async ctx =>
|
||||
{
|
||||
await foreach (var newLogMessage in _logMessageChannel.Reader.ReadAllAsync())
|
||||
{
|
||||
ctx.Status(newLogMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void FinishLoading()
|
||||
{
|
||||
_logMessageChannel?.Writer.Complete();
|
||||
Interlocked.Increment(ref _finishedLoadingCount);
|
||||
}
|
||||
|
||||
private static void ShowInfoToast(string message)
|
||||
{
|
||||
AnsiConsole.MarkupLineInterpolated($"[bold white]INFO: {message}[/]");
|
||||
}
|
||||
|
||||
private static void ShowSuccessToast(string message)
|
||||
{
|
||||
AnsiConsole.MarkupLineInterpolated($"[bold][green]SUCCESS: [/] [white]{message}[/][/]");
|
||||
}
|
||||
|
||||
private static void ShowErrorToast(string message)
|
||||
{
|
||||
AnsiConsole.MarkupLineInterpolated($"[bold][red]ERROR: [/] [white]{message}[/][/]");
|
||||
}
|
||||
|
||||
private void OnImportCompleted()
|
||||
{
|
||||
ImportCompleted = true;
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
|
||||
foreach (var listener in _commandListeners)
|
||||
listener.Dispose();
|
||||
}
|
||||
}
|
||||
38
Il2CppInspector.Redux.CLI/Commands/BaseCommand.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Il2CppInspector.Redux.FrontendCore;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI.Commands;
|
||||
|
||||
internal abstract class BaseCommand<T>(PortProvider portProvider) : AsyncCommand<T> where T : CommandSettings
|
||||
{
|
||||
private const string HubPath = "/il2cpp"; // TODO: Make this into a shared constant
|
||||
|
||||
private readonly int _serverPort = portProvider.Port;
|
||||
|
||||
protected abstract Task<int> ExecuteAsync(CliClient client, T settings);
|
||||
|
||||
public override async Task<int> ExecuteAsync(CommandContext context, T settings)
|
||||
{
|
||||
var connection = new HubConnectionBuilder().WithUrl($"http://localhost:{_serverPort}{HubPath}")
|
||||
.AddJsonProtocol(options =>
|
||||
{
|
||||
options.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0,
|
||||
FrontendCoreJsonSerializerContext.Default);
|
||||
})
|
||||
.Build();
|
||||
|
||||
await connection.StartAsync();
|
||||
|
||||
int result;
|
||||
using (var client = new CliClient(connection))
|
||||
{
|
||||
await client.OnUiLaunched();
|
||||
result = await ExecuteAsync(client, settings);
|
||||
}
|
||||
|
||||
await connection.StopAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
17
Il2CppInspector.Redux.CLI/Commands/InteractiveCommand.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI.Commands;
|
||||
|
||||
internal class InteractiveCommand(PortProvider portProvider) : BaseCommand<InteractiveCommand.Options>(portProvider)
|
||||
{
|
||||
public class Options : CommandSettings;
|
||||
|
||||
protected override async Task<int> ExecuteAsync(CliClient client, Options settings)
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
await AnsiConsole.AskAsync<string>("meow?");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
21
Il2CppInspector.Redux.CLI/Commands/ManualCommand.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI.Commands;
|
||||
|
||||
internal abstract class ManualCommand<T>(PortProvider portProvider) : BaseCommand<T>(portProvider) where T : ManualCommandOptions
|
||||
{
|
||||
public override ValidationResult Validate(CommandContext context, T settings)
|
||||
{
|
||||
foreach (var inputPath in settings.InputPaths)
|
||||
{
|
||||
if (!Path.Exists(inputPath))
|
||||
return ValidationResult.Error($"Provided input path {inputPath} does not exit.");
|
||||
}
|
||||
|
||||
if (File.Exists(settings.OutputPath))
|
||||
return ValidationResult.Error("Provided output path already exists as a file.");
|
||||
|
||||
return ValidationResult.Success();
|
||||
}
|
||||
}
|
||||
15
Il2CppInspector.Redux.CLI/Commands/ManualCommandOptions.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.ComponentModel;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI.Commands;
|
||||
|
||||
internal class ManualCommandOptions : CommandSettings
|
||||
{
|
||||
[CommandArgument(0, "<InputPath>")]
|
||||
[Description("Paths to the input files. Will be subsequently loaded until binary and metadata were found.")]
|
||||
public string[] InputPaths { get; init; } = [];
|
||||
|
||||
[CommandOption("-o|--output")]
|
||||
[Description("Path to the output folder")]
|
||||
public string OutputPath { get; init; } = "";
|
||||
}
|
||||
172
Il2CppInspector.Redux.CLI/Commands/ProcessCommand.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Il2CppInspector.Cpp;
|
||||
using Il2CppInspector.Redux.FrontendCore.Outputs;
|
||||
using Spectre.Console;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI.Commands;
|
||||
|
||||
internal class ProcessCommand(PortProvider portProvider) : ManualCommand<ProcessCommand.Option>(portProvider)
|
||||
{
|
||||
// NOTE: There might be a better option than replicating all available flags here (and in the TS UI).
|
||||
// Investigate this in the future.
|
||||
|
||||
public class Option : ManualCommandOptions
|
||||
{
|
||||
// C++ Scaffolding
|
||||
[CommandOption("--output-cpp-scaffolding")]
|
||||
public bool CppScaffolding { get; init; } = false;
|
||||
|
||||
[CommandOption("--unity-version")]
|
||||
public string? UnityVersion { get; init; }
|
||||
|
||||
[CommandOption("--compiler-type")]
|
||||
public CppCompilerType CompilerType { get; init; } = CppCompilerType.GCC;
|
||||
|
||||
// C# stub
|
||||
[CommandOption("-s|--output-csharp-stub")]
|
||||
public bool CSharpStubs { get; init; } = false;
|
||||
|
||||
[CommandOption("--layout")]
|
||||
public CSharpLayout Layout { get; init; } = CSharpLayout.SingleFile;
|
||||
|
||||
[CommandOption("--flatten-hierarchy")]
|
||||
public bool FlattenHierarchy { get; init; } = false;
|
||||
|
||||
[CommandOption("--sorting-mode")]
|
||||
public TypeSortingMode SortingMode { get; init; } = TypeSortingMode.Alphabetical;
|
||||
|
||||
[CommandOption("--suppress-metadata")]
|
||||
public bool SuppressMetadata { get; init; } = false;
|
||||
|
||||
[CommandOption("--compilable")]
|
||||
public bool MustCompile { get; init; } = false;
|
||||
|
||||
[CommandOption("--separate-assembly-attributes")]
|
||||
public bool SeparateAssemblyAttributes { get; init; } = true;
|
||||
|
||||
// Disassembler metadata
|
||||
[CommandOption("-m|--output-disassembler-metadata")]
|
||||
public bool DisassemblerMetadata { get; init; } = false;
|
||||
|
||||
[CommandOption("--disassembler")]
|
||||
public DisassemblerType Disassembler { get; init; } = DisassemblerType.IDA;
|
||||
|
||||
// Dummy DLL output
|
||||
[CommandOption("-d|--output-dummy-dlls")]
|
||||
public bool DummyDlls { get; init; } = false;
|
||||
|
||||
// Visual Studio solution
|
||||
[CommandOption("--output-vs-solution")]
|
||||
public bool VsSolution { get; init; } = false;
|
||||
|
||||
[CommandOption("--unity-path")]
|
||||
public string? UnityPath { get; init; }
|
||||
|
||||
[CommandOption("--unity-assemblies-path")]
|
||||
public string? UnityAssembliesPath { get; init; }
|
||||
|
||||
[CommandOption("--extract-il2cpp-files")]
|
||||
public string? ExtractIl2CppFilesPath { get; init; }
|
||||
}
|
||||
|
||||
protected override async Task<int> ExecuteAsync(CliClient client, Option settings)
|
||||
{
|
||||
var inspectorVersion = await client.GetInspectorVersion();
|
||||
AnsiConsole.MarkupLineInterpolated($"Using inspector [gray]{inspectorVersion}[/]");
|
||||
|
||||
await client.SubmitInputFiles(settings.InputPaths.ToList());
|
||||
await client.WaitForLoadingToFinishAsync();
|
||||
if (!client.ImportCompleted)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[bold][red]FAILED[/] to load IL2CPP data from the given inputs.[/]");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (settings.ExtractIl2CppFilesPath != null)
|
||||
{
|
||||
await client.ExportIl2CppFiles(settings.ExtractIl2CppFilesPath);
|
||||
await client.WaitForLoadingToFinishAsync();
|
||||
}
|
||||
|
||||
var unityVersions = await client.GetPotentialUnityVersions();
|
||||
|
||||
if (settings.CppScaffolding)
|
||||
{
|
||||
var directory = Path.Join(settings.OutputPath, "cpp");
|
||||
await client.QueueExport(CppScaffoldingOutput.Id, directory, new Dictionary<string, string>
|
||||
{
|
||||
["unityversion"] = settings.UnityVersion ?? unityVersions.First(),
|
||||
["compilertype"] = settings.CompilerType.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.CSharpStubs)
|
||||
{
|
||||
var directory = Path.Join(settings.OutputPath, "cs");
|
||||
await client.QueueExport(CSharpStubOutput.Id, directory, new Dictionary<string, string>
|
||||
{
|
||||
["layout"] = settings.Layout.ToString(),
|
||||
["flattenhierarchy"] = settings.FlattenHierarchy.ToString(),
|
||||
["sortingmode"] = settings.SortingMode.ToString(),
|
||||
["suppressmetadata"] = settings.SuppressMetadata.ToString(),
|
||||
["mustcompile"] = settings.MustCompile.ToString(),
|
||||
["separateassemblyattributes"] = settings.SeparateAssemblyAttributes.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.DisassemblerMetadata)
|
||||
{
|
||||
await client.QueueExport(DisassemblerMetadataOutput.Id, settings.OutputPath,
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
["disassembler"] = settings.Disassembler.ToString(),
|
||||
["unityversion"] = settings.UnityVersion ?? unityVersions.First()
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.DummyDlls)
|
||||
{
|
||||
var directory = Path.Join(settings.OutputPath, "dll");
|
||||
await client.QueueExport(DummyDllOutput.Id, directory, new Dictionary<string, string>
|
||||
{
|
||||
["suppressmetadata"] = settings.SuppressMetadata.ToString()
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.VsSolution)
|
||||
{
|
||||
var directory = Path.Join(settings.OutputPath, "vs");
|
||||
await client.QueueExport(VsSolutionOutput.Id, directory, new Dictionary<string, string>
|
||||
{
|
||||
["unitypath"] = settings.UnityPath ?? "",
|
||||
["unityassembliespath"] = settings.UnityAssembliesPath ?? ""
|
||||
});
|
||||
}
|
||||
|
||||
await client.StartExport();
|
||||
await client.WaitForLoadingToFinishAsync();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public override ValidationResult Validate(CommandContext context, Option settings)
|
||||
{
|
||||
if (settings.UnityPath != null && !Path.Exists(settings.UnityPath))
|
||||
return ValidationResult.Error($"Provided Unity path {settings.UnityPath} does not exist.");
|
||||
|
||||
if (settings.UnityAssembliesPath != null && !Path.Exists(settings.UnityAssembliesPath))
|
||||
return ValidationResult.Error($"Provided Unity assemblies path {settings.UnityAssembliesPath} does not exist.");
|
||||
|
||||
if (settings.ExtractIl2CppFilesPath != null && File.Exists(settings.ExtractIl2CppFilesPath))
|
||||
return ValidationResult.Error(
|
||||
$"Provided extracted IL2CPP files path {settings.ExtractIl2CppFilesPath} already exists as a file.");
|
||||
|
||||
if (settings is
|
||||
{
|
||||
CppScaffolding: false, CSharpStubs: false, DisassemblerMetadata: false, DummyDlls: false,
|
||||
VsSolution: false
|
||||
})
|
||||
return ValidationResult.Error("At least one output format must be specified.");
|
||||
|
||||
return base.Validate(context, settings);
|
||||
}
|
||||
}
|
||||
21
Il2CppInspector.Redux.CLI/Il2CppInspector.Redux.CLI.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
<PublishAot>false</PublishAot>
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.7" />
|
||||
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Il2CppInspector.Redux.FrontendCore\Il2CppInspector.Redux.FrontendCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
6
Il2CppInspector.Redux.CLI/PortProvider.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace Il2CppInspector.Redux.CLI;
|
||||
|
||||
internal sealed class PortProvider(int port)
|
||||
{
|
||||
public int Port => port;
|
||||
}
|
||||
46
Il2CppInspector.Redux.CLI/Program.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Il2CppInspector.Redux.CLI;
|
||||
using Il2CppInspector.Redux.CLI.Commands;
|
||||
using Il2CppInspector.Redux.FrontendCore;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
var builder = WebApplication.CreateSlimBuilder();
|
||||
|
||||
builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
{
|
||||
options.SerializerOptions.TypeInfoResolverChain.Insert(0, FrontendCoreJsonSerializerContext.Default);
|
||||
});
|
||||
|
||||
builder.Services.Configure<JsonHubProtocolOptions>(options =>
|
||||
{
|
||||
options.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, FrontendCoreJsonSerializerContext.Default);
|
||||
});
|
||||
|
||||
builder.Services.AddFrontendCore();
|
||||
builder.Logging.ClearProviders();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseCors();
|
||||
|
||||
app.MapFrontendCore();
|
||||
|
||||
await app.StartAsync();
|
||||
|
||||
var serverUrl = app.Urls.First();
|
||||
var port = new Uri(serverUrl).Port;
|
||||
|
||||
var commandServiceProvider = new ServiceCollection();
|
||||
commandServiceProvider.AddSingleton(new PortProvider(port));
|
||||
|
||||
var commandTypeRegistrar = new ServiceTypeRegistrar(commandServiceProvider);
|
||||
var consoleApp = new CommandApp<InteractiveCommand>(commandTypeRegistrar);
|
||||
|
||||
consoleApp.Configure(config =>
|
||||
{
|
||||
config.AddCommand<ProcessCommand>("process")
|
||||
.WithDescription("Processes the provided input data into one or more output formats.");
|
||||
});
|
||||
|
||||
await consoleApp.RunAsync(args);
|
||||
await app.StopAsync();
|
||||
23
Il2CppInspector.Redux.CLI/Properties/launchSettings.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"profiles": {
|
||||
"WSL": {
|
||||
"commandName": "WSL2",
|
||||
"launchUrl": "http://localhost:5118/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||
"ASPNETCORE_URLS": "http://localhost:5118"
|
||||
},
|
||||
"distributionName": ""
|
||||
},
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "process -d --disassembler ghidra M:\\Downloads\\Reversing\\NotYetAnalyzedAPKs\\pokemon_friends\\libil2cpp.so M:\\Downloads\\Reversing\\NotYetAnalyzedAPKs\\pokemon_friends\\global-metadata.dat",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
},
|
||||
"dotnetRunMessages": true,
|
||||
"applicationUrl": "http://localhost:5118"
|
||||
}
|
||||
},
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json"
|
||||
}
|
||||
30
Il2CppInspector.Redux.CLI/ServiceTypeRegistrar.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI;
|
||||
|
||||
public class ServiceTypeRegistrar(IServiceCollection serviceCollection) : ITypeRegistrar
|
||||
{
|
||||
private readonly IServiceCollection _serviceCollection = serviceCollection;
|
||||
private ServiceTypeResolver? _resolver;
|
||||
|
||||
public void Register(Type service, Type implementation)
|
||||
{
|
||||
_serviceCollection.AddSingleton(service, implementation);
|
||||
}
|
||||
|
||||
public void RegisterInstance(Type service, object implementation)
|
||||
{
|
||||
_serviceCollection.AddSingleton(service, implementation);
|
||||
}
|
||||
|
||||
public void RegisterLazy(Type service, Func<object> factory)
|
||||
{
|
||||
_serviceCollection.AddSingleton(service, _ => factory());
|
||||
}
|
||||
|
||||
public ITypeResolver Build()
|
||||
{
|
||||
_resolver ??= new ServiceTypeResolver(_serviceCollection.BuildServiceProvider());
|
||||
return _resolver;
|
||||
}
|
||||
}
|
||||
13
Il2CppInspector.Redux.CLI/ServiceTypeResolver.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Spectre.Console.Cli;
|
||||
|
||||
namespace Il2CppInspector.Redux.CLI;
|
||||
|
||||
public class ServiceTypeResolver(IServiceProvider serviceProvider) : ITypeResolver
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider = serviceProvider;
|
||||
|
||||
public object? Resolve(Type? type)
|
||||
=> type == null
|
||||
? null
|
||||
: _serviceProvider.GetService(type);
|
||||
}
|
||||
8
Il2CppInspector.Redux.CLI/appsettings.Development.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Il2CppInspector.Redux.CLI/appsettings.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>");
|
||||
}
|
||||
}
|
||||
10
Il2CppInspector.Redux.GUI.UI/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
4
Il2CppInspector.Redux.GUI.UI/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
|
||||
"tabWidth": 4
|
||||
}
|
||||
17
Il2CppInspector.Redux.GUI.UI/components.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "https://next.shadcn-svelte.com/schema.json",
|
||||
"style": "new-york",
|
||||
"tailwind": {
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src\\app.css",
|
||||
"baseColor": "stone"
|
||||
},
|
||||
"aliases": {
|
||||
"components": "$lib/components",
|
||||
"utils": "$lib/utils",
|
||||
"ui": "$lib/components/ui",
|
||||
"hooks": "$lib/hooks"
|
||||
},
|
||||
"typescript": true,
|
||||
"registry": "https://next.shadcn-svelte.com/registry"
|
||||
}
|
||||
46
Il2CppInspector.Redux.GUI.UI/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "il2cppinspectorredux",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@microsoft/signalr": "^8.0.7",
|
||||
"@tauri-apps/api": "^2",
|
||||
"@tauri-apps/plugin-dialog": "~2",
|
||||
"@tauri-apps/plugin-opener": "^2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-static": "^3.0.6",
|
||||
"@sveltejs/kit": "^2.9.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tauri-apps/cli": "^2",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"bits-ui": "1.0.0-next.78",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-svelte": "^0.473.0",
|
||||
"mode-watcher": "^0.5.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.10",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"svelte-sonner": "^0.3.28",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"tailwind-variants": "^0.3.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"typescript": "~5.6.2",
|
||||
"vite": "^6.0.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b"
|
||||
}
|
||||
2349
Il2CppInspector.Redux.GUI.UI/pnpm-lock.yaml
generated
Normal file
6
Il2CppInspector.Redux.GUI.UI/postcss.config.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
7
Il2CppInspector.Redux.GUI.UI/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
5345
Il2CppInspector.Redux.GUI.UI/src-tauri/Cargo.lock
generated
Normal file
26
Il2CppInspector.Redux.GUI.UI/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "il2cppinspectorredux"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[lib]
|
||||
# The `_lib` suffix may seem redundant but it is necessary
|
||||
# to make the lib name unique and wouldn't conflict with the bin name.
|
||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||
name = "il2cppinspectorredux_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = [] }
|
||||
tauri-plugin-opener = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri-plugin-dialog = "2"
|
||||
|
||||
3
Il2CppInspector.Redux.GUI.UI/src-tauri/build.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Capability for the main window",
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"core:default",
|
||||
"opener:default",
|
||||
"dialog:default"
|
||||
]
|
||||
}
|
||||
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 974 B |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 3.8 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 7.6 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 903 B |
|
After Width: | Height: | Size: 8.4 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/icon.icns
Normal file
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 85 KiB |
BIN
Il2CppInspector.Redux.GUI.UI/src-tauri/icons/icon.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
20
Il2CppInspector.Redux.GUI.UI/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
#[tauri::command]
|
||||
fn get_signalr_url() -> String {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
if args.len() < 2 {
|
||||
return String::from("");
|
||||
}
|
||||
|
||||
return args[1].clone();
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![get_signalr_url])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
6
Il2CppInspector.Redux.GUI.UI/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
il2cppinspectorredux_lib::run()
|
||||
}
|
||||
35
Il2CppInspector.Redux.GUI.UI/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Il2CppInspectorRedux",
|
||||
"version": "0.1.0",
|
||||
"identifier": "xyz.lukefz.il2cppinspectorredux",
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"frontendDist": "../build"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Il2CppInspectorRedux",
|
||||
"width": 800,
|
||||
"height": 600
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "all",
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
]
|
||||
}
|
||||
}
|
||||
66
Il2CppInspector.Redux.GUI.UI/src/app.css
Normal file
@@ -0,0 +1,66 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--destructive: 0 72.22% 50.59%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--input: 240 5.9% 90%;
|
||||
--ring: 240 5.9% 10%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--header-height: 60px;
|
||||
--footer-height: 30px;
|
||||
--main-height: calc(100vh - var(--header-height) - var(--footer-height));
|
||||
}
|
||||
}
|
||||