25 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
Luke
ec76447122 Fix misc. issues in the generated disassembler scripts (#17)
* attempt to fix ghidra script issues

* add ghidra python 3 note to readme

* fix binary ninja script erroring on function type parsing

* fix ida script not skipping function creation on pe binaries

* fix writing of multibyte strings breaking fake string segment

* also adjust binja write_string impl
2025-01-25 14:21:56 +01:00
LukeFZ
4e46c29cee Merge branch 'master' of https://github.com/LukeFZ/Il2CppInspectorRedux 2024-12-13 20:58:57 +01:00
LukeFZ
507c42024e Fix loading of 32-Bit binaries by removing shadowed Bits property 2024-12-13 20:58:41 +01:00
Luke
8403f8a62d Fix incorrect if condition in GetVTable causing them to all be empty 2024-11-19 00:13:26 +01:00
LukeFZ
5254c4b85a adjust v24.4<->v24.5 condition and update sln file 2024-11-17 23:20:50 +01:00
Luke
b05c03964a Struct reading and disassembly script overhaul, various misc. loading fixes, bump to .NET 9 (#13)
* Bump projects to .net 9 and update nugets

* add VersionedSerialization + source generator

* migrate versioning to StructVersion class, add handling/detection for 29.2/31.2

* add new struct definitions

* rename serialization methods and add BinaryObjectStreamReader for interop

* Rework metadata struct loading to use new struct versioning

* move 29/31.1/.2 to use tags (-2022,-2023) instead of minor versions

* fix metadata usage validity checks

* rework code registration offsetting a bit and add second 29/31.1 condition

* tweak .1 condition (again)

* 29/31.2 was a psyop

* also remove 29.2 from the readme

* remove loading of packed dlls - this was a very unsafe feature

* support auto-recovering type indices from type handles
fixes loading of memory-dumped v29+ libraries since those replacee their class indices on load with a pointer to the corresponding type

* support loading PEs without an export table

* also read UnresolvedVirtualCallCount on regular v31

* Disable plugin loading for now

* Overhaul disassembler script + add Binary Ninja target (#12)

* Overhaul diassembler scripts:
- No longer defines top level functions
- Split into three classes: StatusHandler (like before), DisassemblerInterface (for interfacing with the used program API), ScriptContext (for definiting general functions that use the disassembler interface)
- Add type annotations to all class methods and remove 2.7 compatibility stuff (Ghidra now supports Python 3 so this is unnecessary anymore)
- Disassembler backends are now responsible for launching metadata/script processing, to better support disassembler differences
- String handling is back in the base ScriptContext class, disassembler interfaces opt into the fake string segment creation and fall back to the old method if it isn't supported

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

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

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

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

* also cache API function types in binary ninja backend

* fix ida script and disable folders again

* Fix metadata usage issues caused by it being a value type now

* make TryMapVATR overrideable and implement it for ELFs

* Make field offset reading use TryMapVATR to reduce exceptions

* Fix NRE in Assembly ctor on < v24.2

* Update actions workflow to produce cross-platform CLI binaries, update readme to reflect .net 9 changes

* workflow: only restore packages for projects that are being built

* workflow: tweak caching and fix gui compilation

* workflow: remove double .zip in CLI artifact name

* 29/31.2 don't actually exist, this logic is not needed
2024-11-14 14:32:11 +01:00
213 changed files with 21371 additions and 1094 deletions

View File

@@ -3,7 +3,111 @@ name: Il2CppInspectorRedux Build
on: [push, workflow_dispatch]
jobs:
build-gui:
build-redux-gui: # this already includes stuff only relevant for linux/macos for when the gui is released on those platforms
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
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
steps:
@@ -32,10 +136,10 @@ jobs:
- name: Upload GUI Artifact
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.GUI
name: Il2CppInspectorRedux.Old.GUI
path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish
build-cli:
build-old-cli:
runs-on: ubuntu-latest
strategy:
matrix:
@@ -47,10 +151,10 @@ jobs:
with:
submodules: true
- name: Setup .NET SDK
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: ${{ matrix.dotnet-version }}
- uses: actions/cache@v3
with:
@@ -59,11 +163,6 @@ jobs:
restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.CLI
@@ -73,5 +172,5 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
name: Il2CppInspectorRedux.Old.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }}

3
.gitignore vendored
View File

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

2
.gitmodules vendored
View File

@@ -1,3 +1,3 @@
[submodule "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.
*/
using System;
namespace Il2CppInspector.Cpp
{
// A field in a C++ type

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

@@ -176,7 +176,7 @@ namespace Il2CppInspector
try {
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
null, new[] { typeof(BinaryObjectStream), typeof(LoadOptions), typeof(EventHandler<string>) }, null)
.Invoke(null, new object[] { binaryObjectStream, loadOptions, statusCallback }) is IFileFormatStream loaded) {
.Invoke(null, [binaryObjectStream, loadOptions, statusCallback]) is IFileFormatStream loaded) {
loaded.IsModified |= preProcessResult.IsStreamModified;
return loaded;
@@ -207,8 +207,6 @@ namespace Il2CppInspector
public virtual string Arch => throw new NotImplementedException();
public virtual int Bits => throw new NotImplementedException();
// Extra parameters to be passed to a loader
protected LoadOptions LoadOptions;

View File

@@ -6,6 +6,7 @@
using System;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization.Attributes;
namespace Il2CppInspector
{
@@ -31,6 +32,7 @@ namespace Il2CppInspector
LC_DYLD_INFO_ONLY = 0x80000022,
LC_FUNCTION_STARTS = 0x26,
LC_ENCRYPTION_INFO_64 = 0x2C,
LC_DYLD_CHAINED_FIXUPS = 0x80000034,
CPU_TYPE_X86 = 7,
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
@@ -172,4 +174,47 @@ namespace Il2CppInspector
public bool r_extern => ((r_data >> 27) & 1) == 1;
public uint r_type => r_data >> 28;
}
[VersionedStruct]
public partial struct MachODyldChainedFixupsHeader
{
public uint FixupsVersion;
public uint StartsOffset;
public uint ImportsOffset;
public uint SymbolsOffset;
public uint ImportsCount;
public uint ImportsFormat;
public uint SymbolsFormat;
}
[VersionedStruct]
public partial struct MachODyldChainedStartsInSegment
{
public const ushort DYLD_CHAINED_PTR_START_NONE = 0xffff;
public uint StructSize;
public ushort PageSize;
public ushort PointerFormat;
public ulong SegmentOffset;
public uint MaxValidPointer;
public ushort PageCount;
}
public enum MachODyldChainedPtr
{
DYLD_CHAINED_PTR_64 = 2,
DYLD_CHAINED_PTR_64_OFFSET = 6,
}
[VersionedStruct]
public partial struct MachODyldChainedPtr64Rebase
{
private ulong _value;
public ulong Target => _value & 0xfffffffff;
public ulong High8 => (_value >> 36) & 0xff;
public ulong Reserved => (_value >> (36 + 8)) & 0x7f;
public ulong Next => (_value >> (36 + 8 + 7)) & 0xfff;
public bool Bind => ((_value >> (36 + 8 + 7 + 12)) & 0x1) == 0x1;
}
}

View File

@@ -4,12 +4,13 @@
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
@@ -172,15 +173,21 @@ namespace Il2CppInspector
if (encryptionInfo.CryptID != 0)
throw new NotImplementedException("This Mach-O executable is encrypted with FairPlay DRM and cannot be processed. Please provide a decrypted version of the executable.");
break;
case MachO.LC_DYLD_CHAINED_FIXUPS:
var chainedFixupsInfo = ReadObject<MachOLinkEditDataCommand>();
ApplyChainedFixups(chainedFixupsInfo);
break;
}
// There might be other data after the load command so always use the specified total size to step forwards
Position = startPos + loadCommand.Size;
}
// Note: Some binaries do not have __mod_init_func, but instead just __init_offset with offsets to the init functions. This check is disabled.
// Must find __mod_init_func
if (funcTab == null)
return false;
//if (funcTab == null)
// return false;
// Process relocations
foreach (var section in machoSections) {
@@ -188,7 +195,7 @@ namespace Il2CppInspector
// TODO: Implement Mach-O relocations
if (rels.Any()) {
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
AnsiConsole.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
break;
}
}
@@ -282,7 +289,7 @@ namespace Il2CppInspector
: SymbolType.Unknown;
if (type == SymbolType.Unknown) {
Console.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} " + CxxDemangler.CxxDemangler.Demangle(name));
AnsiConsole.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} {name}");
}
// Ignore duplicates
@@ -290,7 +297,82 @@ namespace Il2CppInspector
}
}
public override uint[] GetFunctionTable() => ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
private void ApplyChainedFixups(in MachOLinkEditDataCommand info)
{
var chainedFixupsHeader = ReadVersionedObject<MachODyldChainedFixupsHeader>(info.Offset);
if (chainedFixupsHeader.FixupsVersion != 0)
{
AnsiConsole.WriteLine($"Unsupported chained fixups version: {chainedFixupsHeader.FixupsVersion}");
return;
}
if (chainedFixupsHeader.ImportsFormat != 1 /* DYLD_CHAINED_IMPORT */)
{
AnsiConsole.WriteLine($"Unsupported chained fixups import format: {chainedFixupsHeader.ImportsFormat}");
return;
}
//var importsBase = info.Offset + chainedFixupsHeader.ImportsOffset;
//var imports = ReadPrimitiveArray<uint>(importsBase,
// chainedFixupsHeader.ImportsCount);
//var symbolsBase = info.Offset + chainedFixupsHeader.SymbolsOffset; // todo: apparently this supports zlib
var startsBase = info.Offset + chainedFixupsHeader.StartsOffset;
var segmentCount = ReadPrimitive<uint>(startsBase);
var segmentStartOffsets = ReadPrimitiveArray<uint>(startsBase + 4, segmentCount);
foreach (var startOffset in segmentStartOffsets)
{
if (startOffset == 0)
continue;
var startsInfo = ReadVersionedObject<MachODyldChainedStartsInSegment>(startsBase + startOffset);
if (startsInfo.SegmentOffset == 0)
continue;
var pointerFormat = (MachODyldChainedPtr)startsInfo.PointerFormat;
var pages = ReadPrimitiveArray<ushort>(
startsBase + startOffset + MachODyldChainedStartsInSegment.Size(), startsInfo.PageCount);
for (var i = 0; i < pages.Length; i++)
{
var page = pages[i];
if (page == MachODyldChainedStartsInSegment.DYLD_CHAINED_PTR_START_NONE)
continue;
var chainOffset = startsInfo.SegmentOffset + (ulong)(i * startsInfo.PageSize) + page;
while (true)
{
var currentEntry = ReadVersionedObject<MachODyldChainedPtr64Rebase>((long)chainOffset);
var fixedValue = 0ul;
if (!currentEntry.Bind) // todo: bind
{
fixedValue = pointerFormat switch
{
MachODyldChainedPtr.DYLD_CHAINED_PTR_64
or MachODyldChainedPtr.DYLD_CHAINED_PTR_64_OFFSET
=> currentEntry.High8 << 56 | currentEntry.Target,
_ => fixedValue
};
Write((long)chainOffset, fixedValue);
}
if (currentEntry.Next == 0)
break;
chainOffset += currentEntry.Next * 4;
}
}
}
}
public override uint[] GetFunctionTable() => funcTab == null ? [] : ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;

