19 Commits

Author SHA1 Message Date
LukeFZ
4befde8ab4 increase CodeRegistration validation heuristics thresholds due to some games reaching them 2025-11-26 19:54:05 +01:00
LukeFZ
6d674ecc8c use same metareg scanner for < v27 and > v27, implement TryMapVATR in PEReader for performance 2025-11-11 04:14:31 +01:00
LukeFZ
8b93dda191 Merge branch 'master' of https://github.com/LukeFZ/Il2CppInspectorRedux 2025-10-13 09:17:05 +02:00
LukeFZ
bba8a2913a also unload mssdk64 tils to prevent type name conflicts 2025-10-13 09:16:45 +02:00
Luke
193395db29 Change Bin2Object URL to point to forked repo instead 2025-10-12 17:28:16 +02:00
LukeFZ
481d05668d (hopefully) lower python requirement to 3.8 2025-10-12 17:13:20 +02:00
LukeFZ
ca6c958f9a add pyghidra runtime annotation to fix support 2025-10-12 17:08:19 +02:00
LukeFZ
7a621b40c6 fix support for properties without methods 2025-09-26 00:30:59 +02:00
LukeFZ
1a418280fb update bin2object (again) 2025-09-26 00:28:51 +02:00
LukeFZ
f1a69cafe3 fix support for 35+, add support for 39 2025-09-18 23:36:57 +02:00
LukeFZ
e5f2fa703d update bin2object 2025-09-18 23:35:48 +02:00
LukeFZ
38aa333764 fix workflow pnpm install and caching 2025-09-03 23:11:01 +02:00
LukeFZ
3ae9dba60d improve caching in workflow, remove duplicated .net install steps 2025-09-03 23:04:23 +02:00
LukeFZ
c94bc1c225 update tauri js deps, make pnpm install always run on release build 2025-09-03 23:01:01 +02:00
TrialCarrot
ac361bd90f build: auto run pnpm install if node_modules/ doesn't exists (#24)
Co-authored-by: TrialCarrot <TrialCarrot@users.noreply.github.com>
2025-09-03 22:57:55 +02:00
LukeFZ
a88e91451a add (untested) support for metadata v38 2025-08-22 04:53:00 +02:00
LukeFZ
079373815f update readme to inform about the new UIs 2025-08-15 21:28:58 +02:00
Luke
3439ca912b Implement new GUI and CLI, fix misc. smaller issues (#22)
* Initial commit of new UI c# component

* Initial commit of new UI frontend component

* target WinExe to hide console window in release mode, move ui exe into resources

* force single file publishing and add initial gh workflow for publishing ui

* fix workflow errors

* update dependencies and remove cxxdemangler, as it was outdated

* fix c# single file output due to invalid output path

* smaller tweaks, hack around loops in cpp type layouting

* process other queued exports even if one fails and show error message

* add basic support for processing LC_DYLD_CHAINED_FIXUPS

* ELF loading should not use the file offset for loading the dynamic section

* fix symbol table loading in some modified elfs

* add "start export" button on format selection screen, clear all toasts after selecting an export format

* embed ui executable directly into c# assembly

* only build tauri component in c# release builds

* add il2cpp file (binary, metadata) export to advanced tab

* fix and enable binary ninja fake string segment support

* add support for metadata

* unify logic for getting element type index

* fix new ui not allowing script exports other than ida

* new ui: clear out loaded binary if no IL2CPP images could be loaded

* fix toAddr calls in ghidra script target

* remove dependency on a section being named .text in loaded pe files

* tweak symbol reading a bit and remove sht relocation reading

* add initial support for required forward references in il2cpp types, also fix issues with type names clashing with il2cpp api types

* reduce clang errors for header file, fix better array size struct, emit required forward definitions in header

* expose forward definitions in AppModel, fix issue with method-only used types not being emitted

* remove debug log line

* fix spelling mistakes in gui outputs

* fix il2cpp_array_size_t not being an actual type for later method definitions

* change the default port for new ui dev to 5000

* show current version and hash in new ui footer

* seperate redux ui impl into FrontendCore project

* make inspector version a server api, split up output subtypes and tweak some option names

* add redux CLI based on redux GUI output formats

* replace all Console.WriteLine calls in core inspector with AnsiConsole calls

* add workflow for new cli and add back old gui workflow

* disable aot publish and enable single file for redux cli
2025-08-15 21:13:32 +02:00
TrialCarrot
e161e0f226 Fix #14: Error while dotnet publish -c Release (#21)
Co-authored-by: TrialCarrot <TrialCarrot@users.noreply.github.com>
2025-07-18 16:32:20 +02:00
213 changed files with 21352 additions and 1096 deletions

View File

@@ -3,7 +3,111 @@ name: Il2CppInspectorRedux Build
on: [push, workflow_dispatch] on: [push, workflow_dispatch]
jobs: jobs:
build-gui: build-redux-gui: # this already includes stuff only relevant for linux/macos for when the gui is released on those platforms
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
package_json_file: Il2CppInspector.Redux.GUI.UI/package.json
- name: Setup Node.JS
uses: actions/setup-node@v4
with:
node-version: lts/*
cache: "pnpm"
cache-dependency-path: Il2CppInspector.Redux.GUI.UI/pnpm-lock.yaml
- 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
- name: Cache NuGet packages
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 ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI
- name: Build & Publish
run: dotnet publish -c Release --no-self-contained --no-restore -o ./${{ matrix.rid }} -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI/Il2CppInspector.Redux.CLI.csproj
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }}
build-old-gui:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
@@ -32,10 +136,10 @@ jobs:
- name: Upload GUI Artifact - name: Upload GUI Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Il2CppInspectorRedux.GUI name: Il2CppInspectorRedux.Old.GUI
path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish
build-cli: build-old-cli:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -47,10 +151,10 @@ jobs:
with: with:
submodules: true submodules: true
- name: Setup .NET SDK - name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4 uses: actions/setup-dotnet@v4
with: with:
dotnet-version: '9.0.x' dotnet-version: ${{ matrix.dotnet-version }}
- uses: actions/cache@v3 - uses: actions/cache@v3
with: with:
@@ -59,11 +163,6 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}- ${{ 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 - name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.CLI run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.CLI
@@ -73,5 +172,5 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }} name: Il2CppInspectorRedux.Old.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }} path: ./${{ matrix.rid }}

3
.gitignore vendored
View File

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

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "Bin2Object"] [submodule "Bin2Object"]
path = Bin2Object path = Bin2Object
url = https://github.com/djkaty/Bin2Object url = https://github.com/LukeFZ/Bin2Object

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -5,9 +5,6 @@
All rights reserved. All rights reserved.
*/ */
using System;
using System.Collections.Generic;
namespace Il2CppInspector.Cpp namespace Il2CppInspector.Cpp
{ {
/// <summary> /// <summary>
@@ -65,9 +62,9 @@ namespace Il2CppInspector.Cpp
// Uniquely name an object within the parent namespace // Uniquely name an object within the parent namespace
public string GetName(T t) { public string GetName(T t) {
// If we've named this particular object before, just return that name // If we've named this particular object before, just return that name
string name; if (names.TryGetValue(t, out var name))
if (names.TryGetValue(t, out name))
return name; return name;
// Obtain the mangled name for the object // Obtain the mangled name for the object
name = keyFunc(t); name = keyFunc(t);
// Check if the mangled name has been given to another object - if it has, // Check if the mangled name has been given to another object - if it has,

View File

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

View File

@@ -4,15 +4,11 @@
All rights reserved. All rights reserved.
*/ */
using System; using Il2CppInspector.Cpp.UnityHeaders;
using System.Collections; using System.Collections;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Il2CppInspector.Cpp.UnityHeaders;
namespace Il2CppInspector.Cpp namespace Il2CppInspector.Cpp
{ {
@@ -23,7 +19,7 @@ namespace Il2CppInspector.Cpp
public Dictionary<string, CppType> Types { get; } public Dictionary<string, CppType> Types { get; }
// All of the literal typedef aliases // All of the literal typedef aliases
public Dictionary<string, CppType> TypedefAliases { get; } = new Dictionary<string, CppType>(); public Dictionary<string, CppType> TypedefAliases { get; } = [];
public CppType this[string s] => Types.ContainsKey(s)? Types[s] : public CppType this[string s] => Types.ContainsKey(s)? Types[s] :
TypedefAliases.ContainsKey(s)? TypedefAliases[s].AsAlias(s) : null; TypedefAliases.ContainsKey(s)? TypedefAliases[s].AsAlias(s) : null;
@@ -34,7 +30,8 @@ namespace Il2CppInspector.Cpp
// Architecture width in bits (32/64) - to determine pointer sizes // Architecture width in bits (32/64) - to determine pointer sizes
public int WordSize { get; } public int WordSize { get; }
private Dictionary<string, ComplexValueType> complexTypeMap = new Dictionary<string, ComplexValueType> { private Dictionary<string, ComplexValueType> complexTypeMap = new()
{
["struct"] = ComplexValueType.Struct, ["struct"] = ComplexValueType.Struct,
["union"] = ComplexValueType.Union, ["union"] = ComplexValueType.Union,
["enum"] = ComplexValueType.Enum ["enum"] = ComplexValueType.Enum
@@ -44,22 +41,23 @@ namespace Il2CppInspector.Cpp
private string currentGroup = string.Empty; private string currentGroup = string.Empty;
public void SetGroup(string group) => currentGroup = group; public void SetGroup(string group) => currentGroup = group;
private static readonly List<CppType> primitiveTypes = new List<CppType> { private static readonly List<CppType> primitiveTypes =
new CppType("uint8_t", 8), [
new CppType("uint16_t", 16), new("uint8_t", 8),
new CppType("uint32_t", 32), new("uint16_t", 16),
new CppType("uint64_t", 64), new("uint32_t", 32),
new CppType("int8_t", 8), new("uint64_t", 64),
new CppType("int16_t", 16), new("int8_t", 8),
new CppType("int32_t", 32), new("int16_t", 16),
new CppType("int64_t", 64), new("int32_t", 32),
new CppType("char", 8), new("int64_t", 64),
new CppType("int", 32), new("char", 8),
new CppType("float", 32), new("int", 32),
new CppType("double", 64), new("float", 32),
new CppType("bool", 8), new("double", 64),
new CppType("void", 0) new("bool", 8),
}; new("void", 0)
];
public CppTypeCollection(int wordSize) { public CppTypeCollection(int wordSize) {
if (wordSize != 32 && wordSize != 64) if (wordSize != 32 && wordSize != 64)
@@ -538,15 +536,18 @@ namespace Il2CppInspector.Cpp
public CppComplexType Struct(string name = "", int alignmentBytes = 0) { public CppComplexType Struct(string name = "", int alignmentBytes = 0) {
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType)) if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
return (CppComplexType) cppType; return (CppComplexType) cppType;
var type = new CppComplexType(ComplexValueType.Struct) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes}; var type = new CppComplexType(ComplexValueType.Struct) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes};
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
Add(type); Add(type);
return type; return type;
} }
public CppComplexType Union(string name = "", int alignmentBytes = 0) { public CppComplexType Union(string name = "", int alignmentBytes = 0) {
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType)) if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
return (CppComplexType) cppType; return (CppComplexType) cppType;
var type = new CppComplexType(ComplexValueType.Union) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes}; var type = new CppComplexType(ComplexValueType.Union) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes};
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
Add(type); Add(type);
@@ -554,9 +555,13 @@ namespace Il2CppInspector.Cpp
} }
public CppEnumType Enum(CppType underlyingType, string name = "") { 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}; var type = new CppEnumType(underlyingType) {Name = name, Group = currentGroup};
if (!string.IsNullOrEmpty(name)) if (!string.IsNullOrEmpty(name))
Add(type); Add(type);
return type; return type;
} }
@@ -585,11 +590,17 @@ namespace Il2CppInspector.Cpp
cppTypes.AddFromDeclarationText(apis); cppTypes.AddFromDeclarationText(apis);
// Don't allow any of the header type names or primitive type names to be re-used // Don't allow any of the header type names or primitive type names to be re-used
foreach (var type in cppTypes.Types.Values) foreach (var type in cppTypes.Types.Keys)
declGen?.TypeNamespace.TryReserveName(type.Name); {
declGen?.TypeNamespace.TryReserveName(type);
declGen?.GlobalsNamespace.TryReserveName(type);
}
foreach (var typedef in cppTypes.TypedefAliases.Values) foreach (var typedef in cppTypes.TypedefAliases.Keys)
declGen?.GlobalsNamespace.TryReserveName(typedef.Name); {
declGen?.TypeNamespace.TryReserveName(typedef);
declGen?.GlobalsNamespace.TryReserveName(typedef);
}
cppTypes.SetGroup(""); cppTypes.SetGroup("");

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -5,13 +5,14 @@
All rights reserved. All rights reserved.
*/ */
using Il2CppInspector.Next;
using Spectre.Console;
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Il2CppInspector.Next;
using VersionedSerialization; using VersionedSerialization;
namespace Il2CppInspector.Cpp.UnityHeaders namespace Il2CppInspector.Cpp.UnityHeaders
@@ -140,7 +141,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
// No il2cpp exports? Just return the earliest version from the header range // 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 // 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()) { if (!exports.Any()) {
Console.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project"); AnsiConsole.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project");
return typeHeaders.Select(t => new UnityHeaders(t, return typeHeaders.Select(t => new UnityHeaders(t,
apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList(); apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList();
@@ -161,7 +162,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
if (apiMatches.Any()) { if (apiMatches.Any()) {
// Intersect all API ranges with all header ranges to produce final list of possible ranges // Intersect all API ranges with all header ranges to produce final list of possible ranges
Console.WriteLine("IL2CPP API discovery was successful"); AnsiConsole.WriteLine("IL2CPP API discovery was successful");
return typeHeaders.SelectMany( return typeHeaders.SelectMany(
t => apiMatches.Where(a => t.VersionRange.Intersect(a.VersionRange) != null) t => apiMatches.Where(a => t.VersionRange.Intersect(a.VersionRange) != null)
@@ -170,7 +171,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
// None of the possible API versions match the binary // None of the possible API versions match the binary
// Select the oldest API version from the group - C++ project compilation will fail // Select the oldest API version from the group - C++ project compilation will fail
Console.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial"); AnsiConsole.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial");
return typeHeaders.Select(t => new UnityHeaders(t, return typeHeaders.Select(t => new UnityHeaders(t,
apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList(); apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList();

View File

@@ -4,6 +4,7 @@
All rights reserved. All rights reserved.
*/ */
using Spectre.Console;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -51,7 +52,7 @@ namespace Il2CppInspector
public override IFileFormatStream this[uint index] { public override IFileFormatStream this[uint index] {
get { get {
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}"); AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
IFileFormatStream loaded = null; IFileFormatStream loaded = null;
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format // ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format

View File

@@ -4,6 +4,7 @@
All rights reserved. All rights reserved.
*/ */
using Spectre.Console;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
@@ -51,7 +52,7 @@ namespace Il2CppInspector
public override IFileFormatStream this[uint index] { public override IFileFormatStream this[uint index] {
get { get {
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}"); AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
IFileFormatStream loaded = null; IFileFormatStream loaded = null;
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format // ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format

View File

@@ -5,6 +5,7 @@
All rights reserved. All rights reserved.
*/ */
using Spectre.Console;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -160,7 +161,7 @@ namespace Il2CppInspector
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT"); AnsiConsole.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
preferPHT = true; preferPHT = true;
SHT = []; SHT = [];
} }
@@ -170,12 +171,12 @@ namespace Il2CppInspector
// These can happen as a result of conversions from other formats to ELF, // These can happen as a result of conversions from other formats to ELF,
// or if the SHT has been deliberately stripped // or if the SHT has been deliberately stripped
if (!SHT.Any()) { if (!SHT.Any()) {
Console.WriteLine("ELF binary has no SHT - reverting to PHT"); AnsiConsole.WriteLine("ELF binary has no SHT - reverting to PHT");
preferPHT = true; preferPHT = true;
} }
else if (SHT.All(s => conv.ULong(s.sh_addr) == 0ul)) { else if (SHT.All(s => conv.ULong(s.sh_addr) == 0ul)) {
Console.WriteLine("ELF binary SHT is all-zero - reverting to PHT"); AnsiConsole.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
preferPHT = true; preferPHT = true;
} }
@@ -192,7 +193,7 @@ namespace Il2CppInspector
// If the first file offset of the first PHT is zero, assume a dumped image // 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)) { if (PHT.Any(t => conv.ULong(t.p_vaddr) == 0ul)) {
Console.WriteLine("ELF binary appears to be a dumped memory image"); AnsiConsole.WriteLine("ELF binary appears to be a dumped memory image");
isMemoryImage = true; isMemoryImage = true;
} }
preferPHT = true; preferPHT = true;
@@ -202,7 +203,7 @@ namespace Il2CppInspector
else { else {
var shtOverlap = shtShouldBeOrdered.Aggregate((x, y) => x <= y? y : ulong.MaxValue) == ulong.MaxValue; var shtOverlap = shtShouldBeOrdered.Aggregate((x, y) => x <= y? y : ulong.MaxValue) == ulong.MaxValue;
if (shtOverlap) { if (shtOverlap) {
Console.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT"); AnsiConsole.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
preferPHT = true; preferPHT = true;
} }
} }
@@ -223,7 +224,20 @@ namespace Il2CppInspector
// Get dynamic table if it exists (must be done after rebasing) // Get dynamic table if it exists (must be done after rebasing)
if (GetProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC) if (GetProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC)
DynamicTable = ReadArray<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>)))); {
// Important: do not use p_offset here!
// Only load sections should be loaded, which should also include the memory region that contains the dynamic section.
// This just provides the virtual address of the section.
// Some binaries may use the offset here to point to a fake version of the dynamic section,
// making relocation resolution and subsequent analysis fail.
// Reference for Android:
// phdr_table_get_dynamic_section, https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker_phdr.cpp
var dynamicAddr = conv.ULong(PT_DYNAMIC.p_vaddr);
var dynamicSize = (int)(conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>)));
DynamicTable = ReadMappedArray<elf_dynamic<TWord>>(dynamicAddr, dynamicSize);
}
// Get offset of code section // Get offset of code section
var codeSegment = PHT.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X); var codeSegment = PHT.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X);
@@ -254,21 +268,6 @@ namespace Il2CppInspector
StatusUpdate("Finding relocations"); 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 // Relocations in dynamic section
if (GetDynamicEntry(Elf.DT_REL) is elf_dynamic<TWord> dt_rel) { 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)); var dt_rel_count = conv.Int(conv.Div(GetDynamicEntry(Elf.DT_RELSZ).d_un, GetDynamicEntry(Elf.DT_RELENT).d_un));
@@ -291,7 +290,7 @@ namespace Il2CppInspector
} }
// Process relocations // Process relocations
var relsz = Sizeof(typeof(TSym)); var relsz = (uint)Sizeof(typeof(TSym));
var currentRel = 0; var currentRel = 0;
var totalRel = rels.Count(); var totalRel = rels.Count();
@@ -301,7 +300,26 @@ namespace Il2CppInspector
if (currentRel % 1000 == 0) if (currentRel % 1000 == 0)
StatusUpdate($"Processing relocations ({currentRel * 100 / totalRel:F0}%)"); StatusUpdate($"Processing relocations ({currentRel * 100 / totalRel:F0}%)");
var symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz).st_value; // S TWord symValue;
try
{
// man this really needs a full overhaul
symValue = ReadMappedObject<TSym>(conv.ULong(rel.SymbolTable) + conv.ULong(rel.SymbolIndex) * relsz)
.st_value; // S
}
catch (InvalidOperationException)
{
try
{
symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz)
.st_value; // S
}
catch (InvalidOperationException)
{
continue;
}
}
// Ignore relocations into memory addresses not mapped from the image // Ignore relocations into memory addresses not mapped from the image
try { try {
@@ -344,7 +362,7 @@ namespace Il2CppInspector
WriteWord(result.newValue); WriteWord(result.newValue);
} }
} }
Console.WriteLine($"Processed {rels.Count} relocations"); AnsiConsole.WriteLine($"Processed {rels.Count} relocations");
// Build symbol and export tables // Build symbol and export tables
processSymbols(); processSymbols();
@@ -388,7 +406,8 @@ namespace Il2CppInspector
WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt); WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
} }
private void processSymbols() { private void processSymbols()
{
StatusUpdate("Processing symbols"); StatusUpdate("Processing symbols");
// Three possible symbol tables in ELF files // Three possible symbol tables in ELF files
@@ -436,7 +455,15 @@ namespace Il2CppInspector
symbolTable.Clear(); symbolTable.Clear();
var exportTable = new Dictionary<string, Export>(); var exportTable = new Dictionary<string, Export>();
foreach (var pTab in pTables) { var alreadyProcessed = new List<(TWord offset, TWord count)>();
foreach (var pTab in pTables)
{
if (alreadyProcessed.Any(x =>
conv.ULong(x.offset) == conv.ULong(pTab.offset)))
continue;
alreadyProcessed.Add((pTab.offset, pTab.count));
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count)); var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
foreach (var symbol in symbol_table) foreach (var symbol in symbol_table)
@@ -463,7 +490,7 @@ namespace Il2CppInspector
var symbolItem = new Symbol {Name = name, Type = type, VirtualAddress = conv.ULong(symbol.st_value) }; var symbolItem = new Symbol {Name = name, Type = type, VirtualAddress = conv.ULong(symbol.st_value) };
symbolTable.TryAdd(name, symbolItem); symbolTable.TryAdd(name, symbolItem);
if (symbol.st_shndx != (ushort) Elf.SHN_UNDEF) if (symbol.st_shndx != (ushort) Elf.SHN_UNDEF)
exportTable.TryAdd(name, new Export {Name = symbolItem.DemangledName, VirtualAddress = conv.ULong(symbol.st_value)}); exportTable.TryAdd(name, new Export {Name = symbolItem.Name, VirtualAddress = conv.ULong(symbol.st_value)});
} }
} }

