28 Commits

Author SHA1 Message Date
LukeFZ
d43157cab5 29/31.2 don't actually exist, this logic is not needed 2024-11-14 14:29:06 +01:00
LukeFZ
97cbf243b5 Merge refactored-versioning into master 2024-11-14 14:27:14 +01:00
LukeFZ
e6307d009c workflow: remove double .zip in CLI artifact name 2024-11-14 08:03:22 +01:00
LukeFZ
2bd24b9c12 workflow: tweak caching and fix gui compilation 2024-11-14 07:53:14 +01:00
LukeFZ
226d0dfd1d workflow: only restore packages for projects that are being built 2024-11-14 07:47:55 +01:00
LukeFZ
5cd94784f5 Update actions workflow to produce cross-platform CLI binaries, update readme to reflect .net 9 changes 2024-11-14 07:42:27 +01:00
LukeFZ
bcbf4f47e2 Fix NRE in Assembly ctor on < v24.2 2024-11-09 15:44:11 +01:00
LukeFZ
3982e5fd99 Make field offset reading use TryMapVATR to reduce exceptions 2024-11-09 15:43:39 +01:00
LukeFZ
e0e8d052ea make TryMapVATR overrideable and implement it for ELFs 2024-11-09 15:43:22 +01:00
LukeFZ
08431b774a Fix metadata usage issues caused by it being a value type now 2024-11-08 23:33:13 +01:00
Luke
5715760e8b Overhaul disassembler script + add Binary Ninja target (#12)
* Overhaul diassembler scripts:
- No longer defines top level functions
- Split into three classes: StatusHandler (like before), DisassemblerInterface (for interfacing with the used program API), ScriptContext (for definiting general functions that use the disassembler interface)
- Add type annotations to all class methods and remove 2.7 compatibility stuff (Ghidra now supports Python 3 so this is unnecessary anymore)
- Disassembler backends are now responsible for launching metadata/script processing, to better support disassembler differences
- String handling is back in the base ScriptContext class, disassembler interfaces opt into the fake string segment creation and fall back to the old method if it isn't supported

* Add Binary Ninja disassembler script backend
This uses the new backend-controlled execution to launch metadata processing on a background thread to keep the ui responsive

* make binary ninja script use own _BINARYNINJA_ define and add define helpers to header

* Update README to account for new script and binary ninja backend

* implement fake string segment functions for binary ninja but don't advertise support

* also cache API function types in binary ninja backend

* fix ida script and disable folders again
2024-11-08 23:31:40 +01:00
LukeFZ
792268f52f Disable plugin loading for now 2024-11-07 12:51:58 +01:00
LukeFZ
8895979388 also read UnresolvedVirtualCallCount on regular v31 2024-10-27 11:24:14 +01:00
LukeFZ
daa80bcffe support loading PEs without an export table 2024-10-26 19:56:49 +02:00
LukeFZ
d59f67216a support auto-recovering type indices from type handles
fixes loading of memory-dumped v29+ libraries since those replacee their class indices on load with a pointer to the corresponding type
2024-10-13 21:54:59 +02:00
LukeFZ
095bfa16e8 remove loading of packed dlls - this was a very unsafe feature 2024-10-13 21:54:06 +02:00
LukeFZ
e67f856299 also remove 29.2 from the readme 2024-09-01 02:02:13 +02:00
LukeFZ
7632ff2283 29/31.2 was a psyop 2024-09-01 02:01:34 +02:00
LukeFZ
1a12cf5081 tweak .1 condition (again) 2024-08-30 20:23:17 +02:00
LukeFZ
e6c51b47d6 rework code registration offsetting a bit and add second 29/31.1 condition 2024-08-24 22:55:35 +02:00
LukeFZ
5d827fe881 fix metadata usage validity checks 2024-08-18 22:40:29 +02:00
LukeFZ
a7081ccfa9 move 29/31.1/.2 to use tags (-2022,-2023) instead of minor versions 2024-08-18 22:40:14 +02:00
LukeFZ
43d7433e12 Rework metadata struct loading to use new struct versioning 2024-08-17 13:52:09 +02:00
LukeFZ
6c59434984 rename serialization methods and add BinaryObjectStreamReader for interop 2024-08-14 01:00:32 +02:00
LukeFZ
2d3b186b4d add new struct definitions 2024-08-13 18:34:22 +02:00
LukeFZ
23e873280d migrate versioning to StructVersion class, add handling/detection for 29.2/31.2 2024-08-13 15:00:20 +02:00
LukeFZ
22ecdc3612 add VersionedSerialization + source generator 2024-08-13 04:27:23 +02:00
LukeFZ
30c019c4ef Bump projects to .net 9 and update nugets 2024-08-13 04:23:49 +02:00
188 changed files with 955 additions and 13437 deletions

View File

@@ -3,113 +3,7 @@ name: Il2CppInspectorRedux Build
on: [push, workflow_dispatch]
jobs:
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:
build-gui:
runs-on: windows-latest
steps:
@@ -138,10 +32,10 @@ jobs:
- name: Upload GUI Artifact
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.Old.GUI
name: Il2CppInspectorRedux.GUI
path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish
build-old-cli:
build-cli:
runs-on: ubuntu-latest
strategy:
matrix:
@@ -179,5 +73,5 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.Old.CLI-${{ matrix.rid }}
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }}

3
.gitignore vendored
View File

@@ -268,6 +268,3 @@ paket-files/
/Il2CppInspector.sln.DotSettings
/Il2CppTests/samples
/Il2CppTests/TestCpp
# for dotnet-tools.json
.config

View File