View File

@@ -4,13 +4,14 @@
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
@@ -102,7 +103,7 @@ namespace Il2CppInspector
// Unpacking must be done starting here, one byte after the end of the headers
// Packed or previously packed with Themida? This is purely for information
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _)
Console.WriteLine("Themida protection detected");
AnsiConsole.WriteLine("Themida protection detected");
// Packed with anything (including Themida)?
mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null;
@@ -114,26 +115,31 @@ namespace Il2CppInspector
section.Name = wantedSectionTypes[section.Characteristics];
// Get base of code
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections
.FirstOrDefault(x => x.Characteristics.HasFlag(PE.IMAGE_SCN_MEM_EXECUTE))?.PointerToRawData ?? 0;
// Confirm that .rdata section begins at same place as IAT
var rData = sections.First(x => x.Name == ".rdata");
mightBePacked |= rData.VirtualAddress != IATStart;
var rData = sections.FirstOrDefault(x => x.Name == ".rdata");
mightBePacked |= rData == null || rData.VirtualAddress != IATStart;
if (rData != null)
{
// Calculate start of function pointer table
pFuncTable = rData.PointerToRawData + IATSize;
// Calculate start of function pointer table
pFuncTable = rData.PointerToRawData + IATSize;
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
Position = pFuncTable;
if (pe is PEOptHeader32) {
while (ReadUInt32() != 0)
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
Position = pFuncTable;
if (pe is PEOptHeader32)
{
while (ReadUInt32() != 0)
pFuncTable += 4;
pFuncTable += 4;
pFuncTable += 4;
}
else {
while (ReadUInt64() != 0)
}
else
{
while (ReadUInt64() != 0)
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
@@ -191,7 +197,27 @@ namespace Il2CppInspector
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) {
if (uiAddr == 0)
return 0;

View File

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

View File

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

View File

@@ -6,12 +6,13 @@
*/
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Spectre.Console;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Reflection;
using System.Text.RegularExpressions;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using VersionedSerialization;
namespace Il2CppInspector
@@ -191,7 +192,7 @@ namespace Il2CppInspector
var symbols = Image.GetSymbolTable();
if (symbols.Any()) {
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
AnsiConsole.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
symbols.TryGetValue("g_CodeRegistration", out var code);
symbols.TryGetValue("g_MetadataRegistration", out var metadata);
@@ -202,13 +203,13 @@ namespace Il2CppInspector
symbols.TryGetValue("_g_MetadataRegistration", out metadata);
if (code != null && metadata != null) {
Console.WriteLine("Required structures acquired from symbol lookup");
AnsiConsole.WriteLine("Required structures acquired from symbol lookup");
return (code.VirtualAddress, metadata.VirtualAddress);
} else {
Console.WriteLine("No matches in symbol table");
AnsiConsole.WriteLine("No matches in symbol table");
}
} else if (symbols != null) {
Console.WriteLine("No symbol table present in binary file");
AnsiConsole.WriteLine("No symbol table present in binary file");
} else {
Console.WriteLine("Symbol table search not implemented for this binary format");
}
@@ -227,12 +228,12 @@ namespace Il2CppInspector
var (code, metadata) = ConsiderCode(Image, loc);
if (code != 0) {
RegistrationFunctionPointer = loc + Image.GlobalOffset;
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
AnsiConsole.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
return (code, metadata);
}
}
Console.WriteLine("No matches via code heuristics");
AnsiConsole.WriteLine("No matches via code heuristics");
return null;
}
@@ -244,11 +245,11 @@ namespace Il2CppInspector
var (codePtr, metadataPtr) = ImageScan(Metadata);
if (codePtr == 0) {
Console.WriteLine("No matches via data heuristics");
AnsiConsole.WriteLine("No matches via data heuristics");
return null;
}
Console.WriteLine("Required structures acquired from data heuristics");
AnsiConsole.WriteLine("Required structures acquired from data heuristics");
return (codePtr, metadataPtr);
}
@@ -274,8 +275,8 @@ namespace Il2CppInspector
var pointerSize = Image.Bits == 32 ? 4u : 8u;
Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
AnsiConsole.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
AnsiConsole.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
// Root structures from which we find everything else
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
@@ -295,8 +296,11 @@ namespace Il2CppInspector
*/
if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.TypeDefinitionsSizesCount)
|| CodeRegistration.ReversePInvokeWrapperCount > 0x10000
|| CodeRegistration.UnresolvedVirtualCallCount > 0x4000 // >= 22
|| CodeRegistration.InteropDataCount > 0x1000 // >= 23
// L-NOTE: These below boundaries have been updated already as some games
// 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))
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
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];
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())
{
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);
}
TypeReferences = builder.MoveToImmutable();