View File

@@ -176,7 +176,7 @@ namespace Il2CppInspector
try { try {
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public, if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
null, new[] { typeof(BinaryObjectStream), typeof(LoadOptions), typeof(EventHandler<string>) }, null) null, new[] { typeof(BinaryObjectStream), typeof(LoadOptions), typeof(EventHandler<string>) }, null)
.Invoke(null, new object[] { binaryObjectStream, loadOptions, statusCallback }) is IFileFormatStream loaded) { .Invoke(null, [binaryObjectStream, loadOptions, statusCallback]) is IFileFormatStream loaded) {
loaded.IsModified |= preProcessResult.IsStreamModified; loaded.IsModified |= preProcessResult.IsStreamModified;
return loaded; return loaded;

View File

@@ -6,6 +6,7 @@
using System; using System;
using NoisyCowStudios.Bin2Object; using NoisyCowStudios.Bin2Object;
using VersionedSerialization.Attributes;
namespace Il2CppInspector namespace Il2CppInspector
{ {
@@ -31,6 +32,7 @@ namespace Il2CppInspector
LC_DYLD_INFO_ONLY = 0x80000022, LC_DYLD_INFO_ONLY = 0x80000022,
LC_FUNCTION_STARTS = 0x26, LC_FUNCTION_STARTS = 0x26,
LC_ENCRYPTION_INFO_64 = 0x2C, LC_ENCRYPTION_INFO_64 = 0x2C,
LC_DYLD_CHAINED_FIXUPS = 0x80000034,
CPU_TYPE_X86 = 7, CPU_TYPE_X86 = 7,
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86, CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
@@ -172,4 +174,47 @@ namespace Il2CppInspector
public bool r_extern => ((r_data >> 27) & 1) == 1; public bool r_extern => ((r_data >> 27) & 1) == 1;
public uint r_type => r_data >> 28; 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,12 +4,13 @@
All rights reserved. All rights reserved.
*/ */
using NoisyCowStudios.Bin2Object;
using Spectre.Console;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector namespace Il2CppInspector
{ {
@@ -172,15 +173,21 @@ namespace Il2CppInspector
if (encryptionInfo.CryptID != 0) 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."); 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; 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 // There might be other data after the load command so always use the specified total size to step forwards
Position = startPos + loadCommand.Size; 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 // Must find __mod_init_func
if (funcTab == null) //if (funcTab == null)
return false; // return false;
// Process relocations // Process relocations
foreach (var section in machoSections) { foreach (var section in machoSections) {
@@ -188,7 +195,7 @@ namespace Il2CppInspector
// TODO: Implement Mach-O relocations // TODO: Implement Mach-O relocations
if (rels.Any()) { if (rels.Any()) {
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)"); AnsiConsole.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
break; break;
} }
} }
@@ -282,7 +289,7 @@ namespace Il2CppInspector
: SymbolType.Unknown; : SymbolType.Unknown;
if (type == SymbolType.Unknown) { if (type == SymbolType.Unknown) {
Console.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} " + CxxDemangler.CxxDemangler.Demangle(name)); AnsiConsole.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} {name}");
} }
// Ignore duplicates // Ignore duplicates
@@ -290,7 +297,82 @@ namespace Il2CppInspector
} }
} }
public override uint[] GetFunctionTable() => ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray(); private void ApplyChainedFixups(in MachOLinkEditDataCommand info)
{
var chainedFixupsHeader = ReadVersionedObject<MachODyldChainedFixupsHeader>(info.Offset);
if (chainedFixupsHeader.FixupsVersion != 0)
{
AnsiConsole.WriteLine($"Unsupported chained fixups version: {chainedFixupsHeader.FixupsVersion}");
return;
}
if (chainedFixupsHeader.ImportsFormat != 1 /* DYLD_CHAINED_IMPORT */)
{
AnsiConsole.WriteLine($"Unsupported chained fixups import format: {chainedFixupsHeader.ImportsFormat}");
return;
}
//var importsBase = info.Offset + chainedFixupsHeader.ImportsOffset;
//var imports = ReadPrimitiveArray<uint>(importsBase,
// chainedFixupsHeader.ImportsCount);
//var symbolsBase = info.Offset + chainedFixupsHeader.SymbolsOffset; // todo: apparently this supports zlib
var startsBase = info.Offset + chainedFixupsHeader.StartsOffset;
var segmentCount = ReadPrimitive<uint>(startsBase);
var segmentStartOffsets = ReadPrimitiveArray<uint>(startsBase + 4, segmentCount);
foreach (var startOffset in segmentStartOffsets)
{
if (startOffset == 0)
continue;
var startsInfo = ReadVersionedObject<MachODyldChainedStartsInSegment>(startsBase + startOffset);
if (startsInfo.SegmentOffset == 0)
continue;
var pointerFormat = (MachODyldChainedPtr)startsInfo.PointerFormat;
var pages = ReadPrimitiveArray<ushort>(
startsBase + startOffset + MachODyldChainedStartsInSegment.Size(), startsInfo.PageCount);
for (var i = 0; i < pages.Length; i++)
{
var page = pages[i];
if (page == MachODyldChainedStartsInSegment.DYLD_CHAINED_PTR_START_NONE)
continue;
var chainOffset = startsInfo.SegmentOffset + (ulong)(i * startsInfo.PageSize) + page;
while (true)
{
var currentEntry = ReadVersionedObject<MachODyldChainedPtr64Rebase>((long)chainOffset);
var fixedValue = 0ul;
if (!currentEntry.Bind) // todo: bind
{
fixedValue = pointerFormat switch
{
MachODyldChainedPtr.DYLD_CHAINED_PTR_64
or MachODyldChainedPtr.DYLD_CHAINED_PTR_64_OFFSET
=> currentEntry.High8 << 56 | currentEntry.Target,
_ => fixedValue
};
Write((long)chainOffset, fixedValue);
}
if (currentEntry.Next == 0)
break;
chainOffset += currentEntry.Next * 4;
}
}
}
}
public override uint[] GetFunctionTable() => funcTab == null ? [] : ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable; public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;

View File

@@ -4,13 +4,14 @@
All rights reserved. All rights reserved.
*/ */
using NoisyCowStudios.Bin2Object;
using Spectre.Console;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector namespace Il2CppInspector
{ {
@@ -102,7 +103,7 @@ namespace Il2CppInspector
// Unpacking must be done starting here, one byte after the end of the headers // 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 // Packed or previously packed with Themida? This is purely for information
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _) if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _)
Console.WriteLine("Themida protection detected"); AnsiConsole.WriteLine("Themida protection detected");
// Packed with anything (including Themida)? // Packed with anything (including Themida)?
mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null; mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null;
@@ -114,26 +115,31 @@ namespace Il2CppInspector
section.Name = wantedSectionTypes[section.Characteristics]; section.Name = wantedSectionTypes[section.Characteristics];
// Get base of code // Get base of code
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData; GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections
.FirstOrDefault(x => x.Characteristics.HasFlag(PE.IMAGE_SCN_MEM_EXECUTE))?.PointerToRawData ?? 0;
// Confirm that .rdata section begins at same place as IAT // Confirm that .rdata section begins at same place as IAT
var rData = sections.First(x => x.Name == ".rdata"); var rData = sections.FirstOrDefault(x => x.Name == ".rdata");
mightBePacked |= rData.VirtualAddress != IATStart; mightBePacked |= rData == null || rData.VirtualAddress != IATStart;
if (rData != null)
{
// Calculate start of function pointer table
pFuncTable = rData.PointerToRawData + IATSize;
// Calculate start of function pointer table // Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
pFuncTable = rData.PointerToRawData + IATSize; Position = pFuncTable;
if (pe is PEOptHeader32)
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset {
Position = pFuncTable; while (ReadUInt32() != 0)
if (pe is PEOptHeader32) { pFuncTable += 4;
while (ReadUInt32() != 0)
pFuncTable += 4; pFuncTable += 4;
pFuncTable += 4; }
} else
else { {
while (ReadUInt64() != 0) while (ReadUInt64() != 0)
pFuncTable += 8;
pFuncTable += 8; 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 // In the fist go round, we signal that this is at least a valid PE file; we don't try to unpack yet
@@ -192,6 +198,26 @@ namespace Il2CppInspector
return exports.Values; return exports.Values;
} }
public override bool TryMapVATR(ulong uiAddr, out uint fileOffset)
{
if (uiAddr == 0)
{
fileOffset = 0;
return true;
}
var section = sections.FirstOrDefault(x => uiAddr - pe.ImageBase >= x.VirtualAddress &&
uiAddr - pe.ImageBase < x.VirtualAddress + x.SizeOfRawData);
if (section == null)
{
fileOffset = 0;
return false;
}
fileOffset = (uint)(uiAddr - section.VirtualAddress - pe.ImageBase + section.PointerToRawData);
return true;
}
public override uint MapVATR(ulong uiAddr) { public override uint MapVATR(ulong uiAddr) {
if (uiAddr == 0) if (uiAddr == 0)
return 0; return 0;

View File

@@ -4,11 +4,12 @@
All rights reserved. All rights reserved.
*/ */
using NoisyCowStudios.Bin2Object;
using Spectre.Console;
using System; using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector namespace Il2CppInspector
{ {
@@ -100,7 +101,7 @@ namespace Il2CppInspector
// Get the entire remaining chunk, or to the end of the file if it doesn't contain the end of the chunk // 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); var length = (uint) Math.Min(chunk.Memory.End - memoryNext, source.Length);
Console.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}"); AnsiConsole.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}");
// Can't use Stream.CopyTo as it doesn't support length parameter // Can't use Stream.CopyTo as it doesn't support length parameter
var buffer = new byte[length]; var buffer = new byte[length];

View File

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

View File

@@ -6,12 +6,13 @@
*/ */
using Il2CppInspector.Next; using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Spectre.Console;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using VersionedSerialization; using VersionedSerialization;
namespace Il2CppInspector namespace Il2CppInspector
@@ -191,7 +192,7 @@ namespace Il2CppInspector
var symbols = Image.GetSymbolTable(); var symbols = Image.GetSymbolTable();
if (symbols.Any()) { if (symbols.Any()) {
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries"); AnsiConsole.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
symbols.TryGetValue("g_CodeRegistration", out var code); symbols.TryGetValue("g_CodeRegistration", out var code);
symbols.TryGetValue("g_MetadataRegistration", out var metadata); symbols.TryGetValue("g_MetadataRegistration", out var metadata);
@@ -202,13 +203,13 @@ namespace Il2CppInspector
symbols.TryGetValue("_g_MetadataRegistration", out metadata); symbols.TryGetValue("_g_MetadataRegistration", out metadata);
if (code != null && metadata != null) { if (code != null && metadata != null) {
Console.WriteLine("Required structures acquired from symbol lookup"); AnsiConsole.WriteLine("Required structures acquired from symbol lookup");
return (code.VirtualAddress, metadata.VirtualAddress); return (code.VirtualAddress, metadata.VirtualAddress);
} else { } else {
Console.WriteLine("No matches in symbol table"); AnsiConsole.WriteLine("No matches in symbol table");
} }
} else if (symbols != null) { } else if (symbols != null) {
Console.WriteLine("No symbol table present in binary file"); AnsiConsole.WriteLine("No symbol table present in binary file");
} else { } else {
Console.WriteLine("Symbol table search not implemented for this binary format"); Console.WriteLine("Symbol table search not implemented for this binary format");
} }
@@ -227,12 +228,12 @@ namespace Il2CppInspector
var (code, metadata) = ConsiderCode(Image, loc); var (code, metadata) = ConsiderCode(Image, loc);
if (code != 0) { if (code != 0) {
RegistrationFunctionPointer = loc + Image.GlobalOffset; RegistrationFunctionPointer = loc + Image.GlobalOffset;
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer); AnsiConsole.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
return (code, metadata); return (code, metadata);
} }
} }
Console.WriteLine("No matches via code heuristics"); AnsiConsole.WriteLine("No matches via code heuristics");
return null; return null;
} }
@@ -244,11 +245,11 @@ namespace Il2CppInspector
var (codePtr, metadataPtr) = ImageScan(Metadata); var (codePtr, metadataPtr) = ImageScan(Metadata);
if (codePtr == 0) { if (codePtr == 0) {
Console.WriteLine("No matches via data heuristics"); AnsiConsole.WriteLine("No matches via data heuristics");
return null; return null;
} }
Console.WriteLine("Required structures acquired from data heuristics"); AnsiConsole.WriteLine("Required structures acquired from data heuristics");
return (codePtr, metadataPtr); return (codePtr, metadataPtr);
} }
@@ -274,8 +275,8 @@ namespace Il2CppInspector
var pointerSize = Image.Bits == 32 ? 4u : 8u; var pointerSize = Image.Bits == 32 ? 4u : 8u;
Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration)); AnsiConsole.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration)); AnsiConsole.WriteLine("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 // Root structures from which we find everything else
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration); CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
@@ -295,8 +296,11 @@ namespace Il2CppInspector
*/ */
if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.TypeDefinitionsSizesCount) if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.TypeDefinitionsSizesCount)
|| CodeRegistration.ReversePInvokeWrapperCount > 0x10000 || CodeRegistration.ReversePInvokeWrapperCount > 0x10000
|| CodeRegistration.UnresolvedVirtualCallCount > 0x4000 // >= 22 // L-NOTE: These below boundaries have been updated already as some games
|| CodeRegistration.InteropDataCount > 0x1000 // >= 23 // have reached these limits during normal use. Maybe we should just remove them
// at this point?
|| CodeRegistration.UnresolvedVirtualCallCount > 0x8000 // >= 22
|| CodeRegistration.InteropDataCount > 0x2000 // >= 23
|| (Image.Version <= MetadataVersions.V241 && CodeRegistration.InvokerPointersCount > CodeRegistration.MethodPointersCount)) || (Image.Version <= MetadataVersions.V241 && CodeRegistration.InvokerPointersCount > CodeRegistration.MethodPointersCount))
throw new NotSupportedException("The detected Il2CppCodeRegistration / Il2CppMetadataRegistration structs do not pass validation. This may mean that their fields have been re-ordered as a form of obfuscation and Il2CppInspector has not been able to restore the original order automatically. Consider re-ordering the fields in Il2CppBinaryClasses.cs and try again."); throw new NotSupportedException("The detected Il2CppCodeRegistration / Il2CppMetadataRegistration structs do not pass validation. This may mean that their fields have been re-ordered as a form of obfuscation and Il2CppInspector has not been able to restore the original order automatically. Consider re-ordering the fields in Il2CppBinaryClasses.cs and try again.");
@@ -337,7 +341,15 @@ namespace Il2CppInspector
} }
// Read method invoker pointer indices - one per method // Read method invoker pointer indices - one per method
MethodInvokerIndices.Add(module, Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int) module.MethodPointerCount)); try
{
MethodInvokerIndices.Add(module,
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
}
catch (InvalidOperationException)
{
MethodInvokerIndices.Add(module, [..new int[(int)module.MethodPointerCount]]);
}
} }
} }
@@ -398,12 +410,21 @@ namespace Il2CppInspector
var type = TypeReferences[i]; var type = TypeReferences[i];
if (type.Type.IsTypeDefinitionEnum()) if (type.Type.IsTypeDefinitionEnum())
{ {
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize; 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()) else if (type.Type.IsGenericParameterEnum())
{ {
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize; 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); builder.Add(type);
} }
TypeReferences = builder.MoveToImmutable(); TypeReferences = builder.MoveToImmutable();

View File

@@ -5,8 +5,11 @@
*/ */
using Il2CppInspector.Next; using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Il2CppInspector.Utils; using Il2CppInspector.Utils;
using NoisyCowStudios.Bin2Object; using NoisyCowStudios.Bin2Object;
using Spectre.Console;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
@@ -14,8 +17,6 @@ using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using VersionedSerialization; using VersionedSerialization;
namespace Il2CppInspector namespace Il2CppInspector
@@ -84,7 +85,7 @@ namespace Il2CppInspector
return (0ul, null); return (0ul, null);
// Get pointer in binary to default value // Get pointer in binary to default value
var pValue = Metadata.Header.FieldAndParameterDefaultValueDataOffset + dataIndex; var pValue = Metadata.FieldAndParameterDefaultValueDataOffset + dataIndex;
var typeRef = TypeReferences[typeIndex]; var typeRef = TypeReferences[typeIndex];
// Default value is null // Default value is null
@@ -410,7 +411,7 @@ namespace Il2CppInspector
if (metadataFile != null) { if (metadataFile != null) {
// Extract the metadata file to memory // Extract the metadata file to memory
if (!silent) if (!silent)
Console.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}"); AnsiConsole.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
metadataMemoryStream = new MemoryStream(); metadataMemoryStream = new MemoryStream();
using var metadataStream = metadataFile.Open(); using var metadataStream = metadataFile.Open();
@@ -428,7 +429,7 @@ namespace Il2CppInspector
// IPAs will only have one binary (which may or may not be a UB covering multiple architectures) // IPAs will only have one binary (which may or may not be a UB covering multiple architectures)
if (ipaBinaryFolder != null) { if (ipaBinaryFolder != null) {
if (!silent) if (!silent)
Console.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}"); AnsiConsole.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
// Extract the binary file or package to memory // Extract the binary file or package to memory
binaryMemoryStream = new MemoryStream(); binaryMemoryStream = new MemoryStream();
@@ -531,7 +532,7 @@ namespace Il2CppInspector
return null; return null;
} }
Console.WriteLine("Detected metadata version " + metadata.Version); AnsiConsole.WriteLine("Detected metadata version " + metadata.Version);
// Load the il2cpp code file (try all available file formats) // Load the il2cpp code file (try all available file formats)
IFileFormatStream stream; IFileFormatStream stream;
@@ -559,16 +560,16 @@ namespace Il2CppInspector
var processors = new List<Il2CppInspector>(); var processors = new List<Il2CppInspector>();
foreach (var image in stream.Images) { foreach (var image in stream.Images) {
Console.WriteLine("Container format: " + image.Format); AnsiConsole.WriteLine("Container format: " + image.Format);
Console.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness); AnsiConsole.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness);
Console.WriteLine("Architecture word size: {0}-bit", image.Bits); AnsiConsole.WriteLine("Architecture word size: {0}-bit", image.Bits);
Console.WriteLine("Instruction set: " + image.Arch); AnsiConsole.WriteLine("Instruction set: " + image.Arch);
Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset); AnsiConsole.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);
// Architecture-agnostic load attempt // Architecture-agnostic load attempt
try { try {
if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) { if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) {
Console.WriteLine("IL2CPP binary version " + image.Version); AnsiConsole.WriteLine("IL2CPP binary version " + image.Version);
processors.Add(new Il2CppInspector(binary, metadata)); processors.Add(new Il2CppInspector(binary, metadata));
} }