@@ -5,14 +5,16 @@
All rights reserved.
*/
using System.Diagnostics;
using System;
using System.Collections.Generic;
using System.Linq;
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
{
@@ -31,7 +33,7 @@ public class CppDeclarationGenerator
// 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 readonly CppCompilerType InheritanceStyle;
public CppCompilerType InheritanceStyle;
public CppDeclarationGenerator(AppModel appModel) {
this.appModel = appModel;
@@ -44,8 +46,7 @@ public class CppDeclarationGenerator
}
// C type declaration used to name variables of the given C# type
private static readonly Dictionary<string, string> primitiveTypeMap = new()
{
private static Dictionary<string, string> primitiveTypeMap = new Dictionary<string, string> {
["Boolean"] = "bool",
["Byte"] = "uint8_t",
["SByte"] = "int8_t",
@@ -67,28 +68,24 @@ public class CppDeclarationGenerator
if (ti.IsByRef || ti.IsPointer) {
return AsCType(ti.ElementType).AsPointer(WordSize);
}
if (ti.IsValueType) {
if (ti.IsPrimitive && primitiveTypeMap.TryGetValue(ti.Name, out var value)) {
return types.GetType(value);
if (ti.IsPrimitive && primitiveTypeMap.ContainsKey(ti.Name)) {
return types.GetType(primitiveTypeMap[ti.Name]);
}
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
@@ -99,123 +96,37 @@ public class CppDeclarationGenerator
*/
// A cache of field structures that have already been generated, to eliminate duplicate definitions
private readonly HashSet<TypeInfo> _visitedFieldStructs = [];
private readonly HashSet<TypeInfo> VisitedFieldStructs = new HashSet<TypeInfo>();
// A queue of field structures that need to be generated.
private readonly List<TypeInfo> _todoFieldStructs = [];
private readonly HashSet<TypeInfo> _requiredForwardDefinitionsForFields = [];
private readonly HashSet<TypeInfo> _currentVisitedFieldStructs = [];
private readonly HashSet<TypeInfo> _currentTodoFieldStructs = [];
private readonly HashSet<TypeInfo> _currentRequiredForwardDefinitions = [];
private readonly HashSet<TypeInfo> _currentlyVisitingFieldStructs = [];
private class CircularReferenceException(TypeInfo circularType, TypeInfo parentType) : Exception("Circular reference detected")
{
public TypeInfo CircularReferencedType { get; } = circularType;
public TypeInfo ParentType { get; } = parentType;
}
private readonly List<TypeInfo> TodoFieldStructs = new List<TypeInfo>();
// 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))
private void VisitFieldStructs(TypeInfo ti) {
if (VisitedFieldStructs.Contains(ti))
return;
if (ti.IsByRef || ti.ContainsGenericParameters)
return;
_currentVisitedFieldStructs.Add(ti);
_currentlyVisitingFieldStructs.Add(ti);
VisitedFieldStructs.Add(ti);
if (ti.BaseType != null)
VisitFieldStructsInner(ti.BaseType);
VisitFieldStructs(ti.BaseType);
if (ti.IsArray)
VisitFieldStructsInner(ti.ElementType);
VisitFieldStructs(ti.ElementType);
if (ti.IsEnum)
VisitFieldStructsInner(ti.GetEnumUnderlyingType());
VisitFieldStructs(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);
}
VisitFieldStructs(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);
}
}
}
}
VisitFieldStructs(fi.FieldType.ElementType);
}
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);
TodoFieldStructs.Add(ti);
}
// Generate the fields for the base class of all objects (Il2CppObject)
@@ -317,14 +228,6 @@ public class CppDeclarationGenerator
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);
}
@@ -350,8 +253,8 @@ public class CppDeclarationGenerator
// "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));
@@ -361,7 +264,7 @@ public class CppDeclarationGenerator
structs.Add((ti, null, objectOrArrayType, fieldsType));
}
}
_todoFieldStructs.Clear();
TodoFieldStructs.Clear();
return structs;
}
#endregion
@@ -369,7 +272,7 @@ public class CppDeclarationGenerator
#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();
private readonly Dictionary<TypeInfo, TypeInfo> ConcreteImplementations = new Dictionary<TypeInfo, TypeInfo>();
/// <summary>
/// VTables for abstract types have "null" in place of abstract functions.
/// This function searches for concrete implementations so that we can properly
@@ -381,9 +284,8 @@ public class CppDeclarationGenerator
continue;
var baseType = ti.BaseType;
while (baseType != null) {
if (baseType.IsAbstract)
_concreteImplementations.TryAdd(baseType, ti);
if (baseType.IsAbstract && !ConcreteImplementations.ContainsKey(baseType))
ConcreteImplementations[baseType] = ti;
baseType = baseType.BaseType;
}
}
@@ -405,42 +307,37 @@ public class CppDeclarationGenerator
* 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.TryGetValue(ti, out var implementation)) {
var impl = implementation.GetVTable();
if (ti.IsAbstract && ConcreteImplementations.ContainsKey(ti)) {
res = (MethodBase[])res.Clone();
for (int i = 0; i < res.Length; i++)
{
res[i] ??= impl[i];
MethodBase[] impl = ConcreteImplementations[ti].GetVTable();
for (int i = 0; i < res.Length; i++) {
if (res[i] == null)
res[i] = impl[i];
}
}
return res;
}
private readonly HashSet<TypeInfo> _visitedTypes = [];
private readonly List<TypeInfo> _todoTypeStructs = [];
private readonly HashSet<TypeInfo> VisitedTypes = new HashSet<TypeInfo>();
private readonly List<TypeInfo> TodoTypeStructs = new List<TypeInfo>();
/// <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);
_visitedTypes.Add(ti);
if (ti.IsArray || ti.HasElementType)
{
if (ti.IsArray) {
IncludeType(ti.ElementType);
}
else if (ti.IsEnum)
{
} else if (ti.HasElementType) {
IncludeType(ti.ElementType);
} else if (ti.IsEnum) {
IncludeType(ti.GetEnumUnderlyingType());
}
@@ -457,12 +354,10 @@ public class CppDeclarationGenerator
IncludeType(fi.FieldType);
foreach (var mi in GetFilledVTable(ti))
{
if (mi is { ContainsGenericParameters: false })
if (mi != null && !mi.ContainsGenericParameters)
IncludeMethod(mi);
}
_todoTypeStructs.Add(ti);
TodoTypeStructs.Add(ti);
}
// Generate the C structure for virtual function calls in a given type (the VTable)
@@ -542,38 +437,17 @@ public class CppDeclarationGenerator
/// <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
@@ -740,3 +614,4 @@ public class CppDeclarationGenerator
public CppNamespace.Namer<MethodBase> GlobalNamer { get; private set; }
#endregion
}
}