View File

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

View File

@@ -230,7 +230,8 @@ namespace Il2CppInspector
// genericAdjustorThunks was inserted before invokerPointersCount in 24.5 and 27.1
// pointer expected if we need to bump version
if (Image.Version == MetadataVersions.V244 && cr.InvokerPointersCount > 0x50000)
if (Image.Version == MetadataVersions.V244 &&
(cr.InvokerPointersCount > 0x50000 || cr.ReversePInvokeWrapperCount > cr.ReversePInvokeWrappers))
{
Image.Version = MetadataVersions.V245;
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);
@@ -276,21 +277,12 @@ namespace Il2CppInspector
vas = FindAllMappedWords(imageBytes, typesLength).Select(a => a - mrSize + ptrSize * 4);
// >= 19 && < 27
if (Image.Version < MetadataVersions.V270)
foreach (var va in vas)
{
var mr = Image.ReadMappedVersionedObject<Il2CppMetadataRegistration>(va);
if (mr.MetadataUsagesCount == (ulong) metadata.MetadataUsageLists.Length)
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
// >= 19
// Luke: Previously, a check comparing MetadataUsagesCount was used here,
// 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+
// on older versions as well, so we'll just use it for all cases.
if (Image.Version >= MetadataVersions.V190)
{
foreach (var va in vas)
{
@@ -303,6 +295,7 @@ namespace Il2CppInspector
}
}
}
if (metadataRegistration == 0)
return (0, 0);