View File

@@ -277,21 +277,12 @@ namespace Il2CppInspector
vas = FindAllMappedWords(imageBytes, typesLength).Select(a => a - mrSize + ptrSize * 4); vas = FindAllMappedWords(imageBytes, typesLength).Select(a => a - mrSize + ptrSize * 4);
// >= 19 && < 27 // >= 19
if (Image.Version < MetadataVersions.V270) // Luke: Previously, a check comparing MetadataUsagesCount was used here,
foreach (var va in vas) // but I know of at least one binary where this will break detection.
{ // Testing showed that we can just use the same heuristic used for v27+
var mr = Image.ReadMappedVersionedObject<Il2CppMetadataRegistration>(va); // on older versions as well, so we'll just use it for all cases.
if (mr.MetadataUsagesCount == (ulong) metadata.MetadataUsageLists.Length) if (Image.Version >= MetadataVersions.V190)
metadataRegistration = va;
}
// plagiarism. noun - https://www.lexico.com/en/definition/plagiarism
// the practice of taking someone else's work or ideas and passing them off as one's own.
// Synonyms: copying, piracy, theft, strealing, infringement of copyright
// >= 27
else
{ {
foreach (var va in vas) foreach (var va in vas)
{ {
@@ -304,6 +295,7 @@ namespace Il2CppInspector
} }
} }
} }
if (metadataRegistration == 0) if (metadataRegistration == 0)
return (0, 0); return (0, 0);

View File

@@ -5,15 +5,10 @@
All rights reserved. All rights reserved.
*/ */
using System;
using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO; using System.Runtime.CompilerServices;
using System.Linq;
using System.Reflection;
using Il2CppInspector.Next; using Il2CppInspector.Next;
using Il2CppInspector.Next.Metadata; using Il2CppInspector.Next.Metadata;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization; using VersionedSerialization;
namespace Il2CppInspector namespace Il2CppInspector
@@ -48,7 +43,15 @@ namespace Il2CppInspector
public ImmutableArray<uint> VTableMethodIndices { get; set; } public ImmutableArray<uint> VTableMethodIndices { get; set; }
public string[] StringLiterals { get; set; } public string[] StringLiterals { get; set; }
public Dictionary<int, string> Strings { get; private set; } = new Dictionary<int, string>(); public int FieldAndParameterDefaultValueDataOffset => Version >= MetadataVersions.V380
? Header.FieldAndParameterDefaultValueData.Offset
: Header.FieldAndParameterDefaultValueDataOffset;
public int AttributeDataOffset => Version >= MetadataVersions.V380
? Header.AttributeData.Offset
: Header.AttributeDataOffset;
public Dictionary<int, string> Strings { get; private set; } = [];
// Set if something in the metadata has been modified / decrypted // Set if something in the metadata has been modified / decrypted
public bool IsModified { get; private set; } = false; public bool IsModified { get; private set; } = false;
@@ -92,13 +95,63 @@ namespace Il2CppInspector
// Set object versioning for Bin2Object from metadata version // Set object versioning for Bin2Object from metadata version
Version = new StructVersion(Header.Version); Version = new StructVersion(Header.Version);
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) { if (Version < MetadataVersions.V160 || Version > MetadataVersions.V390) {
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version})."); throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version}).");
} }
// Rewind and read metadata header with the correct version settings // Rewind and read metadata header with the correct version settings
Header = ReadVersionedObject<Il2CppGlobalMetadataHeader>(0); Header = ReadVersionedObject<Il2CppGlobalMetadataHeader>(0);
// Setup the proper index sizes for metadata v38+
if (Version >= MetadataVersions.V380)
{
static int GetIndexSize(int elementCount)
{
return elementCount switch
{
<= byte.MaxValue => sizeof(byte),
<= ushort.MaxValue => sizeof(ushort),
_ => sizeof(int)
};
}
var typeDefinitionIndexSize = GetIndexSize(Header.TypeDefinitions.Count);
var genericContainerIndexSize = GetIndexSize(Header.GenericContainers.Count);
var tag = $"{TypeDefinitionIndex.TagPrefix}{typeDefinitionIndexSize}"
+ $"_{GenericContainerIndex.TagPrefix}{genericContainerIndexSize}";
var tempVersion = new StructVersion(Version.Major, Version.Minor, tag);
// now we need to derive the size for TypeIndex.
// this is normally done through s_Il2CppMetadataRegistration->typesCount, but we don't want to use the binary for this
// as we do not have it available at this point.
// thankfully, we can just guess the size based off the three available options and the known total size of
// a type entry that uses TypeIndex.
var expectedEventDefinitionSize = Header.Events.SectionSize / Header.Events.Count;
var maxEventDefinitionSize = Il2CppEventDefinition.Size(tempVersion);
int typeIndexSize;
if (expectedEventDefinitionSize == maxEventDefinitionSize)
typeIndexSize = sizeof(int);
else if (expectedEventDefinitionSize == maxEventDefinitionSize - 2)
typeIndexSize = sizeof(ushort);
else if (expectedEventDefinitionSize == maxEventDefinitionSize - 3)
typeIndexSize = sizeof(byte);
else
throw new InvalidOperationException("Could not determine TypeIndex size based on the metadata header");
var fullTag = $"{tag}_{TypeIndex.TagPrefix}{typeIndexSize}";
if (Version >= MetadataVersions.V390)
{
var parameterIndexSize = GetIndexSize(Header.Parameters.Count);
fullTag += $"_{ParameterIndex.TagPrefix}{parameterIndexSize}";
}
Version = new StructVersion(Version.Major, Version.Minor, fullTag);
}
// Sanity checking // Sanity checking
// Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata always writes the metadata information in the same order it appears in the header, // Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata always writes the metadata information in the same order it appears in the header,
// with each block always coming directly after the previous block, 4-byte aligned. We can use this to check the integrity of the data and // with each block always coming directly after the previous block, 4-byte aligned. We can use this to check the integrity of the data and
@@ -108,8 +161,11 @@ namespace Il2CppInspector
// in the header after the sanity and version fields, and since it will always point directly to the first byte after the end of the header, // in the header after the sanity and version fields, and since it will always point directly to the first byte after the end of the header,
// we can use this value to determine the actual header length and therefore narrow down the metadata version to 24.0/24.1 or 24.2. // we can use this value to determine the actual header length and therefore narrow down the metadata version to 24.0/24.1 or 24.2.
if (!pluginResult.SkipValidation) { if (!pluginResult.SkipValidation)
var realHeaderLength = Header.StringLiteralOffset; {
var realHeaderLength = Version >= MetadataVersions.V380
? Header.StringLiterals.Offset
: Header.StringLiteralOffset;
if (realHeaderLength != Sizeof<Il2CppGlobalMetadataHeader>()) { if (realHeaderLength != Sizeof<Il2CppGlobalMetadataHeader>()) {
if (Version == MetadataVersions.V240) { if (Version == MetadataVersions.V240) {
@@ -125,7 +181,7 @@ namespace Il2CppInspector
// Load all the relevant metadata using offsets provided in the header // Load all the relevant metadata using offsets provided in the header
if (Version >= MetadataVersions.V160) if (Version >= MetadataVersions.V160)
Images = ReadVersionedObjectArray<Il2CppImageDefinition>(Header.ImagesOffset, Header.ImagesSize / Sizeof<Il2CppImageDefinition>()); Images = ReadMetadataArray<Il2CppImageDefinition>(Header.ImagesOffset, Header.ImagesSize, Header.Images);
// As an additional sanity check, all images in the metadata should have Mono.Cecil.MetadataToken == 1 // As an additional sanity check, all images in the metadata should have Mono.Cecil.MetadataToken == 1
// In metadata v24.1, two extra fields were added which will cause the below test to fail. // In metadata v24.1, two extra fields were added which will cause the below test to fail.
@@ -136,28 +192,29 @@ namespace Il2CppInspector
Version = MetadataVersions.V241; Version = MetadataVersions.V241;
// No need to re-read the header, it's the same for both sub-versions // No need to re-read the header, it's the same for both sub-versions
Images = ReadVersionedObjectArray<Il2CppImageDefinition>(Header.ImagesOffset, Header.ImagesSize / Sizeof<Il2CppImageDefinition>()); Images = ReadMetadataArray<Il2CppImageDefinition>(Header.ImagesOffset, Header.ImagesSize, Header.Images);
if (Images.Any(x => x.Token != 1)) if (Images.Any(x => x.Token != 1))
throw new InvalidOperationException("Could not verify the integrity of the metadata file image list"); throw new InvalidOperationException("Could not verify the integrity of the metadata file image list");
} }
Types = ReadVersionedObjectArray<Il2CppTypeDefinition>(Header.TypeDefinitionsOffset, Header.TypeDefinitionsSize / Sizeof<Il2CppTypeDefinition>()); Types = ReadMetadataArray<Il2CppTypeDefinition>(Header.TypeDefinitionsOffset, Header.TypeDefinitionsSize, Header.TypeDefinitions);
Methods = ReadVersionedObjectArray<Il2CppMethodDefinition>(Header.MethodsOffset, Header.MethodsSize / Sizeof<Il2CppMethodDefinition>()); Methods = ReadMetadataArray<Il2CppMethodDefinition>(Header.MethodsOffset, Header.MethodsSize, Header.Methods);
Params = ReadVersionedObjectArray<Il2CppParameterDefinition>(Header.ParametersOffset, Header.ParametersSize / Sizeof<Il2CppParameterDefinition>()); Params = ReadMetadataArray<Il2CppParameterDefinition>(Header.ParametersOffset, Header.ParametersSize, Header.Parameters);
Fields = ReadVersionedObjectArray<Il2CppFieldDefinition>(Header.FieldsOffset, Header.FieldsSize / Sizeof<Il2CppFieldDefinition>()); Fields = ReadMetadataArray<Il2CppFieldDefinition>(Header.FieldsOffset, Header.FieldsSize, Header.Fields);
FieldDefaultValues = ReadVersionedObjectArray<Il2CppFieldDefaultValue>(Header.FieldDefaultValuesOffset, Header.FieldDefaultValuesSize / Sizeof<Il2CppFieldDefaultValue>()); FieldDefaultValues = ReadMetadataArray<Il2CppFieldDefaultValue>(Header.FieldDefaultValuesOffset, Header.FieldDefaultValuesSize, Header.FieldDefaultValues);
Properties = ReadVersionedObjectArray<Il2CppPropertyDefinition>(Header.PropertiesOffset, Header.PropertiesSize / Sizeof<Il2CppPropertyDefinition>()); Properties = ReadMetadataArray<Il2CppPropertyDefinition>(Header.PropertiesOffset, Header.PropertiesSize, Header.Properties);
Events = ReadVersionedObjectArray<Il2CppEventDefinition>(Header.EventsOffset, Header.EventsSize / Sizeof<Il2CppEventDefinition>()); Events = ReadMetadataArray<Il2CppEventDefinition>(Header.EventsOffset, Header.EventsSize, Header.Events);
InterfaceUsageIndices = ReadPrimitiveArray<int>(Header.InterfacesOffset, Header.InterfacesSize / sizeof(int)); InterfaceUsageIndices = ReadMetadataPrimitiveArray<int>(Header.InterfacesOffset, Header.InterfacesSize, Header.Interfaces);
NestedTypeIndices = ReadPrimitiveArray<int>(Header.NestedTypesOffset, Header.NestedTypesSize / sizeof(int)); NestedTypeIndices = ReadMetadataPrimitiveArray<int>(Header.NestedTypesOffset, Header.NestedTypesSize, Header.NestedTypes);
GenericContainers = ReadVersionedObjectArray<Il2CppGenericContainer>(Header.GenericContainersOffset, Header.GenericContainersSize / Sizeof<Il2CppGenericContainer>()); GenericContainers = ReadMetadataArray<Il2CppGenericContainer>(Header.GenericContainersOffset, Header.GenericContainersSize, Header.GenericContainers);
GenericParameters = ReadVersionedObjectArray<Il2CppGenericParameter>(Header.GenericParametersOffset, Header.GenericParametersSize / Sizeof<Il2CppGenericParameter>()); GenericParameters = ReadMetadataArray<Il2CppGenericParameter>(Header.GenericParametersOffset, Header.GenericParametersSize, Header.GenericParameters);
GenericConstraintIndices = ReadPrimitiveArray<int>(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize / sizeof(int)); GenericConstraintIndices = ReadMetadataPrimitiveArray<int>(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize, Header.GenericParameterConstraints);
InterfaceOffsets = ReadVersionedObjectArray<Il2CppInterfaceOffsetPair>(Header.InterfaceOffsetsOffset, Header.InterfaceOffsetsSize / Sizeof<Il2CppInterfaceOffsetPair>()); InterfaceOffsets = ReadMetadataArray<Il2CppInterfaceOffsetPair>(Header.InterfaceOffsetsOffset, Header.InterfaceOffsetsSize, Header.InterfaceOffsets);
VTableMethodIndices = ReadPrimitiveArray<uint>(Header.VTableMethodsOffset, Header.VTableMethodsSize / sizeof(uint)); VTableMethodIndices = ReadMetadataPrimitiveArray<uint>(Header.VTableMethodsOffset, Header.VTableMethodsSize, Header.VtableMethods);
if (Version >= MetadataVersions.V160) { if (Version >= MetadataVersions.V160)
{
// In v24.4 hashValueIndex was removed from Il2CppAssemblyNameDefinition, which is a field in Il2CppAssemblyDefinition // In v24.4 hashValueIndex was removed from Il2CppAssemblyNameDefinition, which is a field in Il2CppAssemblyDefinition
// The number of images and assemblies should be the same. If they are not, we deduce that we are using v24.4 // The number of images and assemblies should be the same. If they are not, we deduce that we are using v24.4
// Note the version comparison matches both 24.2 and 24.3 here since 24.3 is tested for during binary loading // Note the version comparison matches both 24.2 and 24.3 here since 24.3 is tested for during binary loading
@@ -167,32 +224,39 @@ namespace Il2CppInspector
{ {
if (Version == MetadataVersions.V241) if (Version == MetadataVersions.V241)
changedAssemblyDefStruct = true; changedAssemblyDefStruct = true;
Version = MetadataVersions.V244; Version = MetadataVersions.V244;
} }
Assemblies = ReadVersionedObjectArray<Il2CppAssemblyDefinition>(Header.AssembliesOffset, Images.Length); Assemblies = ReadMetadataArray<Il2CppAssemblyDefinition>(Header.AssembliesOffset, Header.AssembliesSize, Header.Assemblies);
if (changedAssemblyDefStruct) if (changedAssemblyDefStruct)
Version = MetadataVersions.V241; Version = MetadataVersions.V241;
ParameterDefaultValues = ReadVersionedObjectArray<Il2CppParameterDefaultValue>(Header.ParameterDefaultValuesOffset, Header.ParameterDefaultValuesSize / Sizeof<Il2CppParameterDefaultValue>()); ParameterDefaultValues = ReadMetadataArray<Il2CppParameterDefaultValue>(Header.ParameterDefaultValuesOffset, Header.ParameterDefaultValuesSize, Header.ParameterDefaultValues);
} }
if (Version >= MetadataVersions.V190 && Version < MetadataVersions.V270) {
MetadataUsageLists = ReadVersionedObjectArray<Il2CppMetadataUsageList>(Header.MetadataUsageListsOffset, Header.MetadataUsageListsCount / Sizeof<Il2CppMetadataUsageList>()); if (Version >= MetadataVersions.V190 && Version < MetadataVersions.V270)
MetadataUsagePairs = ReadVersionedObjectArray<Il2CppMetadataUsagePair>(Header.MetadataUsagePairsOffset, Header.MetadataUsagePairsCount / Sizeof<Il2CppMetadataUsagePair>()); {
MetadataUsageLists = ReadMetadataArray<Il2CppMetadataUsageList>(Header.MetadataUsageListsOffset, Header.MetadataUsageListsCount, default);
MetadataUsagePairs = ReadMetadataArray<Il2CppMetadataUsagePair>(Header.MetadataUsagePairsOffset, Header.MetadataUsagePairsCount, default);
} }
if (Version >= MetadataVersions.V190) {
FieldRefs = ReadVersionedObjectArray<Il2CppFieldRef>(Header.FieldRefsOffset, Header.FieldRefsSize / Sizeof<Il2CppFieldRef>()); if (Version >= MetadataVersions.V190)
{
FieldRefs = ReadMetadataArray<Il2CppFieldRef>(Header.FieldRefsOffset, Header.FieldRefsSize, Header.FieldRefs);
} }
if (Version >= MetadataVersions.V210 && Version < MetadataVersions.V290) {
AttributeTypeIndices = ReadPrimitiveArray<int>(Header.AttributesTypesOffset, Header.AttributesTypesCount / sizeof(int)); if (Version >= MetadataVersions.V210 && Version < MetadataVersions.V290)
AttributeTypeRanges = ReadVersionedObjectArray<Il2CppCustomAttributeTypeRange>(Header.AttributesInfoOffset, Header.AttributesInfoCount / Sizeof<Il2CppCustomAttributeTypeRange>()); {
AttributeTypeIndices = ReadMetadataPrimitiveArray<int>(Header.AttributesTypesOffset, Header.AttributesTypesCount, default);
AttributeTypeRanges = ReadMetadataArray<Il2CppCustomAttributeTypeRange>(Header.AttributesInfoOffset, Header.AttributesInfoCount, default);
} }
if (Version >= MetadataVersions.V290) if (Version >= MetadataVersions.V290)
{ {
AttributeDataRanges = ReadVersionedObjectArray<Il2CppCustomAttributeDataRange>(Header.AttributeDataRangeOffset, AttributeDataRanges = ReadMetadataArray<Il2CppCustomAttributeDataRange>(Header.AttributeDataRangeOffset,
Header.AttributeDataRangeSize / Sizeof<Il2CppCustomAttributeDataRange>()); Header.AttributeDataRangeSize, Header.AttributeDataRanges);
} }
// Get all metadata strings // Get all metadata strings
@@ -201,10 +265,17 @@ namespace Il2CppInspector
Strings = pluginGetStringsResult.Strings; Strings = pluginGetStringsResult.Strings;
else { else {
Position = Header.StringOffset; var stringOffset = Version >= MetadataVersions.V380
? Header.Strings.Offset
: Header.StringOffset;
var stringLength = Version >= MetadataVersions.V380
? Header.Strings.SectionSize
: Header.StringSize;
while (Position < Header.StringOffset + Header.StringSize) Position = stringOffset;
Strings.Add((int) Position - Header.StringOffset, ReadNullTerminatedString());
while (Position < stringOffset + stringLength)
Strings.Add((int)Position - stringOffset, ReadNullTerminatedString());
} }
// Get all string literals // Get all string literals
@@ -212,23 +283,65 @@ namespace Il2CppInspector
if (pluginGetStringLiteralsResult.IsDataModified) if (pluginGetStringLiteralsResult.IsDataModified)
StringLiterals = pluginGetStringLiteralsResult.StringLiterals.ToArray(); StringLiterals = pluginGetStringLiteralsResult.StringLiterals.ToArray();
else { else
var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>()); {
var stringLiteralList = ReadMetadataArray<Il2CppStringLiteral>(Header.StringLiteralOffset,
Header.StringLiteralSize, Header.StringLiterals);
StringLiterals = new string[stringLiteralList.Length]; var dataOffset = Version >= MetadataVersions.V380
for (var i = 0; i < stringLiteralList.Length; i++) ? Header.StringLiteralData.Offset
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length); : Header.StringLiteralDataOffset;
if (Version >= MetadataVersions.V350)
{
var literals = new string[stringLiteralList.Length - 1];
for (var i = 0; i < literals.Length; i++)
{
var currentStringDataIndex = stringLiteralList[i].DataIndex;
var nextStringDataIndex = stringLiteralList[i + 1].DataIndex;
var stringLength = nextStringDataIndex - currentStringDataIndex;
literals[i] = ReadFixedLengthString(dataOffset + currentStringDataIndex, stringLength);
}
StringLiterals = literals;
}
else
{
StringLiterals = new string[stringLiteralList.Length];
for (var i = 0; i < stringLiteralList.Length; i++)
StringLiterals[i] = ReadFixedLengthString(dataOffset + stringLiteralList[i].DataIndex,
(int)stringLiteralList[i].Length);
}
} }
// Post-processing hook // Post-processing hook
IsModified |= PluginHooks.PostProcessMetadata(this).IsStreamModified; IsModified |= PluginHooks.PostProcessMetadata(this).IsStreamModified;
return;
}
public ImmutableArray<T> ReadMetadataPrimitiveArray<T>(int oldOffset, int oldSize, Il2CppSectionMetadata newMetadata)
where T : unmanaged
{
return Version >= MetadataVersions.V380
? ReadPrimitiveArray<T>(newMetadata.Offset, newMetadata.Count)
: ReadPrimitiveArray<T>(oldOffset, oldSize / Unsafe.SizeOf<T>());
}
public ImmutableArray<T> ReadMetadataArray<T>(int oldOffset, int oldSize, Il2CppSectionMetadata newMetadata)
where T : IReadable, new()
{
return Version >= MetadataVersions.V380
? ReadVersionedObjectArray<T>(newMetadata.Offset, newMetadata.Count)
: ReadVersionedObjectArray<T>(oldOffset, oldSize / Sizeof<T>());
} }
// Save metadata to file, overwriting if necessary // Save metadata to file, overwriting if necessary
public void SaveToFile(string pathname) { public void SaveToFile(string pathname) {
Position = 0; Position = 0;
using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write)) using var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write);
CopyTo(outFile); CopyTo(outFile);
} }
public int Sizeof<T>() where T : IReadable => T.Size(Version, Is32Bit); public int Sizeof<T>() where T : IReadable => T.Size(Version, Is32Bit);