View File

@@ -4,6 +4,8 @@
All rights reserved.
*/
using System;
namespace Il2CppInspector.Cpp
{
// A field in a C++ type

View File

@@ -5,6 +5,9 @@
All rights reserved.
*/
using System;
using System.Collections.Generic;
namespace Il2CppInspector.Cpp
{
/// <summary>
@@ -62,9 +65,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
if (names.TryGetValue(t, out var name))
string name;
if (names.TryGetValue(t, out 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,

View File

@@ -456,14 +456,4 @@ namespace Il2CppInspector.Cpp
return sb.ToString();
}
}
public class CppForwardDefinitionType : CppType
{
public CppForwardDefinitionType(string name) : base(name)
{
}
public override string ToString(string format = "") => $"struct {Name};";
}
}

View File

@@ -4,11 +4,15 @@
All rights reserved.
*/
using Il2CppInspector.Cpp.UnityHeaders;
using System;
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
{
@@ -19,7 +23,7 @@ namespace Il2CppInspector.Cpp
public Dictionary<string, CppType> Types { get; }
// All of the literal typedef aliases
public Dictionary<string, CppType> TypedefAliases { get; } = [];
public Dictionary<string, CppType> TypedefAliases { get; } = new Dictionary<string, CppType>();
public CppType this[string s] => Types.ContainsKey(s)? Types[s] :
TypedefAliases.ContainsKey(s)? TypedefAliases[s].AsAlias(s) : null;
@@ -30,8 +34,7 @@ namespace Il2CppInspector.Cpp
// Architecture width in bits (32/64) - to determine pointer sizes
public int WordSize { get; }
private Dictionary<string, ComplexValueType> complexTypeMap = new()
{
private Dictionary<string, ComplexValueType> complexTypeMap = new Dictionary<string, ComplexValueType> {
["struct"] = ComplexValueType.Struct,
["union"] = ComplexValueType.Union,
["enum"] = ComplexValueType.Enum
@@ -41,23 +44,22 @@ namespace Il2CppInspector.Cpp
private string currentGroup = string.Empty;
public void SetGroup(string group) => currentGroup = group;
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)
];
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)
};
public CppTypeCollection(int wordSize) {
if (wordSize != 32 && wordSize != 64)
@@ -536,18 +538,15 @@ 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);
@@ -555,13 +554,9 @@ 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;
}
@@ -590,17 +585,11 @@ 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.Keys)
{
declGen?.TypeNamespace.TryReserveName(type);
declGen?.GlobalsNamespace.TryReserveName(type);
}
foreach (var type in cppTypes.Types.Values)
declGen?.TypeNamespace.TryReserveName(type.Name);
foreach (var typedef in cppTypes.TypedefAliases.Keys)
{
declGen?.TypeNamespace.TryReserveName(typedef);
declGen?.GlobalsNamespace.TryReserveName(typedef);
}
foreach (var typedef in cppTypes.TypedefAliases.Values)
declGen?.GlobalsNamespace.TryReserveName(typedef.Name);
cppTypes.SetGroup("");

View File