View File

@@ -5,15 +5,10 @@
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using Il2CppInspector.Next;
using Il2CppInspector.Next.Metadata;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization;
namespace Il2CppInspector
@@ -48,7 +43,15 @@ namespace Il2CppInspector
public ImmutableArray<uint> VTableMethodIndices { 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
public bool IsModified { get; private set; } = false;
@@ -92,13 +95,63 @@ namespace Il2CppInspector
// Set object versioning for Bin2Object from metadata version
Version = new StructVersion(Header.Version);
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) {
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V390) {
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
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
// 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
@@ -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,
// 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) {
var realHeaderLength = Header.StringLiteralOffset;
if (!pluginResult.SkipValidation)
{
var realHeaderLength = Version >= MetadataVersions.V380
? Header.StringLiterals.Offset
: Header.StringLiteralOffset;
if (realHeaderLength != Sizeof<Il2CppGlobalMetadataHeader>()) {
if (Version == MetadataVersions.V240) {
@@ -122,10 +178,10 @@ namespace Il2CppInspector
throw new InvalidOperationException("Could not verify the integrity of the metadata file or accurately identify the metadata sub-version");
}
}
// Load all the relevant metadata using offsets provided in the header
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
// 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;
// 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))
throw new InvalidOperationException("Could not verify the integrity of the metadata file image list");
}
Types = ReadVersionedObjectArray<Il2CppTypeDefinition>(Header.TypeDefinitionsOffset, Header.TypeDefinitionsSize / Sizeof<Il2CppTypeDefinition>());
Methods = ReadVersionedObjectArray<Il2CppMethodDefinition>(Header.MethodsOffset, Header.MethodsSize / Sizeof<Il2CppMethodDefinition>());
Params = ReadVersionedObjectArray<Il2CppParameterDefinition>(Header.ParametersOffset, Header.ParametersSize / Sizeof<Il2CppParameterDefinition>());
Fields = ReadVersionedObjectArray<Il2CppFieldDefinition>(Header.FieldsOffset, Header.FieldsSize / Sizeof<Il2CppFieldDefinition>());
FieldDefaultValues = ReadVersionedObjectArray<Il2CppFieldDefaultValue>(Header.FieldDefaultValuesOffset, Header.FieldDefaultValuesSize / Sizeof<Il2CppFieldDefaultValue>());
Properties = ReadVersionedObjectArray<Il2CppPropertyDefinition>(Header.PropertiesOffset, Header.PropertiesSize / Sizeof<Il2CppPropertyDefinition>());
Events = ReadVersionedObjectArray<Il2CppEventDefinition>(Header.EventsOffset, Header.EventsSize / Sizeof<Il2CppEventDefinition>());
InterfaceUsageIndices = ReadPrimitiveArray<int>(Header.InterfacesOffset, Header.InterfacesSize / sizeof(int));
NestedTypeIndices = ReadPrimitiveArray<int>(Header.NestedTypesOffset, Header.NestedTypesSize / sizeof(int));
GenericContainers = ReadVersionedObjectArray<Il2CppGenericContainer>(Header.GenericContainersOffset, Header.GenericContainersSize / Sizeof<Il2CppGenericContainer>());
GenericParameters = ReadVersionedObjectArray<Il2CppGenericParameter>(Header.GenericParametersOffset, Header.GenericParametersSize / Sizeof<Il2CppGenericParameter>());
GenericConstraintIndices = ReadPrimitiveArray<int>(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize / sizeof(int));
InterfaceOffsets = ReadVersionedObjectArray<Il2CppInterfaceOffsetPair>(Header.InterfaceOffsetsOffset, Header.InterfaceOffsetsSize / Sizeof<Il2CppInterfaceOffsetPair>());
VTableMethodIndices = ReadPrimitiveArray<uint>(Header.VTableMethodsOffset, Header.VTableMethodsSize / sizeof(uint));
Types = ReadMetadataArray<Il2CppTypeDefinition>(Header.TypeDefinitionsOffset, Header.TypeDefinitionsSize, Header.TypeDefinitions);
Methods = ReadMetadataArray<Il2CppMethodDefinition>(Header.MethodsOffset, Header.MethodsSize, Header.Methods);
Params = ReadMetadataArray<Il2CppParameterDefinition>(Header.ParametersOffset, Header.ParametersSize, Header.Parameters);
Fields = ReadMetadataArray<Il2CppFieldDefinition>(Header.FieldsOffset, Header.FieldsSize, Header.Fields);
FieldDefaultValues = ReadMetadataArray<Il2CppFieldDefaultValue>(Header.FieldDefaultValuesOffset, Header.FieldDefaultValuesSize, Header.FieldDefaultValues);
Properties = ReadMetadataArray<Il2CppPropertyDefinition>(Header.PropertiesOffset, Header.PropertiesSize, Header.Properties);
Events = ReadMetadataArray<Il2CppEventDefinition>(Header.EventsOffset, Header.EventsSize, Header.Events);
InterfaceUsageIndices = ReadMetadataPrimitiveArray<int>(Header.InterfacesOffset, Header.InterfacesSize, Header.Interfaces);
NestedTypeIndices = ReadMetadataPrimitiveArray<int>(Header.NestedTypesOffset, Header.NestedTypesSize, Header.NestedTypes);
GenericContainers = ReadMetadataArray<Il2CppGenericContainer>(Header.GenericContainersOffset, Header.GenericContainersSize, Header.GenericContainers);
GenericParameters = ReadMetadataArray<Il2CppGenericParameter>(Header.GenericParametersOffset, Header.GenericParametersSize, Header.GenericParameters);
GenericConstraintIndices = ReadMetadataPrimitiveArray<int>(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize, Header.GenericParameterConstraints);
InterfaceOffsets = ReadMetadataArray<Il2CppInterfaceOffsetPair>(Header.InterfaceOffsetsOffset, Header.InterfaceOffsetsSize, Header.InterfaceOffsets);
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
// 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
@@ -167,32 +224,39 @@ namespace Il2CppInspector
{
if (Version == MetadataVersions.V241)
changedAssemblyDefStruct = true;
Version = MetadataVersions.V244;
}
Assemblies = ReadVersionedObjectArray<Il2CppAssemblyDefinition>(Header.AssembliesOffset, Images.Length);
Assemblies = ReadMetadataArray<Il2CppAssemblyDefinition>(Header.AssembliesOffset, Header.AssembliesSize, Header.Assemblies);
if (changedAssemblyDefStruct)
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>());
MetadataUsagePairs = ReadVersionedObjectArray<Il2CppMetadataUsagePair>(Header.MetadataUsagePairsOffset, Header.MetadataUsagePairsCount / Sizeof<Il2CppMetadataUsagePair>());
if (Version >= MetadataVersions.V190 && Version < MetadataVersions.V270)
{
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));
AttributeTypeRanges = ReadVersionedObjectArray<Il2CppCustomAttributeTypeRange>(Header.AttributesInfoOffset, Header.AttributesInfoCount / Sizeof<Il2CppCustomAttributeTypeRange>());
if (Version >= MetadataVersions.V210 && Version < MetadataVersions.V290)
{
AttributeTypeIndices = ReadMetadataPrimitiveArray<int>(Header.AttributesTypesOffset, Header.AttributesTypesCount, default);
AttributeTypeRanges = ReadMetadataArray<Il2CppCustomAttributeTypeRange>(Header.AttributesInfoOffset, Header.AttributesInfoCount, default);
}
if (Version >= MetadataVersions.V290)
{
AttributeDataRanges = ReadVersionedObjectArray<Il2CppCustomAttributeDataRange>(Header.AttributeDataRangeOffset,
Header.AttributeDataRangeSize / Sizeof<Il2CppCustomAttributeDataRange>());
AttributeDataRanges = ReadMetadataArray<Il2CppCustomAttributeDataRange>(Header.AttributeDataRangeOffset,
Header.AttributeDataRangeSize, Header.AttributeDataRanges);
}
// Get all metadata strings
@@ -201,10 +265,17 @@ namespace Il2CppInspector
Strings = pluginGetStringsResult.Strings;
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)
Strings.Add((int) Position - Header.StringOffset, ReadNullTerminatedString());
Position = stringOffset;
while (Position < stringOffset + stringLength)
Strings.Add((int)Position - stringOffset, ReadNullTerminatedString());
}
// Get all string literals
@@ -212,23 +283,65 @@ namespace Il2CppInspector
if (pluginGetStringLiteralsResult.IsDataModified)
StringLiterals = pluginGetStringLiteralsResult.StringLiterals.ToArray();
else {
var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>());
else
{
var stringLiteralList = ReadMetadataArray<Il2CppStringLiteral>(Header.StringLiteralOffset,
Header.StringLiteralSize, Header.StringLiterals);
StringLiterals = new string[stringLiteralList.Length];
for (var i = 0; i < stringLiteralList.Length; i++)
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length);
var dataOffset = Version >= MetadataVersions.V380
? Header.StringLiteralData.Offset
: 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
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
public void SaveToFile(string pathname) {
Position = 0;
using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write))
CopyTo(outFile);
using var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write);
CopyTo(outFile);
}
public int Sizeof<T>() where T : IReadable => T.Size(Version, Is32Bit);