View File

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

View File

@@ -14,6 +14,7 @@ using Il2CppInspector.Cpp;
using Il2CppInspector.Cpp.UnityHeaders; using Il2CppInspector.Cpp.UnityHeaders;
using Il2CppInspector.Next; using Il2CppInspector.Next;
using Il2CppInspector.Reflection; using Il2CppInspector.Reflection;
using Spectre.Console;
namespace Il2CppInspector.Model namespace Il2CppInspector.Model
{ {
@@ -40,6 +41,9 @@ namespace Il2CppInspector.Model
// The types are ordered to enable the production of code output without forward dependencies // The types are ordered to enable the production of code output without forward dependencies
public List<CppType> DependencyOrderedCppTypes { get; private set; } 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 // Composite mapping of all the .NET methods in the IL2CPP binary
public MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod> Methods { get; } = new MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod>(); public MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod> Methods { get; } = new MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod>();
@@ -149,12 +153,12 @@ namespace Il2CppInspector.Model
UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last(); UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last();
UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min; UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min;
Console.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})"); AnsiConsole.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})");
// Check for matching metadata and binary versions // Check for matching metadata and binary versions
if (UnityHeaders.MetadataVersion != Image.Version) { if (UnityHeaders.MetadataVersion != Image.Version) {
Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" + AnsiConsole.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
$" does not match metadata version {Image.Version}."); $" does not match metadata version {Image.Version}.");
} }
// Initialize declaration generator to process every type in the binary // Initialize declaration generator to process every type in the binary
@@ -236,8 +240,18 @@ namespace Il2CppInspector.Model
break; break;
case MetadataUsageType.MethodDef or MetadataUsageType.MethodRef: case MetadataUsageType.MethodDef or MetadataUsageType.MethodRef:
var method = TypeModel.GetMetadataUsageMethod(usage); var method = TypeModel.GetMetadataUsageMethod(usage);
declarationGenerator.IncludeMethod(method); declarationGenerator.IncludeMethod(method);
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); var definitions = declarationGenerator.GenerateRemainingTypeDeclarations();
if (definitions == null)
{
// if we end up here, type generation has failed
// todo: this try/catch is a massive hack to sidestep the original issue of generation failing,
// todo: this needs to be improved.
break;
}
AddTypes(definitions);
// Any method here SHOULD already be in the Methods list // 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 // but we have seen one example where this is not the case for a MethodDef
@@ -247,6 +261,7 @@ namespace Il2CppInspector.Model
Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) { Group = Group }); Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) { Group = Group });
} }
Methods[method].MethodInfoPtrAddress = address; Methods[method].MethodInfoPtrAddress = address;
break; break;
// FieldInfo is used for array initializers. // FieldInfo is used for array initializers.
@@ -294,6 +309,8 @@ namespace Il2CppInspector.Model
declarationGenerator.IncludeType(type); declarationGenerator.IncludeType(type);
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations()); AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
RequiredForwardDefinitions = declarationGenerator.GenerateRequiredForwardDefinitions();
// Restore stdout // Restore stdout
Console.SetOut(stdout); Console.SetOut(stdout);

View File

@@ -77,6 +77,7 @@ public partial record struct Il2CppCodeRegistration
[NativeInteger] [NativeInteger]
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")] [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(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
[VersionCondition(GreaterThan = "35.0")]
public uint UnresolvedIndirectCallCount; // UnresolvedVirtualCallCount pre 29.1 public uint UnresolvedIndirectCallCount; // UnresolvedVirtualCallCount pre 29.1
[VersionCondition(GreaterThan = "22.0")] [VersionCondition(GreaterThan = "22.0")]
@@ -84,10 +85,12 @@ public partial record struct Il2CppCodeRegistration
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")] [VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")] [VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
[VersionCondition(GreaterThan = "35.0")]
public Pointer<Il2CppMethodPointer> UnresolvedInstanceCallWrappers; public Pointer<Il2CppMethodPointer> UnresolvedInstanceCallWrappers;
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")] [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(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
[VersionCondition(GreaterThan = "35.0")]
public Pointer<Il2CppMethodPointer> UnresolvedStaticCallPointers; public Pointer<Il2CppMethodPointer> UnresolvedStaticCallPointers;
[NativeInteger] [NativeInteger]

View File

@@ -0,0 +1,45 @@
using VersionedSerialization;
namespace Il2CppInspector.Next.Metadata;
public struct GenericContainerIndex(int value) : IIndexType<GenericContainerIndex>, IReadable, IEquatable<GenericContainerIndex>
{
public const string TagPrefix = nameof(GenericContainerIndex);
static string IIndexType<GenericContainerIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<GenericContainerIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static int Size(in StructVersion version = default, bool is32Bit = false)
=> IIndexType<GenericContainerIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
_value = IIndexType<GenericContainerIndex>.ReadIndex(ref reader, in version);
}
#region Operators + ToString
public static implicit operator int(GenericContainerIndex idx) => idx._value;
public static implicit operator GenericContainerIndex(int idx) => new(idx);
public static bool operator ==(GenericContainerIndex left, GenericContainerIndex right)
=> left._value == right._value;
public static bool operator !=(GenericContainerIndex left, GenericContainerIndex right)
=> !(left == right);
public readonly override bool Equals(object obj)
=> obj is GenericContainerIndex other && Equals(other);
public readonly bool Equals(GenericContainerIndex other)
=> this == other;
public readonly override int GetHashCode()
=> HashCode.Combine(_value);
public readonly override string ToString() => _value.ToString();
#endregion
}

View File

@@ -0,0 +1,55 @@
using VersionedSerialization;
namespace Il2CppInspector.Next.Metadata;
public interface IIndexType<T> where T
: IIndexType<T>, allows ref struct
{
public static abstract string TagPrefix { get; }
public static abstract StructVersion AddedVersion { get; }
private static string TagSize4 => $"{T.TagPrefix}4";
private static string TagSize2 => $"{T.TagPrefix}2";
private static string TagSize1 => $"{T.TagPrefix}1";
private static bool HasCustomSize(in StructVersion version)
=> version >= T.AddedVersion
&& version.Tag != null
&& version.Tag.Contains(T.TagPrefix)
&& !version.Tag.Contains(TagSize4);
public static int IndexSize(in StructVersion version = default, bool is32Bit = false)
{
if (version.Tag != null && HasCustomSize(version))
{
if (version.Tag.Contains(TagSize2))
return sizeof(ushort);
if (version.Tag.Contains(TagSize1))
return sizeof(byte);
}
return sizeof(int);
}
public static int ReadIndex<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
if (version.Tag != null && HasCustomSize(version))
{
if (version.Tag.Contains(TagSize2))
{
var value = reader.ReadPrimitive<ushort>();
return value == ushort.MaxValue ? -1 : value;
}
if (version.Tag.Contains(TagSize1))
{
var value = reader.ReadPrimitive<byte>();
return value == byte.MaxValue ? -1 : value;
}
}
return reader.ReadPrimitive<int>();
}
}

View File