@@ -5,14 +5,13 @@
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
@@ -141,7 +140,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()) {
AnsiConsole.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project");
Console.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();
@@ -162,7 +161,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
if (apiMatches.Any()) {
// Intersect all API ranges with all header ranges to produce final list of possible ranges
AnsiConsole.WriteLine("IL2CPP API discovery was successful");
Console.WriteLine("IL2CPP API discovery was successful");
return typeHeaders.SelectMany(
t => apiMatches.Where(a => t.VersionRange.Intersect(a.VersionRange) != null)
@@ -171,7 +170,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
AnsiConsole.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial");
Console.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();

View File

@@ -4,7 +4,6 @@
All rights reserved.
*/
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.IO;
@@ -52,7 +51,7 @@ namespace Il2CppInspector
public override IFileFormatStream this[uint index] {
get {
AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
Console.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

View File

@@ -4,7 +4,6 @@
All rights reserved.
*/
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.IO;
@@ -52,7 +51,7 @@ namespace Il2CppInspector
public override IFileFormatStream this[uint index] {
get {
AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
Console.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

View File

@@ -5,7 +5,6 @@
All rights reserved.
*/
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -161,7 +160,7 @@ namespace Il2CppInspector
}
catch (Exception ex)
{
AnsiConsole.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
Console.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
preferPHT = true;
SHT = [];
}
@@ -171,12 +170,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()) {
AnsiConsole.WriteLine("ELF binary has no SHT - reverting to PHT");
Console.WriteLine("ELF binary has no SHT - reverting to PHT");
preferPHT = true;
}
else if (SHT.All(s => conv.ULong(s.sh_addr) == 0ul)) {
AnsiConsole.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
Console.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
preferPHT = true;
}
@@ -193,7 +192,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)) {
AnsiConsole.WriteLine("ELF binary appears to be a dumped memory image");
Console.WriteLine("ELF binary appears to be a dumped memory image");
isMemoryImage = true;
}
preferPHT = true;
@@ -203,7 +202,7 @@ namespace Il2CppInspector
else {
var shtOverlap = shtShouldBeOrdered.Aggregate((x, y) => x <= y? y : ulong.MaxValue) == ulong.MaxValue;
if (shtOverlap) {
AnsiConsole.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
Console.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
preferPHT = true;
}
}
@@ -224,20 +223,7 @@ namespace Il2CppInspector
// Get dynamic table if it exists (must be done after rebasing)
if (GetProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC)
{
// 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);
}
DynamicTable = ReadArray<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
// Get offset of code section
var codeSegment = PHT.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X);
@@ -268,6 +254,21 @@ 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));
@@ -290,7 +291,7 @@ namespace Il2CppInspector
}
// Process relocations
var relsz = (uint)Sizeof(typeof(TSym));
var relsz = Sizeof(typeof(TSym));
var currentRel = 0;
var totalRel = rels.Count();
@@ -300,26 +301,7 @@ namespace Il2CppInspector
if (currentRel % 1000 == 0)
StatusUpdate($"Processing relocations ({currentRel * 100 / totalRel:F0}%)");
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;
}
}
var symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz).st_value; // S
// Ignore relocations into memory addresses not mapped from the image
try {
@@ -362,7 +344,7 @@ namespace Il2CppInspector
WriteWord(result.newValue);
}
}
AnsiConsole.WriteLine($"Processed {rels.Count} relocations");
Console.WriteLine($"Processed {rels.Count} relocations");
// Build symbol and export tables
processSymbols();
@@ -406,8 +388,7 @@ 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
@@ -455,15 +436,7 @@ namespace Il2CppInspector
symbolTable.Clear();
var exportTable = new Dictionary<string, Export>();
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));
foreach (var pTab in pTables) {
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
foreach (var symbol in symbol_table)
@@ -490,7 +463,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.Name, VirtualAddress = conv.ULong(symbol.st_value)});
exportTable.TryAdd(name, new Export {Name = symbolItem.DemangledName, VirtualAddress = conv.ULong(symbol.st_value)});
}
}

View File

@@ -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, [binaryObjectStream, loadOptions, statusCallback]) is IFileFormatStream loaded) {
.Invoke(null, new object[] { binaryObjectStream, loadOptions, statusCallback }) is IFileFormatStream loaded) {
loaded.IsModified |= preProcessResult.IsStreamModified;
return loaded;
@@ -207,6 +207,8 @@ namespace Il2CppInspector
public virtual string Arch => throw new NotImplementedException();
public virtual int Bits => throw new NotImplementedException();
// Extra parameters to be passed to a loader
protected LoadOptions LoadOptions;

View File

@@ -6,7 +6,6 @@
using System;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization.Attributes;
namespace Il2CppInspector
{
@@ -32,7 +31,6 @@ 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,
@@ -174,47 +172,4 @@ 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;
}
}

View File

@@ -4,13 +4,12 @@
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
{
@@ -173,21 +172,15 @@ 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) {
@@ -195,7 +188,7 @@ namespace Il2CppInspector
// TODO: Implement Mach-O relocations
if (rels.Any()) {
AnsiConsole.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
break;
}
}
@@ -289,7 +282,7 @@ namespace Il2CppInspector
: SymbolType.Unknown;
if (type == SymbolType.Unknown) {
AnsiConsole.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} {name}");
Console.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} " + CxxDemangler.CxxDemangler.Demangle(name));
}
// Ignore duplicates
@@ -297,82 +290,7 @@ namespace Il2CppInspector
}
}
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 uint[] GetFunctionTable() => 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;

View File

@@ -4,14 +4,13 @@
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
{
@@ -103,7 +102,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 _)
AnsiConsole.WriteLine("Themida protection detected");
Console.WriteLine("Themida protection detected");
// Packed with anything (including Themida)?
mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null;
@@ -115,32 +114,27 @@ namespace Il2CppInspector
section.Name = wantedSectionTypes[section.Characteristics];
// Get base of code
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections
.FirstOrDefault(x => x.Characteristics.HasFlag(PE.IMAGE_SCN_MEM_EXECUTE))?.PointerToRawData ?? 0;
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
// Confirm that .rdata section begins at same place as IAT
var rData = sections.FirstOrDefault(x => x.Name == ".rdata");
mightBePacked |= rData == null || rData.VirtualAddress != IATStart;
if (rData != null)
{
var rData = sections.First(x => x.Name == ".rdata");
mightBePacked |= rData.VirtualAddress != IATStart;
// 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;

View File

@@ -4,12 +4,11 @@
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
{
@@ -101,7 +100,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);
AnsiConsole.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}");
Console.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];

View File

@@ -20,5 +20,7 @@ namespace Il2CppInspector
public ulong VirtualAddress { get; set; }
public string Name { get; set; }
public SymbolType Type { get; set; }
public string DemangledName => CxxDemangler.CxxDemangler.Demangle(Name);
}
}

View File