View File

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

View File

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

View File

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

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")]
public uint Token;
[FieldOffset(20 + 52)]
[VersionCondition(GreaterThan = "38.0")]
public uint ModuleToken;
[FieldOffset(8)]
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,55 +29,127 @@ public partial record struct Il2CppGlobalMetadataHeader
{
public int Sanity { get; private set; }
public int Version { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralDataOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringLiteralDataSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int StringSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int EventsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int EventsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int PropertiesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int PropertiesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int MethodsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int MethodsSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
[VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
[VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldDefaultValuesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldDefaultValuesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldAndParameterDefaultValueDataOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldAndParameterDefaultValueDataSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int FieldMarshaledSizesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int FieldMarshaledSizesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int ParametersOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int ParametersSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int FieldsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParametersOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParametersSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParameterConstraintsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericParameterConstraintsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericContainersOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int GenericContainersSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int NestedTypesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int NestedTypesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfacesOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfacesSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int VTableMethodsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int VTableMethodsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfaceOffsetsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int InterfaceOffsetsSize { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int TypeDefinitionsOffset { get; private set; }
[VersionCondition(LessThan = "35.0")]
public int TypeDefinitionsSize { get; private set; }
[VersionCondition(LessThan = "24.1")]
@@ -86,16 +158,16 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(LessThan = "24.1")]
public int RgctxEntriesCount { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int ImagesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int ImagesSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int AssembliesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
public int AssembliesSize { get; private set; }
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
@@ -110,16 +182,16 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
public int MetadataUsagePairsCount { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
[VersionCondition(GreaterThan = "19.0", LessThan = "35.0")]
public int FieldRefsOffset { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
[VersionCondition(GreaterThan = "19.0", LessThan = "35.0")]
public int FieldRefsSize { get; private set; }
[VersionCondition(GreaterThan = "20.0")]
[VersionCondition(GreaterThan = "20.0", LessThan = "35.0")]
public int ReferencedAssembliesOffset { get; private set; }
[VersionCondition(GreaterThan = "20.0")]
[VersionCondition(GreaterThan = "20.0", LessThan = "35.0")]
public int ReferencedAssembliesSize { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
@@ -134,48 +206,143 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
public int AttributesTypesCount { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
[VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataOffset { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
[VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataSize { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
[VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataRangeOffset { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
[VersionCondition(GreaterThan = "29.0", LessThan = "35.0")]
public int AttributeDataRangeSize { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
[VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterTypesOffset { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
[VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterTypesSize { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
[VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterRangesOffset { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
[VersionCondition(GreaterThan = "22.0", LessThan = "35.0")]
public int UnresolvedIndirectCallParameterRangesSize { get; private set; }
[VersionCondition(GreaterThan = "23.0")]
[VersionCondition(GreaterThan = "23.0", LessThan = "35.0")]
public int WindowsRuntimeTypeNamesOffset { get; private set; }
[VersionCondition(GreaterThan = "23.0")]
[VersionCondition(GreaterThan = "23.0", LessThan = "35.0")]
public int WindowsRuntimeTypeNamesSize { get; private set; }
[VersionCondition(GreaterThan = "27.0")]
[VersionCondition(GreaterThan = "27.0", LessThan = "35.0")]
public int WindowsRuntimeStringsOffset { get; private set; }
[VersionCondition(GreaterThan = "27.0")]
[VersionCondition(GreaterThan = "27.0", LessThan = "35.0")]
public int WindowsRuntimeStringsSize { get; private set; }
[VersionCondition(GreaterThan = "24.0")]
[VersionCondition(GreaterThan = "24.0", LessThan = "35.0")]
public int ExportedTypeDefinitionsOffset { get; private set; }
[VersionCondition(GreaterThan = "24.0")]
[VersionCondition(GreaterThan = "24.0", LessThan = "35.0")]
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 readonly bool SanityValid => Sanity == ExpectedSanity;

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,7 +2,6 @@
using VersionedSerialization.Attributes;
using StringIndex = int;
using TypeIndex = int;
[VersionedStruct]
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]
public partial record struct Il2CppStringLiteral
{
[VersionCondition(LessThan = "31.0")]
public uint Length { get; private set; }
public StringLiteralIndex DataIndex { get; private set; }
}

View File

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

View File

@@ -2,7 +2,6 @@
using VersionedSerialization.Attributes;
using StringIndex = int;
using TypeIndex = int;
[VersionedStruct]
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
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) {
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
if (prop.GetMethod?.IsStatic ?? prop.SetMethod.IsStatic)
s = PropertySig.CreateStatic(GetTypeSig(module, prop.PropertyType));
@@ -430,7 +434,7 @@ namespace Il2CppInspector.Outputs
if (method.VirtualAddress.HasValue) {
var args = new List<(string,object)> {
("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))),
("Offset", $"0x{model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start):X}"),
("VA", method.VirtualAddress.Value.Start.ToAddressString())
};
if (method.Definition.Slot != ushort.MaxValue)
@@ -470,7 +474,7 @@ namespace Il2CppInspector.Outputs
return def.AddAttribute(module, attributeAttribute,
("Name", ca.AttributeType.Name),
("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start)))
("Offset", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
);
}

View File

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

View File

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

View File

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

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

View File

@@ -83,8 +83,8 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
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
PLATFORMS = ["x86", "x64", "arm", "arm64"]
PROBLEMATIC_TYPELIBS = ["gnulnx"]
PLATFORMS = ["x86", "x64", "arm", "arm64", "win7"]
PROBLEMATIC_TYPELIBS = ["gnulnx", "mssdk64"]
for lib in PROBLEMATIC_TYPELIBS:
for platform in PLATFORMS:
@@ -106,8 +106,8 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
ida_typeinf.set_c_macros(original_macros)
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
skip_make_function = ida_segment.get_segm_by_name(".pdata") is not None
if skip_make_function:
self._skip_function_creation = ida_segment.get_segm_by_name(".pdata") is not None
if self._skip_function_creation:
print(".pdata section found, skipping function boundaries")
if FOLDERS_AVAILABLE:
@@ -118,7 +118,7 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
def on_finish(self):
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:
return
@@ -202,11 +202,12 @@ class IDADisassemblerInterface(BaseDisassemblerInterface):
return start
def write_string(self, address: int, value: str):
def write_string(self, address: int, value: str) -> int:
encoded_string = value.encode() + b'\x00'
string_length = len(encoded_string)
ida_bytes.put_bytes(address, encoded_string)
ida_bytes.create_strlit(address, string_length, ida_nalt.STRTYPE_C)
return string_length
def write_address(self, address: int, value: int):
if self._is_32_bit:

View File

@@ -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)
# Target Unity version: %TARGET_UNITY_VERSION%
import json
import os
from datetime import datetime
from typing import Union
import abc
class BaseStatusHandler(abc.ABC):
@@ -28,7 +32,7 @@ class BaseDisassemblerInterface(abc.ABC):
def on_finish(self): pass
@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
def define_data_array(self, address: int, type: str, count: int): pass
@@ -64,7 +68,7 @@ class BaseDisassemblerInterface(abc.ABC):
# only required if supports_fake_string_segment == True
def create_fake_segment(self, name: str, size: int) -> int: return 0
def write_string(self, address: int, value: str): pass
def write_string(self, address: int, value: str) -> int: pass
def write_address(self, address: int, value: int): pass
class ScriptContext:
@@ -108,7 +112,7 @@ class ScriptContext:
self._backend.set_data_name(addr, definition['name'])
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)
self._backend.set_data_type(address, type)
self._backend.set_data_name(address, name)
@@ -191,11 +195,11 @@ class ScriptContext:
self.define_string(d)
ref_addr = self.parse_address(d)
self._backend.write_string(current_string_address, d["string"])
written_string_length = self._backend.write_string(current_string_address, d["string"])
self._backend.set_data_type(ref_addr, r'const char* const')
self._backend.write_address(ref_addr, current_string_address)
current_string_address += len(d["string"]) + 1
current_string_address += written_string_length
self._status.update_progress()
else:
for d in metadata['stringLiterals']:
@@ -286,5 +290,8 @@ class ScriptContext:
end_time = datetime.now()
print(f"Took: {end_time - start_time}")
except RuntimeError: pass
finally: self._status.shutdown()
except RuntimeError:
pass
finally:
self._status.shutdown()

View File

@@ -4,6 +4,11 @@
All rights reserved.
*/
using Il2CppInspector.PluginAPI;
// This is the ONLY line to update when the API version changes
using Il2CppInspector.PluginAPI.V100;
using McMaster.NETCore.Plugins;
using Spectre.Console;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -12,11 +17,6 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using McMaster.NETCore.Plugins;
using Il2CppInspector.PluginAPI;
// This is the ONLY line to update when the API version changes
using Il2CppInspector.PluginAPI.V100;
namespace Il2CppInspector
{

View File

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

View File

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

View File

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

View File

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

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