@@ -20,6 +20,10 @@ public partial record struct Il2CppAssemblyDefinition
[VersionCondition(GreaterThan = "24.1")] [VersionCondition(GreaterThan = "24.1")]
public uint Token; public uint Token;
[FieldOffset(20 + 52)]
[VersionCondition(GreaterThan = "38.0")]
public uint ModuleToken;
[FieldOffset(8)] [FieldOffset(8)]
[VersionCondition(LessThan = "24.0")] [VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex; public int CustomAttributeIndex;

View File

@@ -1,7 +1,6 @@
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using StringIndex = int; using StringIndex = int;
using TypeIndex = int;
using MethodIndex = int; using MethodIndex = int;
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;

View File

@@ -3,7 +3,6 @@
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using FieldIndex = int; using FieldIndex = int;
using TypeIndex = int;
using DefaultValueDataIndex = int; using DefaultValueDataIndex = int;
[VersionedStruct] [VersionedStruct]

View File

@@ -2,7 +2,6 @@
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;
using StringIndex = int; using StringIndex = int;
using TypeIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppFieldDefinition public partial record struct Il2CppFieldDefinition

View File

@@ -2,7 +2,6 @@
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;
using FieldIndex = int; using FieldIndex = int;
using TypeIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppFieldMarshaledSize public partial record struct Il2CppFieldMarshaledSize

View File

@@ -3,7 +3,6 @@
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using FieldIndex = int; using FieldIndex = int;
using TypeIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppFieldRef public partial record struct Il2CppFieldRef

View File

@@ -3,7 +3,6 @@ using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using GenericContainerIndex = int;
using StringIndex = int; using StringIndex = int;
using GenericParameterConstraintIndex = short; using GenericParameterConstraintIndex = short;

View File

@@ -29,55 +29,127 @@ public partial record struct Il2CppGlobalMetadataHeader
{ {
public int Sanity { get; private set; } public int Sanity { get; private set; }
public int Version { get; private set; } public int Version { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralOffset { get; private set; } public int StringLiteralOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralSize { get; private set; } public int StringLiteralSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralDataOffset { get; private set; } public int StringLiteralDataOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralDataSize { get; private set; } public int StringLiteralDataSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringOffset { get; private set; } public int StringOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringSize { get; private set; } public int StringSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int EventsOffset { get; private set; } public int EventsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int EventsSize { get; private set; } public int EventsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int PropertiesOffset { get; private set; } public int PropertiesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int PropertiesSize { get; private set; } public int PropertiesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int MethodsOffset { get; private set; } public int MethodsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int MethodsSize { get; private set; } public int MethodsSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
[VersionCondition(EqualTo = "16.0")] [VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesOffset { get; private set; } public int ParameterDefaultValuesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
[VersionCondition(EqualTo = "16.0")] [VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesSize { get; private set; } public int ParameterDefaultValuesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldDefaultValuesOffset { get; private set; } public int FieldDefaultValuesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldDefaultValuesSize { get; private set; } public int FieldDefaultValuesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldAndParameterDefaultValueDataOffset { get; private set; } public int FieldAndParameterDefaultValueDataOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldAndParameterDefaultValueDataSize { get; private set; } public int FieldAndParameterDefaultValueDataSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int FieldMarshaledSizesOffset { get; private set; } public int FieldMarshaledSizesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int FieldMarshaledSizesSize { get; private set; } public int FieldMarshaledSizesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int ParametersOffset { get; private set; } public int ParametersOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int ParametersSize { get; private set; } public int ParametersSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldsOffset { get; private set; } public int FieldsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldsSize { get; private set; } public int FieldsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParametersOffset { get; private set; } public int GenericParametersOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParametersSize { get; private set; } public int GenericParametersSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParameterConstraintsOffset { get; private set; } public int GenericParameterConstraintsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParameterConstraintsSize { get; private set; } public int GenericParameterConstraintsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericContainersOffset { get; private set; } public int GenericContainersOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericContainersSize { get; private set; } public int GenericContainersSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int NestedTypesOffset { get; private set; } public int NestedTypesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int NestedTypesSize { get; private set; } public int NestedTypesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfacesOffset { get; private set; } public int InterfacesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfacesSize { get; private set; } public int InterfacesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int VTableMethodsOffset { get; private set; } public int VTableMethodsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int VTableMethodsSize { get; private set; } public int VTableMethodsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfaceOffsetsOffset { get; private set; } public int InterfaceOffsetsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfaceOffsetsSize { get; private set; } public int InterfaceOffsetsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int TypeDefinitionsOffset { get; private set; } public int TypeDefinitionsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int TypeDefinitionsSize { get; private set; } public int TypeDefinitionsSize { get; private set; }
[VersionCondition(LessThan = "24.1")] [VersionCondition(LessThan = "24.1")]
@@ -86,16 +158,16 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(LessThan = "24.1")] [VersionCondition(LessThan = "24.1")]
public int RgctxEntriesCount { get; private set; } public int RgctxEntriesCount { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int ImagesOffset { get; private set; } public int ImagesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int ImagesSize { get; private set; } public int ImagesSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int AssembliesOffset { get; private set; } public int AssembliesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")] [VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int AssembliesSize { get; private set; } public int AssembliesSize { get; private set; }
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")] [VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
@@ -110,16 +182,16 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")] [VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
public int MetadataUsagePairsCount { get; private set; } public int MetadataUsagePairsCount { get; private set; }
[VersionCondition(GreaterThan = "19.0")] [VersionCondition(GreaterThan = "19.0", LessThan = "35.0")]
public int FieldRefsOffset { get; private set; } public int FieldRefsOffset { get; private set; }
[VersionCondition(GreaterThan = "19.0")] [VersionCondition(GreaterThan = "19.0", LessThan = "35.0")]
public int FieldRefsSize { get; private set; } public int FieldRefsSize { get; private set; }
[VersionCondition(GreaterThan = "20.0")] [VersionCondition(GreaterThan = "20.0", LessThan = "35.0")]
public int ReferencedAssembliesOffset { get; private set; } public int ReferencedAssembliesOffset { get; private set; }
[VersionCondition(GreaterThan = "20.0")] [VersionCondition(GreaterThan = "20.0", LessThan = "35.0")]
public int ReferencedAssembliesSize { get; private set; } public int ReferencedAssembliesSize { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")] [VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
@@ -134,48 +206,143 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")] [VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
public int AttributesTypesCount { get; private set; } public int AttributesTypesCount { get; private set; }
[VersionCondition(GreaterThan = "29.0")] [VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataOffset { get; private set; } public int AttributeDataOffset { get; private set; }
[VersionCondition(GreaterThan = "29.0")] [VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataSize { get; private set; } public int AttributeDataSize { get; private set; }
[VersionCondition(GreaterThan = "29.0")] [VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataRangeOffset { get; private set; } public int AttributeDataRangeOffset { get; private set; }
[VersionCondition(GreaterThan = "29.0")] [VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataRangeSize { get; private set; } public int AttributeDataRangeSize { get; private set; }
[VersionCondition(GreaterThan = "22.0")] [VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterTypesOffset { get; private set; } public int UnresolvedIndirectCallParameterTypesOffset { get; private set; }
[VersionCondition(GreaterThan = "22.0")] [VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterTypesSize { get; private set; } public int UnresolvedIndirectCallParameterTypesSize { get; private set; }
[VersionCondition(GreaterThan = "22.0")] [VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterRangesOffset { get; private set; } public int UnresolvedIndirectCallParameterRangesOffset { get; private set; }
[VersionCondition(GreaterThan = "22.0")] [VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterRangesSize { get; private set; } public int UnresolvedIndirectCallParameterRangesSize { get; private set; }
[VersionCondition(GreaterThan = "23.0")] [VersionCondition(GreaterThan = "23.0", LessThan = "35.0")]
public int WindowsRuntimeTypeNamesOffset { get; private set; } public int WindowsRuntimeTypeNamesOffset { get; private set; }
[VersionCondition(GreaterThan = "23.0")] [VersionCondition(GreaterThan = "23.0", LessThan = "35.0")]
public int WindowsRuntimeTypeNamesSize { get; private set; } public int WindowsRuntimeTypeNamesSize { get; private set; }
[VersionCondition(GreaterThan = "27.0")] [VersionCondition(GreaterThan = "27.0", LessThan = "35.0")]
public int WindowsRuntimeStringsOffset { get; private set; } public int WindowsRuntimeStringsOffset { get; private set; }
[VersionCondition(GreaterThan = "27.0")] [VersionCondition(GreaterThan = "27.0", LessThan = "35.0")]
public int WindowsRuntimeStringsSize { get; private set; } public int WindowsRuntimeStringsSize { get; private set; }
[VersionCondition(GreaterThan = "24.0")] [VersionCondition(GreaterThan = "24.0", LessThan = "35.0")]
public int ExportedTypeDefinitionsOffset { get; private set; } public int ExportedTypeDefinitionsOffset { get; private set; }
[VersionCondition(GreaterThan = "24.0")] [VersionCondition(GreaterThan = "24.0", LessThan = "35.0")]
public int ExportedTypeDefinitionsSize { get; private set; } public int ExportedTypeDefinitionsSize { get; private set; }
// new, v38 metadata sections
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata StringLiterals { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata StringLiteralData { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Strings { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Events { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Properties { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Methods { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata ParameterDefaultValues { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata FieldDefaultValues { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata FieldAndParameterDefaultValueData { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata FieldMarshaledSizes { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Parameters { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Fields { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata GenericParameters { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata GenericParameterConstraints { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata GenericContainers { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata NestedTypes { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Interfaces { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata VtableMethods { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata InterfaceOffsets { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata TypeDefinitions { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Images { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Assemblies { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata FieldRefs { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata ReferencedAssemblies { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata AttributeData { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata AttributeDataRanges { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata UnresolvedIndirectCallParameterTypes { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata UnresolvedIndirectCallParameterRanges { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata WindowsRuntimeTypeNames { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata WindowsRuntimeStrings { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata ExportedTypeDefinitions { get; private set; }
public const int ExpectedSanity = unchecked((int)0xFAB11BAF); public const int ExpectedSanity = unchecked((int)0xFAB11BAF);
public readonly bool SanityValid => Sanity == ExpectedSanity; public readonly bool SanityValid => Sanity == ExpectedSanity;

View File

@@ -2,7 +2,6 @@
using StringIndex = int; using StringIndex = int;
using AssemblyIndex = int; using AssemblyIndex = int;
using TypeDefinitionIndex = int;
using MethodIndex = int; using MethodIndex = int;
using CustomAttributeIndex = int; using CustomAttributeIndex = int;
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;

View File

@@ -2,8 +2,6 @@
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using TypeIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppInterfaceOffsetPair public partial record struct Il2CppInterfaceOffsetPair
{ {

View File

@@ -4,10 +4,6 @@ using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using StringIndex = int; using StringIndex = int;
using TypeDefinitionIndex = int;
using TypeIndex = int;
using ParameterIndex = int;
using GenericContainerIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppMethodDefinition public partial record struct Il2CppMethodDefinition
@@ -18,7 +14,7 @@ public partial record struct Il2CppMethodDefinition
public TypeDefinitionIndex DeclaringType { get; private set; } public TypeDefinitionIndex DeclaringType { get; private set; }
public TypeIndex ReturnType { get; private set; } public TypeIndex ReturnType { get; private set; }
[VersionCondition(EqualTo = "31.0")] [VersionCondition(GreaterThan = "31.0")]
public uint ReturnParameterToken { get; private set; } public uint ReturnParameterToken { get; private set; }
public ParameterIndex ParameterStart { get; private set; } public ParameterIndex ParameterStart { get; private set; }

View File

@@ -1,7 +1,5 @@
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using ParameterIndex = int;
using TypeIndex = int;
using DefaultValueDataIndex = int; using DefaultValueDataIndex = int;
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;

View File

@@ -2,7 +2,6 @@
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;
using StringIndex = int; using StringIndex = int;
using TypeIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppParameterDefinition public partial record struct Il2CppParameterDefinition

View File

@@ -0,0 +1,11 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
[VersionedStruct]
public partial record struct Il2CppSectionMetadata
{
public int Offset { get; private set; }
public int SectionSize { get; private set; }
public int Count { get; private set; }
}

View File

@@ -6,6 +6,7 @@ using StringLiteralIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppStringLiteral public partial record struct Il2CppStringLiteral
{ {
[VersionCondition(LessThan = "31.0")]
public uint Length { get; private set; } public uint Length { get; private set; }
public StringLiteralIndex DataIndex { get; private set; } public StringLiteralIndex DataIndex { get; private set; }
} }

View File

@@ -1,11 +1,10 @@
using System.Reflection; using System.Reflection;
using VersionedSerialization;
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
using StringIndex = int; using StringIndex = int;
using TypeIndex = int;
using GenericContainerIndex = int;
using FieldIndex = int; using FieldIndex = int;
using MethodIndex = int; using MethodIndex = int;
using EventIndex = int; using EventIndex = int;
@@ -17,7 +16,7 @@ using VTableIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppTypeDefinition public partial record struct Il2CppTypeDefinition
{ {
public const TypeIndex InvalidTypeIndex = -1; public static readonly TypeIndex InvalidTypeIndex = -1;
public StringIndex NameIndex { get; private set; } public StringIndex NameIndex { get; private set; }
public StringIndex NamespaceIndex { get; private set; } public StringIndex NamespaceIndex { get; private set; }
@@ -32,6 +31,8 @@ public partial record struct Il2CppTypeDefinition
public TypeIndex DeclaringTypeIndex { get; private set; } public TypeIndex DeclaringTypeIndex { get; private set; }
public TypeIndex ParentIndex { get; private set; } public TypeIndex ParentIndex { get; private set; }
[VersionCondition(LessThan = "31.0")]
public TypeIndex ElementTypeIndex { get; private set; } public TypeIndex ElementTypeIndex { get; private set; }
[VersionCondition(LessThan = "24.1")] [VersionCondition(LessThan = "24.1")]
@@ -80,4 +81,9 @@ public partial record struct Il2CppTypeDefinition
public uint Token { get; private set; } public uint Token { get; private set; }
public readonly bool IsValid => NameIndex != 0; public readonly bool IsValid => NameIndex != 0;
public int GetEnumElementTypeIndex(StructVersion version)
=> version >= MetadataVersions.V350
? ParentIndex
: ElementTypeIndex;
} }

View File

@@ -2,7 +2,6 @@
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;
using StringIndex = int; using StringIndex = int;
using TypeIndex = int;
[VersionedStruct] [VersionedStruct]
public partial record struct Il2CppWindowsRuntimeTypeNamePair public partial record struct Il2CppWindowsRuntimeTypeNamePair

View File

@@ -0,0 +1,46 @@
using NoisyCowStudios.Bin2Object;
using VersionedSerialization;
namespace Il2CppInspector.Next.Metadata;
public struct ParameterIndex(int value) : IIndexType<ParameterIndex>, IReadable, IEquatable<ParameterIndex>
{
public const string TagPrefix = nameof(ParameterIndex);
static string IIndexType<ParameterIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<ParameterIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static int Size(in StructVersion version = default, bool is32Bit = false)
=> IIndexType<ParameterIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
_value = IIndexType<ParameterIndex>.ReadIndex(ref reader, in version);
}
#region Operators + ToString
public static implicit operator int(ParameterIndex idx) => idx._value;
public static implicit operator ParameterIndex(int idx) => new(idx);
public static bool operator ==(ParameterIndex left, ParameterIndex right)
=> left._value == right._value;
public static bool operator !=(ParameterIndex left, ParameterIndex right)
=> !(left == right);
public readonly override bool Equals(object obj)
=> obj is ParameterIndex other && Equals(other);
public readonly bool Equals(ParameterIndex other)
=> this == other;
public readonly override int GetHashCode()
=> HashCode.Combine(_value);
public readonly override string ToString() => _value.ToString();
#endregion
}

View File

@@ -0,0 +1,45 @@
using VersionedSerialization;
namespace Il2CppInspector.Next.Metadata;
public struct TypeDefinitionIndex(int value) : IIndexType<TypeDefinitionIndex>, IReadable, IEquatable<TypeDefinitionIndex>
{
public const string TagPrefix = nameof(TypeDefinitionIndex);
static string IIndexType<TypeDefinitionIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<TypeDefinitionIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static int Size(in StructVersion version = default, bool is32Bit = false)
=> IIndexType<TypeDefinitionIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
_value = IIndexType<TypeDefinitionIndex>.ReadIndex(ref reader, in version);
}
#region Operators + ToString
public static implicit operator int(TypeDefinitionIndex idx) => idx._value;
public static implicit operator TypeDefinitionIndex(int idx) => new(idx);
public static bool operator ==(TypeDefinitionIndex left, TypeDefinitionIndex right)
=> left._value == right._value;
public static bool operator !=(TypeDefinitionIndex left, TypeDefinitionIndex right)
=> !(left == right);
public readonly override bool Equals(object obj)
=> obj is TypeDefinitionIndex other && Equals(other);
public readonly bool Equals(TypeDefinitionIndex other)
=> this == other;
public readonly override int GetHashCode()
=> HashCode.Combine(_value);
public readonly override string ToString() => _value.ToString();
#endregion
}

View File

@@ -0,0 +1,45 @@
using VersionedSerialization;
namespace Il2CppInspector.Next.Metadata;
public struct TypeIndex(int value) : IIndexType<TypeIndex>, IReadable, IEquatable<TypeIndex>
{
public const string TagPrefix = nameof(TypeIndex);
static string IIndexType<TypeIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<TypeIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static int Size(in StructVersion version = default, bool is32Bit = false)
=> IIndexType<TypeIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
_value = IIndexType<TypeIndex>.ReadIndex(ref reader, in version);
}
#region Operators + ToString
public static implicit operator int(TypeIndex idx) => idx._value;
public static implicit operator TypeIndex(int idx) => new(idx);
public static bool operator ==(TypeIndex left, TypeIndex right)
=> left._value == right._value;
public static bool operator !=(TypeIndex left, TypeIndex right)
=> !(left == right);
public readonly override bool Equals(object obj)
=> obj is TypeIndex other && Equals(other);
public readonly bool Equals(TypeIndex other)
=> this == other;
public readonly override int GetHashCode()
=> HashCode.Combine(_value);
public readonly override string ToString() => _value.ToString();
#endregion
}

View File

@@ -28,4 +28,15 @@ public static class MetadataVersions
// No tag - 29.0/31.0 // No tag - 29.0/31.0
public static readonly string Tag2022 = "2022"; // 29.1/31.1 public static readonly string Tag2022 = "2022"; // 29.1/31.1
// Unity 6000.3.0a2
public static readonly StructVersion V350 = new(35);
// Unity 6000.3.0a5
public static readonly StructVersion V380 = new(38);
// NOTE: This version uses tags to specify the size of TypeIndex, TypeDefinitionIndex, and GenericContainerIndex.
// Unity 6000.3.0b1
public static readonly StructVersion V390 = new(39);
// NOTE This version additionally uses a tag to specify the size of ParameterIndex.
} }

View File

@@ -298,6 +298,10 @@ namespace Il2CppInspector.Outputs
private PropertyDef AddProperty(ModuleDef module, TypeDef mType, PropertyInfo prop) { private PropertyDef AddProperty(ModuleDef module, TypeDef mType, PropertyInfo prop) {
PropertySig s; PropertySig s;
// Example: ZstdSharp MEM_32Bit which gets inlined using weaving and all accessors removed
if (prop.GetMethod == null && prop.SetMethod == null)
return null;
// Static or instance // Static or instance
if (prop.GetMethod?.IsStatic ?? prop.SetMethod.IsStatic) if (prop.GetMethod?.IsStatic ?? prop.SetMethod.IsStatic)
s = PropertySig.CreateStatic(GetTypeSig(module, prop.PropertyType)); s = PropertySig.CreateStatic(GetTypeSig(module, prop.PropertyType));
@@ -430,7 +434,7 @@ namespace Il2CppInspector.Outputs
if (method.VirtualAddress.HasValue) { if (method.VirtualAddress.HasValue) {
var args = new List<(string,object)> { var args = new List<(string,object)> {
("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), ("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))), ("Offset", $"0x{model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start):X}"),
("VA", method.VirtualAddress.Value.Start.ToAddressString()) ("VA", method.VirtualAddress.Value.Start.ToAddressString())
}; };
if (method.Definition.Slot != ushort.MaxValue) if (method.Definition.Slot != ushort.MaxValue)
@@ -470,7 +474,7 @@ namespace Il2CppInspector.Outputs
return def.AddAttribute(module, attributeAttribute, return def.AddAttribute(module, attributeAttribute,
("Name", ca.AttributeType.Name), ("Name", ca.AttributeType.Name),
("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), ("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start))) ("Offset", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
); );
} }

View File

@@ -44,8 +44,13 @@ namespace Il2CppInspector.Outputs
// Write primitive type definitions for when we're not including other headers // Write primitive type definitions for when we're not including other headers
writeCode($""" writeCode($"""
#define IS_LIBCLANG_DECOMPILER (defined(_IDACLANG_) || defined(_BINARYNINJA_)) #if defined(_IDACLANG_) || defined(_BINARYNINJA_)
#define IS_DECOMPILER (defined(_GHIDRA_) || defined(_IDA_) || IS_LIBCLANG_DECOMPILER) #define IS_LIBCLANG_DECOMPILER
#endif
#if defined(_GHIDRA_) || defined(_IDA_) || defined(IS_LIBCLANG_DECOMPILER)
#define IS_DECOMPILER
#endif
#if defined(_GHIDRA_) || defined(_IDA_) #if defined(_GHIDRA_) || defined(_IDA_)
typedef unsigned __int8 uint8_t; typedef unsigned __int8 uint8_t;
@@ -58,7 +63,7 @@ namespace Il2CppInspector.Outputs
typedef __int64 int64_t; typedef __int64 int64_t;
#endif #endif
#if IS_LIBCLANG_DECOMPILER #if defined(IS_LIBCLANG_DECOMPILER)
typedef unsigned char uint8_t; typedef unsigned char uint8_t;
typedef unsigned short uint16_t; typedef unsigned short uint16_t;
typedef unsigned int uint32_t; typedef unsigned int uint32_t;
@@ -67,21 +72,26 @@ namespace Il2CppInspector.Outputs
typedef short int16_t; typedef short int16_t;
typedef int int32_t; typedef int int32_t;
typedef long int64_t; typedef long int64_t;
#ifdef linux
#undef linux
#endif #endif
#if defined(_GHIDRA_) || IS_LIBCLANG_DECOMPILER #endif
#if defined(_GHIDRA_) || defined(IS_LIBCLANG_DECOMPILER)
typedef int{_model.Package.BinaryImage.Bits}_t intptr_t; typedef int{_model.Package.BinaryImage.Bits}_t intptr_t;
typedef uint{_model.Package.BinaryImage.Bits}_t uintptr_t; typedef uint{_model.Package.BinaryImage.Bits}_t uintptr_t;
typedef uint{_model.Package.BinaryImage.Bits}_t size_t; typedef uint{_model.Package.BinaryImage.Bits}_t size_t;
#endif #endif
#if !IS_DECOMPILER #ifndef IS_DECOMPILER
#define _CPLUSPLUS_ #define _CPLUSPLUS_
#endif #endif
"""); """);
if (_useBetterArraySize) if (_useBetterArraySize)
writeCode("#define actual_il2cpp_array_size_t il2cpp_array_size_t"); writeCode("#define il2cpp_array_size_t actual_il2cpp_array_size_t");
writeSectionHeader("IL2CPP internal types"); writeSectionHeader("IL2CPP internal types");
writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits)); writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits));
@@ -94,9 +104,7 @@ namespace Il2CppInspector.Outputs
{ {
int32_t size; int32_t size;
actual_il2cpp_array_size_t value; actual_il2cpp_array_size_t value;
} better_il2cpp_array_size_t; } il2cpp_array_size_t;
#define better_il2cpp_array_size_t il2cpp_array_size_t
"""); """);
if (_model.TargetCompiler == CppCompilerType.MSVC) if (_model.TargetCompiler == CppCompilerType.MSVC)
@@ -115,17 +123,20 @@ namespace Il2CppInspector.Outputs
} }
// C does not support namespaces // C does not support namespaces
writeCode("#if !IS_DECOMPILER"); writeCode("#ifndef IS_DECOMPILER");
writeCode("namespace app {"); writeCode("namespace app {");
writeCode("#endif"); writeCode("#endif");
writeLine(""); writeLine("");
writeForwardDefinitions();
writeTypesForGroup("Required forward definitions", "required_forward_definitions");
writeTypesForGroup("Application types from method calls", "types_from_methods"); writeTypesForGroup("Application types from method calls", "types_from_methods");
writeTypesForGroup("Application types from generic methods", "types_from_generic_methods"); writeTypesForGroup("Application types from generic methods", "types_from_generic_methods");
writeTypesForGroup("Application types from usages", "types_from_usages"); writeTypesForGroup("Application types from usages", "types_from_usages");
writeTypesForGroup("Application unused value types", "unused_concrete_types"); writeTypesForGroup("Application unused value types", "unused_concrete_types");
writeCode("#if !IS_DECOMPILER"); writeCode("#ifndef IS_DECOMPILER");
writeCode("}"); writeCode("}");
writeCode("#endif"); writeCode("#endif");
} }
@@ -306,19 +317,32 @@ namespace Il2CppInspector.Outputs
writeLine(""); writeLine("");
} }
private void writeTypesForGroup(string header, string group) { private void writeForwardDefinitions()
{
writeSectionHeader("Required forward definitions");
foreach (var cppType in _model.RequiredForwardDefinitions)
writeCode(cppType.ToString());
}
private void writeTypesForGroup(string header, string group)
{
writeSectionHeader(header); writeSectionHeader(header);
foreach (var cppType in _model.GetDependencyOrderedCppTypeGroup(group)) foreach (var cppType in _model.GetDependencyOrderedCppTypeGroup(group))
if (cppType is CppEnumType) { {
if (cppType is CppEnumType)
{
// Ghidra can't process C++ enum base types // Ghidra can't process C++ enum base types
writeCode("#if defined(_CPLUSPLUS_)"); writeCode("#if defined(_CPLUSPLUS_)");
writeCode(cppType.ToString()); writeCode(cppType.ToString());
writeCode("#else"); writeCode("#else");
writeCode(cppType.ToString("c")); writeCode(cppType.ToString("c"));
writeCode("#endif"); writeCode("#endif");
} else { }
else
{
writeCode(cppType.ToString()); writeCode(cppType.ToString());
} }
}
} }
private void writeCode(string text) { private void writeCode(string text) {

View File

@@ -217,7 +217,6 @@ namespace Il2CppInspector.Outputs
foreach (var symbol in symbols) { foreach (var symbol in symbols) {
writeObject(() => { writeObject(() => {
writeName(symbol.VirtualAddress, symbol.Name); writeName(symbol.VirtualAddress, symbol.Name);
writer.WriteString("demangledName", symbol.DemangledName);
writer.WriteString("type", symbol.Type.ToString()); writer.WriteString("type", symbol.Type.ToString());
}); });
} }

View File

@@ -1,302 +1,330 @@
from binaryninja import * from binaryninja import (
BinaryView,
Component,
Type,
PointerType,
TypeParser,
Platform,
Endianness,
ArrayType,
BackgroundTaskThread,
demangle_gnu3,
get_qualified_name,
SegmentFlag,
SectionSemantics,
)
from binaryninja.log import log_error
#try: # try:
# from typing import TYPE_CHECKING # from typing import TYPE_CHECKING
# if TYPE_CHECKING: # if TYPE_CHECKING:
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext # from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
# import json # import json
# import os # import os
# import sys # import sys
# from datetime import datetime # from datetime import datetime
# from typing import Literal # from typing import Literal
# bv: BinaryView = None # type: ignore # bv: BinaryView = None # type: ignore
#except: # except:
# pass # pass
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface): class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
# this is implemented, supports_fake_string_segment: bool = True
# 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 _status: BaseStatusHandler
_view: BinaryView _view: BinaryView
_undo_id: str _undo_id: str
_components: dict[str, Component] _components: dict[str, Component]
_type_cache: dict[str, Type] _type_cache: dict[str, Type]
_function_type_cache: dict[str, Type] _function_type_cache: dict[str, Type]
_address_size: int _address_size: int
_endianness: Literal["little", "big"] _endianness: Literal["little", "big"]
TYPE_PARSER_OPTIONS = [ TYPE_PARSER_OPTIONS = ["--target=x86_64-pc-linux", "-x", "c++", "-D_BINARYNINJA_=1"]
"--target=x86_64-pc-linux",
"-x", "c++",
"-D_BINARYNINJA_=1"
]
def __init__(self, status: BaseStatusHandler): def __init__(self, status: BaseStatusHandler):
self._status = status self._status = status
def _get_or_create_type(self, type: str) -> Type: def _get_or_create_type(self, type: str) -> Type:
if type.startswith("struct "): if type.startswith("struct "):
type = type[len("struct "):] type = type[len("struct ") :]
elif type.startswith("class "): elif type.startswith("class "):
type = type[len("class "):] type = type[len("class ") :]
if type in self._type_cache: if type in self._type_cache:
return self._type_cache[type] return self._type_cache[type]
if type.endswith("*"): if type.endswith("*"):
base_type = self._get_or_create_type(type[:-1].strip()) base_type = self._get_or_create_type(type[:-1].strip())
parsed = PointerType.create(self._view.arch, base_type) # type: ignore parsed = PointerType.create(self._view.arch, base_type) # type: ignore
else: else:
parsed = self._view.get_type_by_name(type) parsed = self._view.get_type_by_name(type)
if parsed is None: if parsed is None:
parsed, errors = self._view.parse_type_string(type) parsed, errors = self._view.parse_type_string(type)
self._type_cache[type] = parsed self._type_cache[type] = parsed
return parsed return parsed
def _parse_type_source(self, types: str, filename: str | None = None): def _parse_type_source(self, types: str, filename: Union[str, None] = None):
parsed_types, errors = TypeParser.default.parse_types_from_source( parsed_types, errors = TypeParser.default.parse_types_from_source(
types, types,
filename if filename else "types.hpp", filename if filename else "types.hpp",
self._view.platform if self._view.platform is not None else Platform["windows-x86_64"], self._view.platform
self._view, if self._view.platform is not None
self.TYPE_PARSER_OPTIONS else Platform["windows-x86_64"],
) self._view,
self.TYPE_PARSER_OPTIONS,
)
if parsed_types is None: if parsed_types is None:
log_error("Failed to import types.") log_error("Failed to import types.")
log_error(errors) log_error(errors)
return None return None
return parsed_types return parsed_types
def get_script_directory(self) -> str: def get_script_directory(self) -> str:
return CURRENT_PATH return CURRENT_PATH
def on_start(self): def on_start(self):
self._view = bv # type: ignore self._view = bv # type: ignore
self._undo_id = self._view.begin_undo_actions() self._undo_id = self._view.begin_undo_actions()
self._view.set_analysis_hold(True) self._view.set_analysis_hold(True)
self._components = {} self._components = {}
self._type_cache = {} self._type_cache = {}
self._function_type_cache = {} self._function_type_cache = {}
self._address_size = self._view.address_size 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") self._status.update_step("Parsing header")
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f: 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 = self._parse_type_source(f.read(), "il2cpp.hpp")
if parsed_types is None: if parsed_types is None:
return return
self._status.update_step("Importing header types", len(parsed_types.types)) self._status.update_step("Importing header types", len(parsed_types.types))
def import_progress_func(progress: int, total: int): def import_progress_func(progress: int, total: int):
self._status.update_progress(1) self._status.update_progress(1)
return True 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): def on_finish(self):
self._view.commit_undo_actions(self._undo_id) self._view.commit_undo_actions(self._undo_id)
self._view.set_analysis_hold(False) self._view.set_analysis_hold(False)
self._view.update_analysis() self._view.update_analysis()
def define_function(self, address: int, end: int | None = None): def define_function(self, address: int, end: Union[int, None] = None):
if self._view.get_function_at(address) is not None: if self._view.get_function_at(address) is not None:
return return
self._view.create_user_function(address) self._view.create_user_function(address)
def define_data_array(self, address: int, type: str, count: int): def define_data_array(self, address: int, type: str, count: int):
parsed_type = self._get_or_create_type(type) parsed_type = self._get_or_create_type(type)
array_type = ArrayType.create(parsed_type, count) array_type = ArrayType.create(parsed_type, count)
var = self._view.get_data_var_at(address) var = self._view.get_data_var_at(address)
if var is None: if var is None:
self._view.define_user_data_var(address, array_type) self._view.define_user_data_var(address, array_type)
else: else:
var.type = array_type var.type = array_type
def set_data_type(self, address: int, type: str): def set_data_type(self, address: int, type: str):
var = self._view.get_data_var_at(address) var = self._view.get_data_var_at(address)
dtype = self._get_or_create_type(type) dtype = self._get_or_create_type(type)
if var is None: if var is None:
self._view.define_user_data_var(address, dtype) self._view.define_user_data_var(address, dtype)
else: else:
var.type = dtype var.type = dtype
def set_function_type(self, address: int, type: str): def set_function_type(self, address: int, type: str):
function = self._view.get_function_at(address) function = self._view.get_function_at(address)
if function is None: if function is None:
return return
if type in self._function_type_cache: if type in self._function_type_cache:
function.type = self._function_type_cache[type] # type: ignore function.type = self._function_type_cache[type] # type: ignore
else: else:
#log_info(f"skipping function type setting for {address}, {type}") # log_info(f"skipping function type setting for {address}, {type}")
#pass # pass
function.type = type.replace("this", "`this`") function.type = type.replace("this", "`this`")
def set_data_comment(self, address: int, cmt: str): def set_data_comment(self, address: int, cmt: str):
self._view.set_comment_at(address, cmt) self._view.set_comment_at(address, cmt)
def set_function_comment(self, address: int, cmt: str): def set_function_comment(self, address: int, cmt: str):
function = self._view.get_function_at(address) function = self._view.get_function_at(address)
if function is None: if function is None:
return return
function.comment = cmt function.comment = cmt
def set_data_name(self, address: int, name: str): def set_data_name(self, address: int, name: str):
var = self._view.get_data_var_at(address) var = self._view.get_data_var_at(address)
if var is None: if var is None:
return return
if name.startswith("_Z"): if name.startswith("_Z"):
type, demangled = demangle_gnu3(self._view.arch, name, self._view) type, demangled = demangle_gnu3(self._view.arch, name, self._view)
var.name = get_qualified_name(demangled) var.name = get_qualified_name(demangled)
else: else:
var.name = name var.name = name
def set_function_name(self, address: int, name: str): def set_function_name(self, address: int, name: str):
function = self._view.get_function_at(address) function = self._view.get_function_at(address)
if function is None: if function is None:
return return
if name.startswith("_Z"): if name.startswith("_Z"):
type, demangled = demangle_gnu3(self._view.arch, name, self._view) type, demangled = demangle_gnu3(self._view.arch, name, self._view)
function.name = get_qualified_name(demangled) function.name = get_qualified_name(demangled)
#function.type = type - this does not work due to the generated types not being namespaced. :( # function.type = type - this does not work due to the generated types not being namespaced. :(
else: else:
function.name = name function.name = name
def add_cross_reference(self, from_address: int, to_address: int): def add_cross_reference(self, from_address: int, to_address: int):
self._view.add_user_data_ref(from_address, to_address) self._view.add_user_data_ref(from_address, to_address)
def import_c_typedef(self, type_def: str): def import_c_typedef(self, type_def: str):
self._view.define_user_type(None, type_def) self._view.define_user_type(None, type_def)
# optional # optional
def _get_or_create_component(self, name: str): def _get_or_create_component(self, name: str):
if name in self._components: if name in self._components:
return self._components[name] return self._components[name]
current = name current = name
if current.count("/") != 0: if current.count("/") != 0:
split_idx = current.rindex("/") split_idx = current.rindex("/")
parent, child = current[:split_idx], current[split_idx:] parent, child = current[:split_idx], current[split_idx:]
parent = self._get_or_create_component(name) parent = self._get_or_create_component(name)
component = self._view.create_component(child, parent) component = self._view.create_component(child, parent)
else: else:
component = self._view.create_component(name) component = self._view.create_component(name)
self._components[name] = component self._components[name] = component
return component return component
def add_function_to_group(self, address: int, group: str): def add_function_to_group(self, address: int, group: str):
return return
function = self._view.get_function_at(address) function = self._view.get_function_at(address)
if function is None: if function is None:
return return
self._get_or_create_component(group).add_function(function) self._get_or_create_component(group).add_function(function)
def cache_function_types(self, signatures: list[str]): def cache_function_types(self, signatures: list[str]):
function_sigs = set(signatures) function_sigs = set(signatures)
if len(function_sigs) == 0: if len(function_sigs) == 0:
return return
typestr = ";\n".join(function_sigs).replace("this", "_this") + ";" typestr = ";\n".join(function_sigs).replace("this", "_this") + ";"
parsed_types = self._parse_type_source(typestr, "cached_types.hpp") parsed_types = self._parse_type_source(typestr, "cached_types.hpp")
if parsed_types is None: if parsed_types is None:
return return
# bv.parse_types_from_source returns a dict in the functions field. # bv.parse_types_from_source returns a dict in the functions field.
# TypeParser.parse_types_from_source does not. # TypeParser.parse_types_from_source does not.
for function_sig, function in zip(function_sigs, parsed_types.functions): for function_sig, function in zip(function_sigs, parsed_types.functions):
self._function_type_cache[function_sig] = function.type self._function_type_cache[function_sig] = function.type
# only required if supports_fake_string_segment == True # only required if supports_fake_string_segment == True
def create_fake_segment(self, name: str, size: int) -> int: def create_fake_segment(self, name: str, size: int) -> int:
last_end_addr = self._view.mapped_address_ranges[-1].end last_end_addr = self._view.mapped_address_ranges[-1].end
if last_end_addr % 0x1000 != 0: if last_end_addr % 0x1000 != 0:
last_end_addr += (0x1000 - (last_end_addr % 0x1000)) last_end_addr += 0x1000 - (last_end_addr % 0x1000)
self._view.add_user_segment(last_end_addr, size, 0, 0, SegmentFlag.SegmentContainsData) self._view.memory_map.add_memory_region(
self._view.add_user_section(name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics) f"mem_{name}",
return last_end_addr last_end_addr,
bytes(size),
SegmentFlag.SegmentContainsData | SegmentFlag.SegmentReadable,
)
def write_string(self, address: int, value: str) -> int: self._view.add_user_section(
encoded = value.encode() + b"\x00" name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics
self._view.write(address, encoded) )
return len(encoded)
def write_address(self, address: int, value: int): return last_end_addr
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
def write_string(self, address: int, value: str) -> int:
encoded = value.encode() + b"\x00"
self._view.write(address, encoded)
return len(encoded)
def write_address(self, address: int, value: int):
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
class BinaryNinjaStatusHandler(BaseStatusHandler): class BinaryNinjaStatusHandler(BaseStatusHandler):
def __init__(self, thread: BackgroundTaskThread): def __init__(self, thread: BackgroundTaskThread):
self.step = "Initializing" self.step = "Initializing"
self.max_items = 0 self.max_items = 0
self.current_items = 0 self.current_items = 0
self.start_time = datetime.now() self.start_time = datetime.now()
self.step_start_time = self.start_time self.step_start_time = self.start_time
self.last_updated_time = datetime.min self.last_updated_time = datetime.min
self._thread = thread self._thread = thread
def initialize(self): pass def initialize(self):
pass
def update(self): def update(self):
if self.was_cancelled(): if self.was_cancelled():
raise RuntimeError("Cancelled script.") raise RuntimeError("Cancelled script.")
current_time = datetime.now() current_time = datetime.now()
if 0.5 > (current_time - self.last_updated_time).total_seconds(): if 0.5 > (current_time - self.last_updated_time).total_seconds():
return return
self.last_updated_time = current_time self.last_updated_time = current_time
step_time = current_time - self.step_start_time step_time = current_time - self.step_start_time
total_time = current_time - self.start_time total_time = current_time - self.start_time
self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})" self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})"
def update_step(self, step, max_items = 0): def update_step(self, step, max_items=0):
self.step = step self.step = step
self.max_items = max_items self.max_items = max_items
self.current_items = 0 self.current_items = 0
self.step_start_time = datetime.now() self.step_start_time = datetime.now()
self.last_updated_time = datetime.min self.last_updated_time = datetime.min
self.update() self.update()
def update_progress(self, new_progress = 1): def update_progress(self, new_progress=1):
self.current_items += new_progress self.current_items += new_progress
self.update() self.update()
def was_cancelled(self): return False def was_cancelled(self):
return False
def close(self):
pass
def close(self):
pass
# Entry point # Entry point
class Il2CppTask(BackgroundTaskThread): class Il2CppTask(BackgroundTaskThread):
def __init__(self): def __init__(self):
BackgroundTaskThread.__init__(self, "Processing IL2CPP metadata...", False) BackgroundTaskThread.__init__(self, "Processing IL2CPP metadata...", False)
def run(self):
status = BinaryNinjaStatusHandler(self)
backend = BinaryNinjaDisassemblerInterface(status)
context = ScriptContext(backend, status)
context.process()
def run(self):
status = BinaryNinjaStatusHandler(self)
backend = BinaryNinjaDisassemblerInterface(status)
context = ScriptContext(backend, status)
context.process()
Il2CppTask().start() Il2CppTask().start()

View File

@@ -6,6 +6,7 @@ from ghidra.program.model.symbol import SourceType
from ghidra.program.model.symbol import RefType from ghidra.program.model.symbol import RefType
from ghidra.app.cmd.label import DemanglerCmd from ghidra.app.cmd.label import DemanglerCmd
from ghidra.app.services import DataTypeManagerService from ghidra.app.services import DataTypeManagerService
from java.lang import Long
#try: #try:
# from typing import TYPE_CHECKING # from typing import TYPE_CHECKING
@@ -21,6 +22,9 @@ from ghidra.app.services import DataTypeManagerService
class GhidraDisassemblerInterface(BaseDisassemblerInterface): class GhidraDisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment = False supports_fake_string_segment = False
def _to_address(self, value):
return toAddr(Long(value))
def get_script_directory(self) -> str: def get_script_directory(self) -> str:
return getSourceFile().getParentFile().toString() return getSourceFile().getParentFile().toString()
@@ -39,7 +43,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time # 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 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)'): if currentProgram.getExecutableFormat().endswith('(ELF)'):
currentProgram.setImageBase(toAddr(0), True) currentProgram.setImageBase(self._to_address(0), True)
# Don't trigger decompiler # Don't trigger decompiler
setAnalysisOption(currentProgram, "Call Convention ID", "false") setAnalysisOption(currentProgram, "Call Convention ID", "false")
@@ -47,8 +51,8 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
def on_finish(self): def on_finish(self):
pass pass
def define_function(self, address: int, end: int | None = None): def define_function(self, address: int, end: Union[int, None] = None):
address = toAddr(address) address = self._to_address(address)
# Don't override existing functions # Don't override existing functions
fn = getFunctionAt(address) fn = getFunctionAt(address)
if fn is None: if fn is None:
@@ -61,7 +65,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
t = getDataTypes(type)[0] t = getDataTypes(type)[0]
a = ArrayDataType(t, count, t.getLength()) a = ArrayDataType(t, count, t.getLength())
address = toAddr(address) address = self._to_address(address)
removeDataAt(address) removeDataAt(address)
createData(address, a) createData(address, a)
@@ -71,7 +75,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
try: try:
t = getDataTypes(type)[0] t = getDataTypes(type)[0]
address = toAddr(address) address = self._to_address(address)
removeDataAt(address) removeDataAt(address)
createData(address, t) createData(address, t)
except: except:
@@ -79,16 +83,16 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
def set_function_type(self, address: int, type: str): def set_function_type(self, address: int, type: str):
typeSig = CParserUtils.parseSignature(DataTypeManagerService@None, currentProgram, type) typeSig = CParserUtils.parseSignature(DataTypeManagerService@None, currentProgram, type)
ApplyFunctionSignatureCmd(toAddr(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram) ApplyFunctionSignatureCmd(self._to_address(address), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
def set_data_comment(self, address: int, cmt: str): def set_data_comment(self, address: int, cmt: str):
setEOLComment(toAddr(address), cmt) setEOLComment(self._to_address(address), cmt)
def set_function_comment(self, address: int, cmt: str): def set_function_comment(self, address: int, cmt: str):
setPlateComment(toAddr(address), cmt) setPlateComment(self._to_address(address), cmt)
def set_data_name(self, address: int, name: str): def set_data_name(self, address: int, name: str):
address = toAddr(address) address = self._to_address(address)
if len(name) > 2000: if len(name) > 2000:
print("Name length exceeds 2000 characters, skipping (%s)" % name) print("Name length exceeds 2000 characters, skipping (%s)" % name)
@@ -107,7 +111,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
return self.set_data_name(address, name) return self.set_data_name(address, name)
def add_cross_reference(self, from_address: int, to_address: int): def add_cross_reference(self, from_address: int, to_address: int):
self.xrefs.addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.DATA, SourceType.USER_DEFINED, 0) self.xrefs.addMemoryReference(self._to_address(from_address), self._to_address(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
def import_c_typedef(self, type_def: str): def import_c_typedef(self, type_def: str):
# Code declarations are not supported in Ghidra # Code declarations are not supported in Ghidra

View File

@@ -83,8 +83,8 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
ida_ida.inf_set_genflags(self._cached_genflags & ~ida_ida.INFFL_AUTO) ida_ida.inf_set_genflags(self._cached_genflags & ~ida_ida.INFFL_AUTO)
# Unload type libraries we know to cause issues - like the c++ linux one # Unload type libraries we know to cause issues - like the c++ linux one
PLATFORMS = ["x86", "x64", "arm", "arm64"] PLATFORMS = ["x86", "x64", "arm", "arm64", "win7"]
PROBLEMATIC_TYPELIBS = ["gnulnx"] PROBLEMATIC_TYPELIBS = ["gnulnx", "mssdk64"]
for lib in PROBLEMATIC_TYPELIBS: for lib in PROBLEMATIC_TYPELIBS:
for platform in PLATFORMS: for platform in PLATFORMS:
@@ -118,7 +118,7 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
def on_finish(self): def on_finish(self):
ida_ida.inf_set_genflags(self._cached_genflags) ida_ida.inf_set_genflags(self._cached_genflags)
def define_function(self, address: int, end: int | None = None): def define_function(self, address: int, end: Union[int, None] = None):
if self._skip_function_creation: if self._skip_function_creation:
return return

View File

@@ -1,9 +1,13 @@
#@runtime PyGhidra
# ^-- required for ghidra, ignored by all others
# Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty) # Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)
# Target Unity version: %TARGET_UNITY_VERSION% # Target Unity version: %TARGET_UNITY_VERSION%
import json import json
import os import os
from datetime import datetime from datetime import datetime
from typing import Union
import abc import abc
class BaseStatusHandler(abc.ABC): class BaseStatusHandler(abc.ABC):
@@ -28,7 +32,7 @@ class BaseDisassemblerInterface(abc.ABC):
def on_finish(self): pass def on_finish(self): pass
@abc.abstractmethod @abc.abstractmethod
def define_function(self, address: int, end: int | None = None): pass def define_function(self, address: int, end: Union[int, None] = None): pass
@abc.abstractmethod @abc.abstractmethod
def define_data_array(self, address: int, type: str, count: int): pass def define_data_array(self, address: int, type: str, count: int): pass
@@ -108,7 +112,7 @@ class ScriptContext:
self._backend.set_data_name(addr, definition['name']) self._backend.set_data_name(addr, definition['name'])
self._backend.set_data_comment(addr, definition['string']) self._backend.set_data_comment(addr, definition['string'])
def define_field(self, addr: str, name: str, type: str, il_type: str | None = None): def define_field(self, addr: str, name: str, type: str, il_type: Union[str, None] = None):
address = self.from_hex(addr) address = self.from_hex(addr)
self._backend.set_data_type(address, type) self._backend.set_data_type(address, type)
self._backend.set_data_name(address, name) self._backend.set_data_name(address, name)

View File

@@ -4,6 +4,11 @@
All rights reserved. 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;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -12,11 +17,6 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices; 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 namespace Il2CppInspector
{ {

View File

@@ -118,8 +118,8 @@ namespace Il2CppInspector.Reflection
var range = pkg.Metadata.AttributeDataRanges[customAttributeIndex]; var range = pkg.Metadata.AttributeDataRanges[customAttributeIndex];
var next = pkg.Metadata.AttributeDataRanges[customAttributeIndex + 1]; var next = pkg.Metadata.AttributeDataRanges[customAttributeIndex + 1];
var startOffset = (uint)pkg.Metadata.Header.AttributeDataOffset + range.StartOffset; var startOffset = (uint)pkg.Metadata.AttributeDataOffset + range.StartOffset;
var endOffset = (uint)pkg.Metadata.Header.AttributeDataOffset + next.StartOffset; var endOffset = (uint)pkg.Metadata.AttributeDataOffset + next.StartOffset;
var reader = new CustomAttributeDataReader(pkg, asm, pkg.Metadata, startOffset, endOffset); var reader = new CustomAttributeDataReader(pkg, asm, pkg.Metadata, startOffset, endOffset);
if (reader.Count == 0) if (reader.Count == 0)

View File

@@ -13,6 +13,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata; using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata; using Il2CppInspector.Next.Metadata;
@@ -784,7 +785,9 @@ namespace Il2CppInspector.Reflection
// Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum) // Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum)
if (Definition.Bitfield.EnumType) { if (Definition.Bitfield.EnumType) {
IsEnum = true; IsEnum = true;
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.ElementTypeIndex);
var enumUnderlyingTypeIndex = Definition.GetEnumElementTypeIndex(Assembly.Model.Package.Version);
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, enumUnderlyingTypeIndex);
} }
// Pass-by-reference type // Pass-by-reference type

View File

@@ -165,10 +165,9 @@ namespace Il2CppInspector.Reflection
// Generic type definitions have an invoker index of -1 // Generic type definitions have an invoker index of -1
foreach (var method in MethodsByDefinitionIndex) { foreach (var method in MethodsByDefinitionIndex) {
var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition); var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition);
if (index != -1) { if (index != -1)
if (MethodInvokers[index] == null) {
MethodInvokers[index] = new MethodInvoker(method, index); MethodInvokers[index] ??= new MethodInvoker(method, index);
method.Invoker = MethodInvokers[index]; method.Invoker = MethodInvokers[index];
} }
} }
@@ -176,10 +175,11 @@ namespace Il2CppInspector.Reflection
// Create method invokers sourced from generic method invoker indices // Create method invokers sourced from generic method invoker indices
foreach (var spec in GenericMethods.Keys) { foreach (var spec in GenericMethods.Keys) {
if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) { if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) {
if (MethodInvokers[index] == null) if (index != -1)
MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index); {
MethodInvokers[index] ??= new MethodInvoker(GenericMethods[spec], index);
GenericMethods[spec].Invoker = MethodInvokers[index]; GenericMethods[spec].Invoker = MethodInvokers[index];
}
} }
} }

View File

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

View File

@@ -0,0 +1,136 @@
using System.Threading.Channels;
using Il2CppInspector.Redux.FrontendCore;
using Microsoft.AspNetCore.SignalR.Client;
using Spectre.Console;
namespace Il2CppInspector.Redux.CLI;
public class CliClient : IDisposable
{
public bool ImportCompleted { get; private set; }
private volatile int _finishedLoadingCount = 0;
private readonly HubConnection _connection;
private readonly List<IDisposable> _commandListeners = [];
private Channel<string>? _logMessageChannel;
public CliClient(HubConnection connection)
{
_connection = connection;
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowLogMessage), ShowLogMessage));
_commandListeners.Add(_connection.On(nameof(UiClient.BeginLoading), BeginLoading));
_commandListeners.Add(_connection.On(nameof(UiClient.FinishLoading), FinishLoading));
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowInfoToast), ShowInfoToast));
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowSuccessToast), ShowSuccessToast));
_commandListeners.Add(_connection.On<string>(nameof(UiClient.ShowErrorToast), ShowErrorToast));
_commandListeners.Add(_connection.On(nameof(UiClient.OnImportCompleted), OnImportCompleted));
}
public async ValueTask OnUiLaunched(CancellationToken cancellationToken = default)
{
await _connection.InvokeAsync(nameof(Il2CppHub.OnUiLaunched), cancellationToken);
}
public async ValueTask SubmitInputFiles(List<string> inputFiles, CancellationToken cancellationToken = default)
{
await _connection.InvokeAsync(nameof(Il2CppHub.SubmitInputFiles), inputFiles, cancellationToken);
}
public async ValueTask QueueExport(string exportTypeId, string outputDirectory, Dictionary<string, string> settings,
CancellationToken cancellationToken = default)
{
await _connection.InvokeAsync(nameof(Il2CppHub.QueueExport), exportTypeId, outputDirectory, settings, cancellationToken);
}
public async ValueTask StartExport(CancellationToken cancellationToken = default)
{
await _connection.InvokeAsync(nameof(Il2CppHub.StartExport), cancellationToken);
}
public async ValueTask<List<string>> GetPotentialUnityVersions(CancellationToken cancellationToken = default)
=> await _connection.InvokeAsync<List<string>>(nameof(Il2CppHub.GetPotentialUnityVersions), cancellationToken);
public async ValueTask ExportIl2CppFiles(string outputDirectory, CancellationToken cancellationToken = default)
{
await _connection.InvokeAsync(nameof(Il2CppHub.ExportIl2CppFiles), outputDirectory, cancellationToken);
}
public async ValueTask<string> GetInspectorVersion(CancellationToken cancellationToken = default)
=> await _connection.InvokeAsync<string>(nameof(Il2CppHub.GetInspectorVersion), cancellationToken);
public async ValueTask WaitForLoadingToFinishAsync(CancellationToken cancellationToken = default)
{
var currentLoadingCount = _finishedLoadingCount;
while (_finishedLoadingCount == currentLoadingCount)
await Task.Delay(10, cancellationToken);
}
private async Task ShowLogMessage(string message)
{
if (_logMessageChannel == null)
{
AnsiConsole.MarkupLine($"[white bold]{message}[/]");
return;
}
await _logMessageChannel.Writer.WriteAsync(message);
}
private void BeginLoading()
{
_logMessageChannel = Channel.CreateUnbounded<string>(new UnboundedChannelOptions
{
SingleReader = true,
SingleWriter = true,
AllowSynchronousContinuations = true
});
AnsiConsole.Status()
.Spinner(Spinner.Known.Triangle)
.StartAsync("Loading", async ctx =>
{
await foreach (var newLogMessage in _logMessageChannel.Reader.ReadAllAsync())
{
ctx.Status(newLogMessage);
}
});
}
private void FinishLoading()
{
_logMessageChannel?.Writer.Complete();
Interlocked.Increment(ref _finishedLoadingCount);
}
private static void ShowInfoToast(string message)
{
AnsiConsole.MarkupLineInterpolated($"[bold white]INFO: {message}[/]");
}
private static void ShowSuccessToast(string message)
{
AnsiConsole.MarkupLineInterpolated($"[bold][green]SUCCESS: [/] [white]{message}[/][/]");
}
private static void ShowErrorToast(string message)
{
AnsiConsole.MarkupLineInterpolated($"[bold][red]ERROR: [/] [white]{message}[/][/]");
}
private void OnImportCompleted()
{
ImportCompleted = true;
}
public void Dispose()
{
GC.SuppressFinalize(this);
foreach (var listener in _commandListeners)
listener.Dispose();
}
}

View File

@@ -0,0 +1,38 @@
using Il2CppInspector.Redux.FrontendCore;
using Microsoft.AspNetCore.SignalR.Client;
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal abstract class BaseCommand<T>(PortProvider portProvider) : AsyncCommand<T> where T : CommandSettings
{
private const string HubPath = "/il2cpp"; // TODO: Make this into a shared constant
private readonly int _serverPort = portProvider.Port;
protected abstract Task<int> ExecuteAsync(CliClient client, T settings);
public override async Task<int> ExecuteAsync(CommandContext context, T settings)
{
var connection = new HubConnectionBuilder().WithUrl($"http://localhost:{_serverPort}{HubPath}")
.AddJsonProtocol(options =>
{
options.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0,
FrontendCoreJsonSerializerContext.Default);
})
.Build();
await connection.StartAsync();
int result;
using (var client = new CliClient(connection))
{
await client.OnUiLaunched();
result = await ExecuteAsync(client, settings);
}
await connection.StopAsync();
return result;
}
}

View File

@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.SignalR.Client;
using Spectre.Console;
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal class InteractiveCommand(PortProvider portProvider) : BaseCommand<InteractiveCommand.Options>(portProvider)
{
public class Options : CommandSettings;
protected override async Task<int> ExecuteAsync(CliClient client, Options settings)
{
await Task.Delay(1000);
await AnsiConsole.AskAsync<string>("meow?");
return 0;
}
}

View File

@@ -0,0 +1,21 @@
using Spectre.Console;
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal abstract class ManualCommand<T>(PortProvider portProvider) : BaseCommand<T>(portProvider) where T : ManualCommandOptions
{
public override ValidationResult Validate(CommandContext context, T settings)
{
foreach (var inputPath in settings.InputPaths)
{
if (!Path.Exists(inputPath))
return ValidationResult.Error($"Provided input path {inputPath} does not exit.");
}
if (File.Exists(settings.OutputPath))
return ValidationResult.Error("Provided output path already exists as a file.");
return ValidationResult.Success();
}
}

View File

@@ -0,0 +1,15 @@
using System.ComponentModel;
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal class ManualCommandOptions : CommandSettings
{
[CommandArgument(0, "<InputPath>")]
[Description("Paths to the input files. Will be subsequently loaded until binary and metadata were found.")]
public string[] InputPaths { get; init; } = [];
[CommandOption("-o|--output")]
[Description("Path to the output folder")]
public string OutputPath { get; init; } = "";
}

View File

@@ -0,0 +1,172 @@
using Il2CppInspector.Cpp;
using Il2CppInspector.Redux.FrontendCore.Outputs;
using Spectre.Console;
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal class ProcessCommand(PortProvider portProvider) : ManualCommand<ProcessCommand.Option>(portProvider)
{
// NOTE: There might be a better option than replicating all available flags here (and in the TS UI).
// Investigate this in the future.
public class Option : ManualCommandOptions
{
// C++ Scaffolding
[CommandOption("--output-cpp-scaffolding")]
public bool CppScaffolding { get; init; } = false;
[CommandOption("--unity-version")]
public string? UnityVersion { get; init; }
[CommandOption("--compiler-type")]
public CppCompilerType CompilerType { get; init; } = CppCompilerType.GCC;
// C# stub
[CommandOption("-s|--output-csharp-stub")]
public bool CSharpStubs { get; init; } = false;
[CommandOption("--layout")]
public CSharpLayout Layout { get; init; } = CSharpLayout.SingleFile;
[CommandOption("--flatten-hierarchy")]
public bool FlattenHierarchy { get; init; } = false;
[CommandOption("--sorting-mode")]
public TypeSortingMode SortingMode { get; init; } = TypeSortingMode.Alphabetical;
[CommandOption("--suppress-metadata")]
public bool SuppressMetadata { get; init; } = false;
[CommandOption("--compilable")]
public bool MustCompile { get; init; } = false;
[CommandOption("--separate-assembly-attributes")]
public bool SeparateAssemblyAttributes { get; init; } = true;
// Disassembler metadata
[CommandOption("-m|--output-disassembler-metadata")]
public bool DisassemblerMetadata { get; init; } = false;
[CommandOption("--disassembler")]
public DisassemblerType Disassembler { get; init; } = DisassemblerType.IDA;
// Dummy DLL output
[CommandOption("-d|--output-dummy-dlls")]
public bool DummyDlls { get; init; } = false;
// Visual Studio solution
[CommandOption("--output-vs-solution")]
public bool VsSolution { get; init; } = false;
[CommandOption("--unity-path")]
public string? UnityPath { get; init; }
[CommandOption("--unity-assemblies-path")]
public string? UnityAssembliesPath { get; init; }
[CommandOption("--extract-il2cpp-files")]
public string? ExtractIl2CppFilesPath { get; init; }
}
protected override async Task<int> ExecuteAsync(CliClient client, Option settings)
{
var inspectorVersion = await client.GetInspectorVersion();
AnsiConsole.MarkupLineInterpolated($"Using inspector [gray]{inspectorVersion}[/]");
await client.SubmitInputFiles(settings.InputPaths.ToList());
await client.WaitForLoadingToFinishAsync();
if (!client.ImportCompleted)
{
AnsiConsole.MarkupLine("[bold][red]FAILED[/] to load IL2CPP data from the given inputs.[/]");
return 1;
}
if (settings.ExtractIl2CppFilesPath != null)
{
await client.ExportIl2CppFiles(settings.ExtractIl2CppFilesPath);
await client.WaitForLoadingToFinishAsync();
}
var unityVersions = await client.GetPotentialUnityVersions();
if (settings.CppScaffolding)
{
var directory = Path.Join(settings.OutputPath, "cpp");
await client.QueueExport(CppScaffoldingOutput.Id, directory, new Dictionary<string, string>
{
["unityversion"] = settings.UnityVersion ?? unityVersions.First(),
["compilertype"] = settings.CompilerType.ToString()
});
}
if (settings.CSharpStubs)
{
var directory = Path.Join(settings.OutputPath, "cs");
await client.QueueExport(CSharpStubOutput.Id, directory, new Dictionary<string, string>
{
["layout"] = settings.Layout.ToString(),
["flattenhierarchy"] = settings.FlattenHierarchy.ToString(),
["sortingmode"] = settings.SortingMode.ToString(),
["suppressmetadata"] = settings.SuppressMetadata.ToString(),
["mustcompile"] = settings.MustCompile.ToString(),
["separateassemblyattributes"] = settings.SeparateAssemblyAttributes.ToString()
});
}
if (settings.DisassemblerMetadata)
{
await client.QueueExport(DisassemblerMetadataOutput.Id, settings.OutputPath,
new Dictionary<string, string>
{
["disassembler"] = settings.Disassembler.ToString(),
["unityversion"] = settings.UnityVersion ?? unityVersions.First()
});
}
if (settings.DummyDlls)
{
var directory = Path.Join(settings.OutputPath, "dll");
await client.QueueExport(DummyDllOutput.Id, directory, new Dictionary<string, string>
{
["suppressmetadata"] = settings.SuppressMetadata.ToString()
});
}
if (settings.VsSolution)
{
var directory = Path.Join(settings.OutputPath, "vs");
await client.QueueExport(VsSolutionOutput.Id, directory, new Dictionary<string, string>
{
["unitypath"] = settings.UnityPath ?? "",
["unityassembliespath"] = settings.UnityAssembliesPath ?? ""
});
}
await client.StartExport();
await client.WaitForLoadingToFinishAsync();
return 0;
}
public override ValidationResult Validate(CommandContext context, Option settings)
{
if (settings.UnityPath != null && !Path.Exists(settings.UnityPath))
return ValidationResult.Error($"Provided Unity path {settings.UnityPath} does not exist.");
if (settings.UnityAssembliesPath != null && !Path.Exists(settings.UnityAssembliesPath))
return ValidationResult.Error($"Provided Unity assemblies path {settings.UnityAssembliesPath} does not exist.");
if (settings.ExtractIl2CppFilesPath != null && File.Exists(settings.ExtractIl2CppFilesPath))
return ValidationResult.Error(
$"Provided extracted IL2CPP files path {settings.ExtractIl2CppFilesPath} already exists as a file.");
if (settings is
{
CppScaffolding: false, CSharpStubs: false, DisassemblerMetadata: false, DummyDlls: false,
VsSolution: false
})
return ValidationResult.Error("At least one output format must be specified.");
return base.Validate(context, settings);
}
}

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishAot>false</PublishAot>
<PublishSingleFile>true</PublishSingleFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="9.0.7" />
<PackageReference Include="Spectre.Console.Cli" Version="0.50.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Il2CppInspector.Redux.FrontendCore\Il2CppInspector.Redux.FrontendCore.csproj" />
</ItemGroup>
</Project>

View File

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

View File

@@ -0,0 +1,46 @@
using Il2CppInspector.Redux.CLI;
using Il2CppInspector.Redux.CLI.Commands;
using Il2CppInspector.Redux.FrontendCore;
using Microsoft.AspNetCore.SignalR;
using Spectre.Console.Cli;
var builder = WebApplication.CreateSlimBuilder();
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, FrontendCoreJsonSerializerContext.Default);
});
builder.Services.Configure<JsonHubProtocolOptions>(options =>
{
options.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, FrontendCoreJsonSerializerContext.Default);
});
builder.Services.AddFrontendCore();
builder.Logging.ClearProviders();
var app = builder.Build();
app.UseCors();
app.MapFrontendCore();
await app.StartAsync();
var serverUrl = app.Urls.First();
var port = new Uri(serverUrl).Port;
var commandServiceProvider = new ServiceCollection();
commandServiceProvider.AddSingleton(new PortProvider(port));
var commandTypeRegistrar = new ServiceTypeRegistrar(commandServiceProvider);
var consoleApp = new CommandApp<InteractiveCommand>(commandTypeRegistrar);
consoleApp.Configure(config =>
{
config.AddCommand<ProcessCommand>("process")
.WithDescription("Processes the provided input data into one or more output formats.");
});
await consoleApp.RunAsync(args);
await app.StopAsync();

View File

@@ -0,0 +1,23 @@
{
"profiles": {
"WSL": {
"commandName": "WSL2",
"launchUrl": "http://localhost:5118/",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_URLS": "http://localhost:5118"
},
"distributionName": ""
},
"http": {
"commandName": "Project",
"commandLineArgs": "process -d --disassembler ghidra M:\\Downloads\\Reversing\\NotYetAnalyzedAPKs\\pokemon_friends\\libil2cpp.so M:\\Downloads\\Reversing\\NotYetAnalyzedAPKs\\pokemon_friends\\global-metadata.dat",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5118"
}
},
"$schema": "https://json.schemastore.org/launchsettings.json"
}

View File

@@ -0,0 +1,30 @@
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI;
public class ServiceTypeRegistrar(IServiceCollection serviceCollection) : ITypeRegistrar
{
private readonly IServiceCollection _serviceCollection = serviceCollection;
private ServiceTypeResolver? _resolver;
public void Register(Type service, Type implementation)
{
_serviceCollection.AddSingleton(service, implementation);
}
public void RegisterInstance(Type service, object implementation)
{
_serviceCollection.AddSingleton(service, implementation);
}
public void RegisterLazy(Type service, Func<object> factory)
{
_serviceCollection.AddSingleton(service, _ => factory());
}
public ITypeResolver Build()
{
_resolver ??= new ServiceTypeResolver(_serviceCollection.BuildServiceProvider());
return _resolver;
}
}

View File

@@ -0,0 +1,13 @@
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI;
public class ServiceTypeResolver(IServiceProvider serviceProvider) : ITypeResolver
{
private readonly IServiceProvider _serviceProvider = serviceProvider;
public object? Resolve(Type? type)
=> type == null
? null
: _serviceProvider.GetService(type);
}

View File

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

View File

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

View File

@@ -0,0 +1,54 @@
using System.Reflection;
namespace Il2CppInspector.Redux.FrontendCore;
public static class Extensions
{
internal static bool GetAsBooleanOrDefault(this Dictionary<string, string> dict, string key, bool defaultValue)
{
if (dict.TryGetValue(key, out var value) && bool.TryParse(value, out var boolResult))
return boolResult;
return defaultValue;
}
internal static T GetAsEnumOrDefault<T>(this Dictionary<string, string> dict, string key, T defaultValue)
where T : struct, Enum
{
if (dict.TryGetValue(key, out var value) && Enum.TryParse<T>(value, true, out var enumResult))
return enumResult;
return defaultValue;
}
internal static string? GetAssemblyVersion(this Assembly assembly)
=> assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion;
public static WebApplication MapFrontendCore(this WebApplication app)
{
app.MapHub<Il2CppHub>("/il2cpp");
return app;
}
public static IServiceCollection AddFrontendCore(this IServiceCollection services)
{
services.AddSignalR(config =>
{
#if DEBUG
config.EnableDetailedErrors = true;
#endif
});
return services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.SetIsOriginAllowed(origin =>
origin.StartsWith("http://localhost") || origin.StartsWith("http://tauri.localhost"))
.AllowAnyHeader()
.WithMethods("GET", "POST")
.AllowCredentials();
});
});
}
}

View File

@@ -0,0 +1,8 @@
using System.Text.Json.Serialization;
namespace Il2CppInspector.Redux.FrontendCore;
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(Dictionary<string, string>))]
public partial class FrontendCoreJsonSerializerContext : JsonSerializerContext;

View File

@@ -0,0 +1,58 @@
using Microsoft.AspNetCore.SignalR;
namespace Il2CppInspector.Redux.FrontendCore;
public class Il2CppHub : Hub
{
private const string ContextKey = "context";
private UiContext State
{
get
{
if (!Context.Items.TryGetValue(ContextKey, out var context)
|| context is not UiContext ctx)
{
Context.Items[ContextKey] = ctx = new UiContext();
}
return ctx;
}
}
private UiClient Client => new(Clients.Caller);
public async Task OnUiLaunched()
{
await State.Initialize(Client);
}
public async Task SubmitInputFiles(List<string> inputFiles)
{
await State.LoadInputFiles(Client, inputFiles);
}
public async Task QueueExport(string exportTypeId, string outputDirectory, Dictionary<string, string> settings)
{
await State.QueueExport(Client, exportTypeId, outputDirectory, settings);
}
public async Task StartExport()
{
await State.StartExport(Client);
}
public async Task<IEnumerable<string>> GetPotentialUnityVersions()
{
return await State.GetPotentialUnityVersions();
}
public async Task ExportIl2CppFiles(string outputDirectory)
{
await State.ExportIl2CppFiles(Client, outputDirectory);
}
public async Task<string> GetInspectorVersion()
{
return await UiContext.GetInspectorVersion();
}
}

View File

@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<OutputType>Library</OutputType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Bin2Object\Bin2Object\Bin2Object.csproj" />
<ProjectReference Include="..\Il2CppInspector.Common\Il2CppInspector.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,23 @@
namespace Il2CppInspector.Redux.FrontendCore;
public class LoadingSession : IAsyncDisposable
{
private readonly UiClient _client;
private LoadingSession(UiClient client)
{
_client = client;
}
public static async Task<LoadingSession> Start(UiClient client)
{
await client.BeginLoading();
return new LoadingSession(client);
}
public async ValueTask DisposeAsync()
{
await _client.FinishLoading();
GC.SuppressFinalize(this);
}
}

View File

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

View File

@@ -0,0 +1,66 @@
using Il2CppInspector.Model;
using Il2CppInspector.Outputs;
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public class CSharpStubOutput : IOutputFormatProvider
{
public static string Id => "cs";
private class Settings(Dictionary<string, string> settings)
{
public readonly CSharpLayout Layout = settings.GetAsEnumOrDefault("layout", CSharpLayout.SingleFile);
public readonly bool FlattenHierarchy = settings.GetAsBooleanOrDefault("flattenhierarchy", false);
public readonly TypeSortingMode SortingMode = settings.GetAsEnumOrDefault("sortingmode", TypeSortingMode.Alphabetical);
public readonly bool SuppressMetadata = settings.GetAsBooleanOrDefault("suppressmetadata", false);
public readonly bool MustCompile = settings.GetAsBooleanOrDefault("mustcompile", false);
public readonly bool SeperateAssemblyAttributes = settings.GetAsBooleanOrDefault("seperateassemblyattributes", true);
}
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
{
var settings = new Settings(settingsDict);
var writer = new CSharpCodeStubs(model.TypeModel)
{
SuppressMetadata = settings.SuppressMetadata,
MustCompile = settings.MustCompile
};
await client.ShowLogMessage("Writing C# type definitions");
var outputPathFile = Path.Join(outputPath, "il2cpp.cs");
switch (settings.Layout, settings.SortingMode)
{
case (CSharpLayout.SingleFile, TypeSortingMode.TypeDefinitionIndex):
writer.WriteSingleFile(outputPathFile, info => info.Index);
break;
case (CSharpLayout.SingleFile, TypeSortingMode.Alphabetical):
writer.WriteSingleFile(outputPathFile, info => info.Name);
break;
case (CSharpLayout.Namespace, TypeSortingMode.TypeDefinitionIndex):
writer.WriteFilesByNamespace(outputPath, info => info.Index, settings.FlattenHierarchy);
break;
case (CSharpLayout.Namespace, TypeSortingMode.Alphabetical):
writer.WriteFilesByNamespace(outputPath, info => info.Name, settings.FlattenHierarchy);
break;
case (CSharpLayout.Assembly, TypeSortingMode.TypeDefinitionIndex):
writer.WriteFilesByAssembly(outputPath, info => info.Index, settings.SeperateAssemblyAttributes);
break;
case (CSharpLayout.Assembly, TypeSortingMode.Alphabetical):
writer.WriteFilesByAssembly(outputPath, info => info.Name, settings.SeperateAssemblyAttributes);
break;
case (CSharpLayout.Class, _):
writer.WriteFilesByClass(outputPath, settings.FlattenHierarchy);
break;
case (CSharpLayout.Tree, _):
writer.WriteFilesByClassTree(outputPath, settings.SeperateAssemblyAttributes);
break;
}
}
}

View File

@@ -0,0 +1,31 @@
using Il2CppInspector.Cpp;
using Il2CppInspector.Cpp.UnityHeaders;
using Il2CppInspector.Model;
using Il2CppInspector.Outputs;
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public class CppScaffoldingOutput : IOutputFormatProvider
{
public static string Id => "cppscaffolding";
private class Settings(Dictionary<string, string> settings)
{
public readonly string UnityVersion = settings.GetValueOrDefault("unityversion", "");
public readonly CppCompilerType Compiler = settings.GetAsEnumOrDefault("compiler", CppCompilerType.GCC);
}
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
{
var settings = new Settings(settingsDict);
await client.ShowLogMessage($"Building application model for Unity {settings.UnityVersion}/{settings.Compiler}");
model.Build(new UnityVersion(settings.UnityVersion), settings.Compiler);
await client.ShowLogMessage("Generating C++ scaffolding");
var scaffolding = new CppScaffolding(model);
await client.ShowLogMessage("Writing C++ scaffolding");
scaffolding.Write(outputPath);
}
}

View File

@@ -0,0 +1,53 @@
using Il2CppInspector.Cpp;
using Il2CppInspector.Cpp.UnityHeaders;
using Il2CppInspector.Model;
using Il2CppInspector.Outputs;
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public class DisassemblerMetadataOutput : IOutputFormatProvider
{
public static string Id => "disassemblermetadata";
private class Settings(Dictionary<string, string> dict)
{
public readonly DisassemblerType Disassembler = dict.GetAsEnumOrDefault("disassembler", DisassemblerType.IDA);
public readonly string UnityVersion = dict.GetValueOrDefault("unityversion", "");
}
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
{
var settings = new Settings(settingsDict);
await client.ShowLogMessage($"Building application model for Unity {settings.UnityVersion}/{CppCompilerType.GCC}");
model.Build(new UnityVersion(settings.UnityVersion), CppCompilerType.GCC);
var headerPath = Path.Join(outputPath, "il2cpp.h");
{
await client.ShowLogMessage("Generating C++ types");
var cppScaffolding = new CppScaffolding(model, useBetterArraySize: true);
await client.ShowLogMessage("Writing C++ types");
cppScaffolding.WriteTypes(headerPath);
}
var metadataPath = Path.Join(outputPath, "il2cpp.json");
{
await client.ShowLogMessage("Generating disassembler metadata");
var jsonMetadata = new JSONMetadata(model);
await client.ShowLogMessage("Writing disassembler metadata");
jsonMetadata.Write(metadataPath);
}
if (settings.Disassembler != DisassemblerType.None)
{
var scriptPath = Path.Join(outputPath, "il2cpp.py");
await client.ShowLogMessage($"Generating python script for {settings.Disassembler}");
var script = new PythonScript(model);
await client.ShowLogMessage($"Writing python script for {settings.Disassembler}");
script.WriteScriptToFile(scriptPath, settings.Disassembler.ToString(), headerPath, metadataPath);
}
}
}

View File

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

View File

@@ -0,0 +1,27 @@
using Il2CppInspector.Model;
using Il2CppInspector.Outputs;
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public class DummyDllOutput : IOutputFormatProvider
{
public static string Id => "dummydlls";
private class Settings(Dictionary<string, string> dict)
{
public readonly bool SuppressMetadata = dict.GetAsBooleanOrDefault("suppressmetadata", false);
}
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
{
var outputSettings = new Settings(settingsDict);
await client.ShowLogMessage("Generating .NET dummy assemblies");
var shims = new AssemblyShims(model.TypeModel)
{
SuppressMetadata = outputSettings.SuppressMetadata
};
shims.Write(outputPath, client.EventHandler);
}
}

View File

@@ -0,0 +1,14 @@
using Il2CppInspector.Model;
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public interface IOutputFormat
{
public Task Export(AppModel model, UiClient client, string outputPath,
Dictionary<string, string> settingsDict);
}
public interface IOutputFormatProvider : IOutputFormat
{
public static abstract string Id { get; }
}

View File

@@ -0,0 +1,38 @@
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public static class OutputFormatRegistry
{
public static IEnumerable<string> AvailableOutputFormats => OutputFormats.Keys;
private static readonly Dictionary<string, IOutputFormat> OutputFormats = [];
public static void RegisterOutputFormat<T>() where T : IOutputFormatProvider, new()
{
if (OutputFormats.ContainsKey(T.Id))
throw new InvalidOperationException("An output format with this id was already registered.");
OutputFormats[T.Id] = new T();
}
public static IOutputFormat GetOutputFormat(string id)
{
if (!OutputFormats.TryGetValue(id, out var format))
throw new ArgumentException($"Failed to find output format for id {id}", nameof(id));
return format;
}
private static void RegisterBuiltinOutputFormats()
{
RegisterOutputFormat<CSharpStubOutput>();
RegisterOutputFormat<VsSolutionOutput>();
RegisterOutputFormat<DummyDllOutput>();
RegisterOutputFormat<DisassemblerMetadataOutput>();
RegisterOutputFormat<CppScaffoldingOutput>();
}
static OutputFormatRegistry()
{
RegisterBuiltinOutputFormats();
}
}

View File

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

View File

@@ -0,0 +1,29 @@
using Il2CppInspector.Model;
using Il2CppInspector.Outputs;
namespace Il2CppInspector.Redux.FrontendCore.Outputs;
public class VsSolutionOutput : IOutputFormatProvider
{
public static string Id => "vssolution";
private class Settings(Dictionary<string, string> settings)
{
public readonly string UnityPath = settings.GetValueOrDefault("unitypath", "");
public readonly string UnityAssembliesPath = settings.GetValueOrDefault("unityassembliespath", "");
}
public async Task Export(AppModel model, UiClient client, string outputPath, Dictionary<string, string> settingsDict)
{
var settings = new Settings(settingsDict);
var writer = new CSharpCodeStubs(model.TypeModel)
{
MustCompile = true,
SuppressMetadata = true
};
await client.ShowLogMessage("Writing Visual Studio solution");
writer.WriteSolution(outputPath, settings.UnityPath, settings.UnityAssembliesPath);
}
}

View File

@@ -0,0 +1,54 @@
namespace Il2CppInspector.Redux.FrontendCore;
public static class PathHeuristics
{
private static readonly string[] AllowedMetadataExtensionComponents =
[
"dat", "dec"
];
private static readonly string[] AllowedMetadataNameComponents =
[
"metadata"
];
private static readonly string[] AllowedBinaryPathComponents =
[
"GameAssembly",
"il2cpp",
"UnityFramework"
];
private static readonly string[] AllowedBinaryExtensionComponents =
[
"dll", "so", "exe", "bin", "prx", "sprx", "dylib"
];
public static bool IsMetadataPath(string path)
{
var extension = Path.GetExtension(path);
if (AllowedMetadataExtensionComponents.Any(extension.Contains))
return true;
var filename = Path.GetFileNameWithoutExtension(path);
if (AllowedMetadataNameComponents.Any(filename.Contains))
return true;
return false;
}
public static bool IsBinaryPath(string path)
{
var extension = Path.GetExtension(path);
// empty to allow macho binaries which do not have an extension
if (extension == "" || AllowedBinaryExtensionComponents.Any(extension.Contains))
return true;
var filename = Path.GetFileNameWithoutExtension(path);
if (AllowedBinaryPathComponents.Any(filename.Contains))
return true;
return false;
}
}

View File

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

View File

@@ -0,0 +1,44 @@
using Microsoft.AspNetCore.SignalR;
namespace Il2CppInspector.Redux.FrontendCore;
public class UiClient(ISingleClientProxy client)
{
private EventHandler<string>? _handler;
public EventHandler<string> EventHandler
{
get
{
_handler ??= (_, status) =>
{
#pragma warning disable CS4014
ShowLogMessage(status);
#pragma warning restore CS4014
};
return _handler;
}
}
public async Task ShowLogMessage(string message, CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(ShowLogMessage), message, cancellationToken);
public async Task BeginLoading(CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(BeginLoading), cancellationToken);
public async Task FinishLoading(CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(FinishLoading), cancellationToken);
public async Task ShowInfoToast(string message, CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(ShowInfoToast), message, cancellationToken);
public async Task ShowSuccessToast(string message, CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(ShowSuccessToast), message, cancellationToken);
public async Task ShowErrorToast(string message, CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(ShowErrorToast), message, cancellationToken);
public async Task OnImportCompleted(CancellationToken cancellationToken = default)
=> await client.SendAsync(nameof(OnImportCompleted), cancellationToken);
}

View File

@@ -0,0 +1,251 @@
using System.Diagnostics;
using Il2CppInspector.Cpp.UnityHeaders;
using Il2CppInspector.Model;
using Il2CppInspector.Redux.FrontendCore.Outputs;
using Il2CppInspector.Reflection;
using Inspector = Il2CppInspector.Il2CppInspector;
namespace Il2CppInspector.Redux.FrontendCore;
public class UiContext
{
private const string BugReportSuffix =
"""
If you believe this is a bug in Il2CppInspectorRedux, please use the CLI version to generate the complete output and paste it when filing a bug report.
Do not send a screenshot of this error!
""";
private Metadata? _metadata;
private IFileFormatStream? _binary;
private readonly List<AppModel> _appModels = [];
private readonly List<UnityHeaders> _potentialUnityVersions = [];
private readonly LoadOptions _loadOptions = new();
private readonly List<(string FormatId, string OutputDirectory, Dictionary<string, string> Settings)> _queuedExports = [];
private async Task<bool> TryLoadMetadataFromStream(UiClient client, MemoryStream stream)
{
try
{
_metadata = Metadata.FromStream(stream, client.EventHandler);
return true;
}
catch (Exception e)
{
await client.ShowErrorToast($"{e.Message}{BugReportSuffix}");
}
return false;
}
private async Task<bool> TryLoadBinaryFromStream(UiClient client, MemoryStream stream)
{
await client.ShowLogMessage("Processing binary");
try
{
var file = FileFormatStream.Load(stream, _loadOptions, client.EventHandler)
?? throw new InvalidOperationException("Failed to determine binary file format.");
if (file.NumImages == 0)
throw new InvalidOperationException("Failed to find any binary images in the file");
_binary = file;
return true;
}
catch (Exception e)
{
await client.ShowErrorToast($"{e.Message}{BugReportSuffix}");
}
return false;
}
private async Task<bool> TryInitializeInspector(UiClient client)
{
Debug.Assert(_binary != null);
Debug.Assert(_metadata != null);
_appModels.Clear();
var inspectors = Inspector.LoadFromStream(_binary, _metadata, client.EventHandler);
if (inspectors.Count == 0)
{
await client.ShowErrorToast(
"""
Failed to auto-detect any IL2CPP binary images in the provided files.
This may mean the binary file is packed, encrypted or obfuscated, that the file
is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data.
Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!
""");
_binary = null;
return false;
}
foreach (var inspector in inspectors)
{
await client.ShowLogMessage(
$"Building .NET type model for {inspector.BinaryImage.Format}/{inspector.BinaryImage.Arch} image");
try
{
var typeModel = new TypeModel(inspector);
// Just create the app model, do not initialize it - this is done lazily depending on the exports
_appModels.Add(new AppModel(typeModel, makeDefaultBuild: false));
}
catch (Exception e)
{
await client.ShowErrorToast($"Failed to build type model: {e.Message}{BugReportSuffix}");
// Clear out failed metadata and binary so subsequent loads do not use any stale data.
_metadata = null;
_binary = null;
return false;
}
}
_potentialUnityVersions.Clear();
_potentialUnityVersions.AddRange(UnityHeaders.GuessHeadersForBinary(_appModels[0].Package.Binary));
return true;
}
public async Task Initialize(UiClient client, CancellationToken cancellationToken = default)
{
await client.ShowSuccessToast("SignalR initialized!", cancellationToken);
}
public async Task LoadInputFiles(UiClient client, List<string> inputFiles,
CancellationToken cancellationToken = default)
{
await using (await LoadingSession.Start(client))
{
var streams = Inspector.GetStreamsFromPackage(inputFiles);
if (streams != null)
{
// The input files contained a package that provides the metadata and binary.
// Use these instead of parsing all files individually.
if (!await TryLoadMetadataFromStream(client, streams.Value.Metadata))
return;
if (!await TryLoadBinaryFromStream(client, streams.Value.Binary))
return;
}
else
{
foreach (var inputFile in inputFiles)
{
if (_metadata != null && _binary != null)
break;
await client.ShowLogMessage($"Processing {inputFile}", cancellationToken);
var data = await File.ReadAllBytesAsync(inputFile, cancellationToken);
var stream = new MemoryStream(data);
if ( _metadata == null && PathHeuristics.IsMetadataPath(inputFile))
{
if (await TryLoadMetadataFromStream(client, stream))
{
await client.ShowSuccessToast($"Loaded metadata (v{_metadata!.Version}) from {inputFile}", cancellationToken);
}
}
else if (_binary == null && PathHeuristics.IsBinaryPath(inputFile))
{
stream.Position = 0;
_loadOptions.BinaryFilePath = inputFile;
if (await TryLoadBinaryFromStream(client, stream))
{
await client.ShowSuccessToast($"Loaded binary from {inputFile}", cancellationToken);
}
}
}
}
if (_metadata != null && _binary != null)
{
if (await TryInitializeInspector(client))
{
await client.ShowSuccessToast($"Successfully loaded IL2CPP (v{_appModels[0].Package.Version}) data!", cancellationToken);
await client.OnImportCompleted(cancellationToken);
}
}
}
}
public Task QueueExport(UiClient client, string exportFormatId, string outputDirectory,
Dictionary<string, string> settings, CancellationToken cancellationToken = default)
{
_queuedExports.Add((exportFormatId, outputDirectory, settings));
return Task.CompletedTask;
}
public async Task StartExport(UiClient client, CancellationToken cancellationToken = default)
{
// todo: support different app model selection (when loading packages)
Debug.Assert(_appModels.Count > 0);
await using (await LoadingSession.Start(client))
{
var model = _appModels[0];
foreach (var (formatId, outputDirectory, settings) in _queuedExports)
{
try
{
var outputFormat = OutputFormatRegistry.GetOutputFormat(formatId);
await outputFormat.Export(model, client, outputDirectory, settings);
}
catch (Exception ex)
{
await client.ShowErrorToast($"Export for format {formatId} failed: {ex}",
cancellationToken);
}
}
_queuedExports.Clear();
}
await client.ShowSuccessToast("Export finished", cancellationToken);
}
public Task<List<string>> GetPotentialUnityVersions()
{
return Task.FromResult(_potentialUnityVersions.Select(x => x.VersionRange.Min.ToString()).ToList());
}
public async Task ExportIl2CppFiles(UiClient client, string outputDirectory, CancellationToken cancellationToken = default)
{
Debug.Assert(_appModels.Count > 0);
var pkg = _appModels[0].Package;
await using (await LoadingSession.Start(client))
{
await Task.Run(async () =>
{
Directory.CreateDirectory(outputDirectory);
await client.ShowLogMessage("Extracting IL2CPP binary", cancellationToken);
pkg.SaveBinaryToFile(Path.Join(outputDirectory, pkg.BinaryImage.DefaultFilename));
await client.ShowLogMessage("Extracting IL2CPP metadata", cancellationToken);
pkg.SaveMetadataToFile(Path.Join(outputDirectory, "global-metadata.dat"));
await client.ShowSuccessToast("Successfully extracted IL2CPP files.", cancellationToken);
}, cancellationToken);
}
}
public static Task<string> GetInspectorVersion()
{
return Task.FromResult(typeof(UiContext).Assembly.GetAssemblyVersion() ?? "<unknown>");
}
}

10
Il2CppInspector.Redux.GUI.UI/.gitignore vendored Normal file
View File

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

View File

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

View File

@@ -0,0 +1,17 @@
{
"$schema": "https://next.shadcn-svelte.com/schema.json",
"style": "new-york",
"tailwind": {
"config": "tailwind.config.ts",
"css": "src\\app.css",
"baseColor": "stone"
},
"aliases": {
"components": "$lib/components",
"utils": "$lib/utils",
"ui": "$lib/components/ui",
"hooks": "$lib/hooks"
},
"typescript": true,
"registry": "https://next.shadcn-svelte.com/registry"
}

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