@@ -6,13 +6,12 @@
*/
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
@@ -192,7 +191,7 @@ namespace Il2CppInspector
var symbols = Image.GetSymbolTable();
if (symbols.Any()) {
AnsiConsole.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
symbols.TryGetValue("g_CodeRegistration", out var code);
symbols.TryGetValue("g_MetadataRegistration", out var metadata);
@@ -203,13 +202,13 @@ namespace Il2CppInspector
symbols.TryGetValue("_g_MetadataRegistration", out metadata);
if (code != null && metadata != null) {
AnsiConsole.WriteLine("Required structures acquired from symbol lookup");
Console.WriteLine("Required structures acquired from symbol lookup");
return (code.VirtualAddress, metadata.VirtualAddress);
} else {
AnsiConsole.WriteLine("No matches in symbol table");
Console.WriteLine("No matches in symbol table");
}
} else if (symbols != null) {
AnsiConsole.WriteLine("No symbol table present in binary file");
Console.WriteLine("No symbol table present in binary file");
} else {
Console.WriteLine("Symbol table search not implemented for this binary format");
}
@@ -228,12 +227,12 @@ namespace Il2CppInspector
var (code, metadata) = ConsiderCode(Image, loc);
if (code != 0) {
RegistrationFunctionPointer = loc + Image.GlobalOffset;
AnsiConsole.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
return (code, metadata);
}
}
AnsiConsole.WriteLine("No matches via code heuristics");
Console.WriteLine("No matches via code heuristics");
return null;
}
@@ -245,11 +244,11 @@ namespace Il2CppInspector
var (codePtr, metadataPtr) = ImageScan(Metadata);
if (codePtr == 0) {
AnsiConsole.WriteLine("No matches via data heuristics");
Console.WriteLine("No matches via data heuristics");
return null;
}
AnsiConsole.WriteLine("Required structures acquired from data heuristics");
Console.WriteLine("Required structures acquired from data heuristics");
return (codePtr, metadataPtr);
}
@@ -275,8 +274,8 @@ namespace Il2CppInspector
var pointerSize = Image.Bits == 32 ? 4u : 8u;
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));
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));
// Root structures from which we find everything else
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
@@ -338,15 +337,7 @@ namespace Il2CppInspector
}
// Read method invoker pointer indices - one per method
try
{
MethodInvokerIndices.Add(module,
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
}
catch (InvalidOperationException)
{
MethodInvokerIndices.Add(module, [..new int[(int)module.MethodPointerCount]]);
}
MethodInvokerIndices.Add(module, Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int) module.MethodPointerCount));
}
}
@@ -407,21 +398,12 @@ 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();

View File

@@ -5,11 +5,8 @@
*/
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;
@@ -17,6 +14,8 @@ 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
@@ -411,7 +410,7 @@ namespace Il2CppInspector
if (metadataFile != null) {
// Extract the metadata file to memory
if (!silent)
AnsiConsole.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
Console.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
metadataMemoryStream = new MemoryStream();
using var metadataStream = metadataFile.Open();
@@ -429,7 +428,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)
AnsiConsole.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
Console.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
// Extract the binary file or package to memory
binaryMemoryStream = new MemoryStream();
@@ -532,7 +531,7 @@ namespace Il2CppInspector
return null;
}
AnsiConsole.WriteLine("Detected metadata version " + metadata.Version);
Console.WriteLine("Detected metadata version " + metadata.Version);
// Load the il2cpp code file (try all available file formats)
IFileFormatStream stream;
@@ -560,16 +559,16 @@ namespace Il2CppInspector
var processors = new List<Il2CppInspector>();
foreach (var image in stream.Images) {
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);
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);
// Architecture-agnostic load attempt
try {
if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) {
AnsiConsole.WriteLine("IL2CPP binary version " + image.Version);
Console.WriteLine("IL2CPP binary version " + image.Version);
processors.Add(new Il2CppInspector(binary, metadata));
}

View File

@@ -230,8 +230,7 @@ namespace Il2CppInspector
// genericAdjustorThunks was inserted before invokerPointersCount in 24.5 and 27.1
// pointer expected if we need to bump version
if (Image.Version == MetadataVersions.V244 &&
(cr.InvokerPointersCount > 0x50000 || cr.ReversePInvokeWrapperCount > cr.ReversePInvokeWrappers))
if (Image.Version == MetadataVersions.V244 && cr.InvokerPointersCount > 0x50000)
{
Image.Version = MetadataVersions.V245;
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);

View File

@@ -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.V350) {
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) {
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version}).");
}
@@ -215,26 +215,9 @@ 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

View File

@@ -41,8 +41,10 @@
<ItemGroup>
<PackageReference Include="dnlib" Version="4.4.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
<PackageReference Include="Spectre.Console" Version="0.50.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
<NoWarn>NU1605</NoWarn>
</PackageReference>
</ItemGroup>
<ItemGroup>

View File

@@ -14,7 +14,6 @@ using Il2CppInspector.Cpp;
using Il2CppInspector.Cpp.UnityHeaders;
using Il2CppInspector.Next;
using Il2CppInspector.Reflection;
using Spectre.Console;
namespace Il2CppInspector.Model
{
@@ -41,9 +40,6 @@ 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>();
@@ -153,11 +149,11 @@ namespace Il2CppInspector.Model
UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last();
UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min;
AnsiConsole.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})");
Console.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) {
AnsiConsole.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
$" does not match metadata version {Image.Version}.");
}
@@ -240,18 +236,8 @@ namespace Il2CppInspector.Model
break;
case MetadataUsageType.MethodDef or MetadataUsageType.MethodRef:
var method = TypeModel.GetMetadataUsageMethod(usage);
declarationGenerator.IncludeMethod(method);
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);
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
// 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
@@ -261,7 +247,6 @@ namespace Il2CppInspector.Model
Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) { Group = Group });
}
Methods[method].MethodInfoPtrAddress = address;
break;
// FieldInfo is used for array initializers.
@@ -309,8 +294,6 @@ namespace Il2CppInspector.Model
declarationGenerator.IncludeType(type);
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
RequiredForwardDefinitions = declarationGenerator.GenerateRequiredForwardDefinitions();
// Restore stdout
Console.SetOut(stdout);

View File

@@ -77,7 +77,6 @@ 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")]
@@ -85,12 +84,10 @@ 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]

View File

@@ -18,7 +18,7 @@ public partial record struct Il2CppMethodDefinition
public TypeDefinitionIndex DeclaringType { get; private set; }
public TypeIndex ReturnType { get; private set; }
[VersionCondition(GreaterThan = "31.0")]
[VersionCondition(EqualTo = "31.0")]
public uint ReturnParameterToken { get; private set; }
public ParameterIndex ParameterStart { get; private set; }

View File

@@ -6,7 +6,6 @@ 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; }
}

View File

@@ -1,5 +1,4 @@
using System.Reflection;
using VersionedSerialization;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
@@ -33,8 +32,6 @@ 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")]
@@ -83,9 +80,4 @@ 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;
}

View File

@@ -28,7 +28,4 @@ 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);
}

View File

@@ -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", $"0x{model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start):X}"),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))),
("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", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start)))
);
}

View File

@@ -44,13 +44,8 @@ namespace Il2CppInspector.Outputs
// Write primitive type definitions for when we're not including other headers
writeCode($"""
#if defined(_IDACLANG_) || defined(_BINARYNINJA_)
#define IS_LIBCLANG_DECOMPILER
#endif
#if defined(_GHIDRA_) || defined(_IDA_) || defined(IS_LIBCLANG_DECOMPILER)
#define IS_DECOMPILER
#endif
#define IS_LIBCLANG_DECOMPILER (defined(_IDACLANG_) || defined(_BINARYNINJA_))
#define IS_DECOMPILER (defined(_GHIDRA_) || defined(_IDA_) || IS_LIBCLANG_DECOMPILER)
#if defined(_GHIDRA_) || defined(_IDA_)
typedef unsigned __int8 uint8_t;
@@ -63,7 +58,7 @@ namespace Il2CppInspector.Outputs
typedef __int64 int64_t;
#endif
#if defined(IS_LIBCLANG_DECOMPILER)
#if IS_LIBCLANG_DECOMPILER
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
@@ -72,26 +67,21 @@ namespace Il2CppInspector.Outputs
typedef short int16_t;
typedef int int32_t;
typedef long int64_t;
#ifdef linux
#undef linux
#endif
#endif
#if defined(_GHIDRA_) || defined(IS_LIBCLANG_DECOMPILER)
#if defined(_GHIDRA_) || 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
#ifndef IS_DECOMPILER
#if !IS_DECOMPILER
#define _CPLUSPLUS_
#endif
""");
if (_useBetterArraySize)
writeCode("#define il2cpp_array_size_t actual_il2cpp_array_size_t");
writeCode("#define actual_il2cpp_array_size_t il2cpp_array_size_t");
writeSectionHeader("IL2CPP internal types");
writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits));
@@ -104,7 +94,9 @@ namespace Il2CppInspector.Outputs
{
int32_t size;
actual_il2cpp_array_size_t value;
} il2cpp_array_size_t;
} better_il2cpp_array_size_t;
#define better_il2cpp_array_size_t il2cpp_array_size_t
""");
if (_model.TargetCompiler == CppCompilerType.MSVC)
@@ -123,20 +115,17 @@ namespace Il2CppInspector.Outputs
}
// C does not support namespaces
writeCode("#ifndef IS_DECOMPILER");
writeCode("#if !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("#ifndef IS_DECOMPILER");
writeCode("#if !IS_DECOMPILER");
writeCode("}");
writeCode("#endif");
}
@@ -317,33 +306,20 @@ namespace Il2CppInspector.Outputs
writeLine("");
}
private void writeForwardDefinitions()
{
writeSectionHeader("Required forward definitions");
foreach (var cppType in _model.RequiredForwardDefinitions)
writeCode(cppType.ToString());
}
private void writeTypesForGroup(string header, string group)
{
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)

View File

@@ -217,6 +217,7 @@ 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());
});
}

View File

@@ -1,19 +1,4 @@
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
from binaryninja import *
#try:
# from typing import TYPE_CHECKING
@@ -23,16 +8,16 @@ from binaryninja.log import log_error
# import os
# import sys
# from datetime import datetime
# from typing import Literal
# bv: BinaryView = None # type: ignore
#except:
# pass
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment: bool = True
# 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
_status: BaseStatusHandler
@@ -45,8 +30,6 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
_address_size: int
_endianness: Literal["little", "big"]
TYPE_PARSER_OPTIONS = ["--target=x86_64-pc-linux", "-x", "c++", "-D_BINARYNINJA_=1"]
def __init__(self, status: BaseStatusHandler):
self._status = status
@@ -71,24 +54,6 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
self._type_cache[type] = parsed
return parsed
def _parse_type_source(self, types: str, filename: str | None = None):
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,
self.TYPE_PARSER_OPTIONS,
)
if parsed_types is None:
log_error("Failed to import types.")
log_error(errors)
return None
return parsed_types
def get_script_directory(self) -> str:
return CURRENT_PATH
@@ -101,15 +66,26 @@ 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")
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f:
parsed_types = self._parse_type_source(f.read(), "il2cpp.hpp")
parsed_types, errors = TypeParser.default.parse_types_from_source(
f.read(),
"il2cpp.h",
self._view.platform if self._view.platform is not None else Platform["windows-x86_64"],
self._view,
[
"--target=x86_64-pc-linux",
"-x", "c++",
"-D_BINARYNINJA_=1"
]
)
if parsed_types is None:
log_error("Failed to import header")
log_error(errors)
return
self._status.update_step("Importing header types", len(parsed_types.types))
@@ -118,9 +94,7 @@ 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)
@@ -232,38 +206,22 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
return
typestr = ";\n".join(function_sigs).replace("this", "_this") + ";"
parsed_types = self._parse_type_source(typestr, "cached_types.hpp")
if parsed_types is None:
return
# bv.parse_types_from_source returns a dict in the functions field.
# TypeParser.parse_types_from_source does not.
for function_sig, function in zip(function_sigs, parsed_types.functions):
self._function_type_cache[function_sig] = function.type
res = self._view.parse_types_from_string(typestr)
for function_sig, function in zip(function_sigs, res.functions.values()): # type: ignore
self._function_type_cache[function_sig] = function
# only required if supports_fake_string_segment == True
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)
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
)
last_end_addr += (0x1000 - (last_end_addr % 0x1000))
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:
encoded = value.encode() + b"\x00"
self._view.write(address, encoded)
return len(encoded)
def write_string(self, address: int, value: str):
self._view.write(address, value.encode() + b"\x00")
def write_address(self, address: int, value: int):
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
@@ -279,8 +237,7 @@ 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():
@@ -308,13 +265,11 @@ class BinaryNinjaStatusHandler(BaseStatusHandler):
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):
@@ -326,5 +281,4 @@ class Il2CppTask(BackgroundTaskThread):
context = ScriptContext(backend, status)
context.process()
Il2CppTask().start()

View File

@@ -5,8 +5,6 @@ from ghidra.program.model.data import ArrayDataType
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
@@ -22,9 +20,6 @@ from java.lang import Long
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()
@@ -43,7 +38,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(self._to_address(0), True)
currentProgram.setImageBase(toAddr(0), True)
# Don't trigger decompiler
setAnalysisOption(currentProgram, "Call Convention ID", "false")
@@ -52,7 +47,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
pass
def define_function(self, address: int, end: int | None = None):
address = self._to_address(address)
address = toAddr(address)
# Don't override existing functions
fn = getFunctionAt(address)
if fn is None:
@@ -65,7 +60,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
t = getDataTypes(type)[0]
a = ArrayDataType(t, count, t.getLength())
address = self._to_address(address)
address = toAddr(address)
removeDataAt(address)
createData(address, a)
@@ -75,24 +70,25 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
try:
t = getDataTypes(type)[0]
address = self._to_address(address)
address = toAddr(address)
removeDataAt(address)
createData(address, t)
except:
print("Failed to set type: %s" % type)
def set_function_type(self, address: int, type: str):
typeSig = CParserUtils.parseSignature(DataTypeManagerService@None, currentProgram, type)
ApplyFunctionSignatureCmd(self._to_address(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
make_function(address)
typeSig = CParserUtils.parseSignature(None, currentProgram, type)
ApplyFunctionSignatureCmd(toAddr(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
def set_data_comment(self, address: int, cmt: str):
setEOLComment(self._to_address(address), cmt)
setEOLComment(toAddr(address), cmt)
def set_function_comment(self, address: int, cmt: str):
setPlateComment(self._to_address(address), cmt)
setPlateComment(toAddr(address), cmt)
def set_data_name(self, address: int, name: str):
address = self._to_address(address)
address = toAddr(address)
if len(name) > 2000:
print("Name length exceeds 2000 characters, skipping (%s)" % name)
@@ -111,7 +107,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(self._to_address(from_address), self._to_address(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
self.xrefs.addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
def import_c_typedef(self, type_def: str):
# Code declarations are not supported in Ghidra

View File

@@ -106,8 +106,8 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
ida_typeinf.set_c_macros(original_macros)
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
self._skip_function_creation = ida_segment.get_segm_by_name(".pdata") is not None
if self._skip_function_creation:
skip_make_function = ida_segment.get_segm_by_name(".pdata") is not None
if skip_make_function:
print(".pdata section found, skipping function boundaries")
if FOLDERS_AVAILABLE:
@@ -202,12 +202,11 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
return start
def write_string(self, address: int, value: str) -> int:
def write_string(self, address: int, value: str):
encoded_string = value.encode() + b'\x00'
string_length = len(encoded_string)
ida_bytes.put_bytes(address, encoded_string)
ida_bytes.create_strlit(address, string_length, ida_nalt.STRTYPE_C)
return string_length
def write_address(self, address: int, value: int):
if self._is_32_bit:

View File

@@ -64,7 +64,7 @@ class BaseDisassemblerInterface(abc.ABC):
# only required if supports_fake_string_segment == True
def create_fake_segment(self, name: str, size: int) -> int: return 0
def write_string(self, address: int, value: str) -> int: pass
def write_string(self, address: int, value: str): pass
def write_address(self, address: int, value: int): pass
class ScriptContext:
@@ -191,11 +191,11 @@ class ScriptContext:
self.define_string(d)
ref_addr = self.parse_address(d)
written_string_length = self._backend.write_string(current_string_address, d["string"])
self._backend.write_string(current_string_address, d["string"])
self._backend.set_data_type(ref_addr, r'const char* const')
self._backend.write_address(ref_addr, current_string_address)
current_string_address += written_string_length
current_string_address += len(d["string"]) + 1
self._status.update_progress()
else:
for d in metadata['stringLiterals']:
@@ -286,8 +286,5 @@ class ScriptContext:
end_time = datetime.now()
print(f"Took: {end_time - start_time}")
except RuntimeError:
pass
finally:
self._status.shutdown()
except RuntimeError: pass
finally: self._status.shutdown()

View File

@@ -4,11 +4,6 @@
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;
@@ -17,6 +12,11 @@ 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
{

View File

@@ -13,7 +13,6 @@ using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
@@ -256,7 +255,7 @@ namespace Il2CppInspector.Reflection
public PropertyInfo GetProperty(string name) => DeclaredProperties.FirstOrDefault(p => p.Name == name);
public MethodBase[] GetVTable() {
if (Definition.IsValid) {
if (!Definition.IsValid) {
MetadataUsage[] vt = Assembly.Model.Package.GetVTable(Definition);
MethodBase[] res = new MethodBase[vt.Length];
for (int i = 0; i < vt.Length; i++) {
@@ -785,9 +784,7 @@ namespace Il2CppInspector.Reflection
// Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum)
if (Definition.Bitfield.EnumType) {
IsEnum = true;
var enumUnderlyingTypeIndex = Definition.GetEnumElementTypeIndex(Assembly.Model.Package.Version);
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, enumUnderlyingTypeIndex);
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.ElementTypeIndex);
}
// Pass-by-reference type

View File

@@ -165,9 +165,10 @@ 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)
{
MethodInvokers[index] ??= new MethodInvoker(method, index);
if (index != -1) {
if (MethodInvokers[index] == null)
MethodInvokers[index] = new MethodInvoker(method, index);
method.Invoker = MethodInvokers[index];
}
}
@@ -175,13 +176,12 @@ 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 (index != -1)
{
MethodInvokers[index] ??= new MethodInvoker(GenericMethods[spec], index);
if (MethodInvokers[index] == null)
MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index);
GenericMethods[spec].Invoker = MethodInvokers[index];
}
}
}
// Post-processing hook
PluginHooks.PostProcessTypeModel(this);

View File

@@ -4,7 +4,6 @@ using System.Diagnostics;
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Spectre.Console;
namespace Il2CppInspector.Utils;
@@ -123,7 +122,7 @@ public static class BlobReader
}
catch (InvalidDataException)
{
AnsiConsole.WriteLine($"Found invalid compressed int at metadata address 0x{address:x8}. Reading as normal int.");
Console.WriteLine($"Found invalid compressed int at metadata address 0x{address:x8}. Reading as normal int.");
return blob.ReadInt32(address);
}
}
@@ -143,7 +142,7 @@ public static class BlobReader
}
catch (InvalidDataException)
{
AnsiConsole.WriteLine($"Found invalid compressed uint at metadata address 0x{address:x8}. Reading as normal uint.");
Console.WriteLine($"Found invalid compressed uint at metadata address 0x{address:x8}. Reading as normal uint.");
return blob.ReadUInt32(address);
}
}
@@ -164,9 +163,7 @@ public static class BlobReader
var typeHandle = inspector.TypeReferences[typeIndex].Data.KlassIndex;
enumType = inspector.TypeDefinitions[typeHandle];
var elementTypeIndex = enumType.GetEnumElementTypeIndex(inspector.Version);
var elementTypeHandle = inspector.TypeReferences[elementTypeIndex].Data.KlassIndex;
var elementTypeHandle = inspector.TypeReferences[enumType.ElementTypeIndex].Data.KlassIndex;
var elementType = inspector.TypeDefinitions[elementTypeHandle];
typeEnum = inspector.TypeReferences[elementType.ByValTypeIndex].Type;
}

View File

@@ -1,136 +0,0 @@
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();
}
}

View File

@@ -1,38 +0,0 @@
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;
}
}

View File

@@ -1,17 +0,0 @@
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;
}
}

View File

@@ -1,21 +0,0 @@
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();
}
}

View File

@@ -1,15 +0,0 @@
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; } = "";
}

View File

@@ -1,172 +0,0 @@
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);
}
}

View File

@@ -1,21 +0,0 @@
<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>

View File

@@ -1,6 +0,0 @@
namespace Il2CppInspector.Redux.CLI;
internal sealed class PortProvider(int port)
{
public int Port => port;
}

View File

@@ -1,46 +0,0 @@
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();

View File

@@ -1,23 +0,0 @@
{
"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"
}

View File

@@ -1,30 +0,0 @@
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;
}
}

View File

@@ -1,13 +0,0 @@
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);
}

View File

@@ -1,8 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -1,9 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -1,54 +0,0 @@
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();
});
});
}
}

View File

@@ -1,8 +0,0 @@
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;

View File

@@ -1,58 +0,0 @@
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();
}
}

View File

@@ -1,16 +0,0 @@
<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>

View File

@@ -1,23 +0,0 @@
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);
}
}

View File

@@ -1,10 +0,0 @@
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public enum CSharpLayout
{
SingleFile,
Namespace,
Assembly,
Class,
Tree
}

View File

@@ -1,66 +0,0 @@
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;
}
}
}

View File

@@ -1,31 +0,0 @@
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);
}
}

View File

@@ -1,53 +0,0 @@
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);
}
}
}

View File

@@ -1,9 +0,0 @@
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public enum DisassemblerType
{
IDA,
Ghidra,
BinaryNinja,
None
}

View File

@@ -1,27 +0,0 @@
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);
}
}

View File

@@ -1,14 +0,0 @@
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; }
}

View File

@@ -1,38 +0,0 @@
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();
}
}

View File

@@ -1,7 +0,0 @@
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public enum TypeSortingMode
{
Alphabetical,
TypeDefinitionIndex
}

View File

@@ -1,29 +0,0 @@
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);
}
}

View File

@@ -1,54 +0,0 @@
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;
}
}

View File

@@ -1,12 +0,0 @@
{
"profiles": {
"Il2CppInspector.Redux.FrontendCore": {
"commandName": "Project",
"launchBrowser": false,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:43298;http://localhost:43299"
}
}
}

View File

@@ -1,44 +0,0 @@
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);
}

View File

@@ -1,251 +0,0 @@
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>");
}
}

View File

@@ -1,10 +0,0 @@
.DS_Store
node_modules
/build
/.svelte-kit
/package
.env
.env.*
!.env.example
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -1,4 +0,0 @@
{
"plugins": ["prettier-plugin-svelte", "prettier-plugin-tailwindcss"],
"tabWidth": 4
}

View File

@@ -1,17 +0,0 @@
{
"$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"
}

View File

@@ -1,46 +0,0 @@
{
"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"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View File

@@ -1,7 +0,0 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +0,0 @@
[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"

View File

@@ -1,3 +0,0 @@
fn main() {
tauri_build::build()
}

View File

@@ -1,13 +0,0 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main"
],
"permissions": [
"core:default",
"opener:default",
"dialog:default"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1,20 +0,0 @@
// 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");
}

Some files were not shown because too many files have changed in this diff Show More