44 Commits

Author SHA1 Message Date
LukeFZ
2603c81d03 disable aot publish and enable single file for redux cli 2025-07-30 00:01:30 +02:00
LukeFZ
4cc3862436 add workflow for new cli and add back old gui workflow 2025-07-29 23:58:42 +02:00
LukeFZ
e5dc520698 replace all Console.WriteLine calls in core inspector with AnsiConsole calls 2025-07-29 23:54:41 +02:00
LukeFZ
e3f6d8444f add redux CLI based on redux GUI output formats 2025-07-29 23:47:38 +02:00
LukeFZ
832b0020eb make inspector version a server api, split up output subtypes and tweak some option names 2025-07-29 23:46:14 +02:00
LukeFZ
6583787d8f seperate redux ui impl into FrontendCore project 2025-07-29 20:39:12 +02:00
LukeFZ
62a27ee47f show current version and hash in new ui footer 2025-07-29 18:56:49 +02:00
LukeFZ
5d92ceb2f3 change the default port for new ui dev to 5000 2025-07-29 18:18:15 +02:00
LukeFZ
67bb15d7a2 fix il2cpp_array_size_t not being an actual type for later method definitions 2025-07-25 21:38:05 +02:00
LukeFZ
9d9ca9a253 fix spelling mistakes in gui outputs 2025-07-25 21:27:47 +02:00
LukeFZ
eada036ce0 remove debug log line 2025-07-25 21:22:20 +02:00
LukeFZ
79521493da expose forward definitions in AppModel, fix issue with method-only used types not being emitted 2025-07-25 21:21:43 +02:00
LukeFZ
48473e9247 reduce clang errors for header file, fix better array size struct, emit required forward definitions in header 2025-07-25 21:21:10 +02:00
LukeFZ
6ddbf7ecae add initial support for required forward references in il2cpp types, also fix issues with type names clashing with il2cpp api types 2025-07-25 21:20:04 +02:00
LukeFZ
771eb8eb52 tweak symbol reading a bit and remove sht relocation reading 2025-07-25 17:43:29 +02:00
LukeFZ
cb6f913bce remove dependency on a section being named .text in loaded pe files 2025-07-25 17:43:06 +02:00
LukeFZ
9c76271f6c fix toAddr calls in ghidra script target 2025-07-21 17:18:36 +02:00
LukeFZ
2f13a27296 new ui: clear out loaded binary if no IL2CPP images could be loaded 2025-07-21 17:14:42 +02:00
LukeFZ
a8a757f3f5 fix new ui not allowing script exports other than ida 2025-07-21 17:14:19 +02:00
LukeFZ
893d2c1300 unify logic for getting element type index 2025-07-21 17:06:16 +02:00
LukeFZ
8055ed1cdb add support for metadata 2025-07-21 17:03:20 +02:00
LukeFZ
d70db09901 fix and enable binary ninja fake string segment support 2025-04-23 16:52:05 +02:00
LukeFZ
c12429bf97 add il2cpp file (binary, metadata) export to advanced tab 2025-04-22 14:53:08 +02:00
LukeFZ
aa53eb2dea only build tauri component in c# release builds 2025-04-22 14:52:41 +02:00
LukeFZ
74ed53b201 embed ui executable directly into c# assembly 2025-04-20 16:57:32 +02:00
LukeFZ
1163ed597c add "start export" button on format selection screen, clear all toasts after selecting an export format 2025-04-20 16:26:01 +02:00
LukeFZ
6659e0893b fix symbol table loading in some modified elfs 2025-03-11 15:12:48 +01:00
LukeFZ
404265bd1e ELF loading should not use the file offset for loading the dynamic section 2025-03-01 05:46:00 +01:00
LukeFZ
fe56b2fe2f add basic support for processing LC_DYLD_CHAINED_FIXUPS 2025-02-14 19:53:18 +01:00
LukeFZ
d87a6ef3e3 process other queued exports even if one fails and show error message 2025-02-08 17:42:10 +01:00
LukeFZ
ffb1996252 smaller tweaks, hack around loops in cpp type layouting 2025-02-08 17:40:15 +01:00
LukeFZ
e6bd289aa4 fix c# single file output due to invalid output path 2025-02-08 17:39:17 +01:00
LukeFZ
47cfb8176c update dependencies and remove cxxdemangler, as it was outdated 2025-01-25 16:59:12 +01:00
LukeFZ
6ee0813572 fix workflow errors 2025-01-25 16:26:18 +01:00
LukeFZ
ee81375265 force single file publishing and add initial gh workflow for publishing ui 2025-01-25 16:22:33 +01:00
LukeFZ
1b6cf7c776 target WinExe to hide console window in release mode, move ui exe into resources 2025-01-25 15:51:33 +01:00
LukeFZ
44af299ec3 Initial commit of new UI frontend component 2025-01-25 15:38:45 +01:00
LukeFZ
cc822b418b Initial commit of new UI c# component 2025-01-25 15:37:43 +01: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
287 changed files with 18284 additions and 5056 deletions

View File

@@ -1,57 +1,183 @@
name: Il2CppInspectorRedux Build
on:
push:
branches: [ master ]
workflow_dispatch:
on: [push, workflow_dispatch]
jobs:
build:
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@v3
- uses: actions/checkout@v4
with:
submodules: true
- uses: microsoft/setup-msbuild@v1.1
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup Node.JS
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Setup Tauri dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install frontend dependencies
run: pnpm install
working-directory: ./Il2CppInspector.Redux.GUI.UI
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-redux-gui-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-redux-gui-
- name: Restore NuGet packages
run: dotnet restore -r win-x64 ./Il2CppInspector.Redux.GUI
# note: we embed the exe directly into the c# component, and it it is built alongside it
# in another msbuild target.
- name: Build GUI
run: dotnet publish ./Il2CppInspector.Redux.GUI/Il2CppInspector.Redux.GUI.csproj -r win-x64 --no-self-contained
- name: Copy components to output directory
run: |
mkdir ./build_output
cp ./Il2CppInspector.Redux.GUI/bin/Release/net9.0/win-x64/publish/Il2CppInspector.Redux.GUI.exe ./build_output/
- name: Upload GUI Artifact
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.GUI
path: build_output
build-redux-cli:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [ '9.0.x' ]
rid: ['win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64']
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI
- name: Build & Publish
run: dotnet publish -c Release --no-self-contained --no-restore -o ./${{ matrix.rid }} -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI/Il2CppInspector.Redux.CLI.csproj
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }}
build-old-gui:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-gui-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-gui-
- name: Restore NuGet packages
run: dotnet restore -r win-x64 ./Il2CppInspector.GUI
- name: Build GUI
run: dotnet publish ./Il2CppInspector.GUI/Il2CppInspector.GUI.csproj -c Release -r win-x64 --no-self-contained
- name: Upload GUI Artifact
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.Old.GUI
path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish
build-old-cli:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [ '9.0.x' ]
rid: ['win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64']
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Restore NuGet packages
run: nuget restore
- name: Build GUI
run: msbuild /t:Il2CppInspector_GUI:publish /p:Configuration=Release /p:Platform="Any CPU" /p:TargetFramework=net8.0-windows /p:SelfContained=false /verbosity:minimal
- name: Build CLI
run: msbuild /t:Il2CppInspector_CLI:publish /p:Configuration=Release /p:Platform="Any CPU" /p:TargetFramework=net8.0 /p:SelfContained=false /verbosity:minimal
- name: Add Plugins folder (GUI)
shell: pwsh
working-directory: Il2CppInspector.GUI/bin/Release/net8.0-windows/win-x64/publish
run: ../../../../../../get-plugins.ps1
- name: Add Plugins folder (CLI)
shell: pwsh
working-directory: Il2CppInspector.CLI/bin/Release/net8.0/win-x64/publish
run: ../../../../../../get-plugins.ps1
- name: Upload GUI Artifact
uses: actions/upload-artifact@v3
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
with:
name: Il2CppInspectorRedux.GUI
path: Il2CppInspector.GUI/bin/Release/net8.0-windows/win-x64/publish
dotnet-version: ${{ matrix.dotnet-version }}
- name: Upload CLI Artifact
uses: actions/upload-artifact@v2
- name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.CLI
- name: Build & Publish
run: dotnet publish -c Release --no-self-contained --no-restore -o ./${{ matrix.rid }} -r ${{ matrix.rid }} ./Il2CppInspector.CLI/Il2CppInspector.CLI.csproj
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.CLI
path: Il2CppInspector.CLI/bin/Release/net8.0/win-x64/publish
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

View File

@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<!-- Plugins may require bass class library assemblies we're not using so disable trimming -->
@@ -32,7 +32,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.6.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
</ItemGroup>
<ItemGroup>

View File

@@ -5,6 +5,7 @@
All rights reserved.
*/
using Il2CppInspector.Next;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -342,10 +343,10 @@ namespace Il2CppInspector
// In v21 and later, R0-R2 + PC will be set and they will be the only registers set
// Pre-v21, R0-R1 + PC will be the only registers set
if (image.Version >= 21 && regs.Count == 4 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1) && regs.TryGetValue(2, out uint _))
if (image.Version >= MetadataVersions.V210 && regs.Count == 4 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1) && regs.TryGetValue(2, out uint _))
return (r0 & 0xffff_fffe, r1 & 0xffff_fffe);
if (image.Version < 21 && regs.Count == 3 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1))
if (image.Version < MetadataVersions.V210 && regs.Count == 3 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1))
return (r0 & 0xffff_fffe, r1 & 0xffff_fffe);
return (0, 0);

View File

@@ -4,6 +4,7 @@
All rights reserved.
*/
using Il2CppInspector.Next;
using System;
using System.Collections.Generic;
@@ -168,10 +169,10 @@ namespace Il2CppInspector
// Is it Il2CppCodegenRegistration(void)?
// In v21 and later, X0-X2 will be set and they will be the only registers set
// Pre-v21, X0-X1 will be the only registers set
if (image.Version >= 21 && regs.Count == 3 && regs.TryGetValue(0, out ulong x0) && regs.TryGetValue(1, out x1) && regs.TryGetValue(2, out ulong _))
if (image.Version >= MetadataVersions.V210 && regs.Count == 3 && regs.TryGetValue(0, out ulong x0) && regs.TryGetValue(1, out x1) && regs.TryGetValue(2, out ulong _))
return (x0, x1);
if (image.Version < 21 && regs.Count == 2 && regs.TryGetValue(0, out x0) && regs.TryGetValue(1, out x1))
if (image.Version < MetadataVersions.V210 && regs.Count == 2 && regs.TryGetValue(0, out x0) && regs.TryGetValue(1, out x1))
return (x0, x1);
return (0, 0);

View File

@@ -4,6 +4,7 @@
All rights reserved.
*/
using Il2CppInspector.Next;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
@@ -216,7 +217,7 @@ namespace Il2CppInspector
offset = nextLea?.foundOffset + leaSize ?? buff2Size;
}
if ((image.Version < 21 && leas.Count == 2) || (image.Version >= 21 && leas.Count == 3)) {
if ((image.Version < MetadataVersions.V210 && leas.Count == 2) || (image.Version >= MetadataVersions.V210 && leas.Count == 3)) {
// Register-based argument passing?
var leaRSI = leas.FirstOrDefault(l => l.Value == RSI).Key.address;
var leaRDI = leas.FirstOrDefault(l => l.Value == RDI).Key.address;

View File

@@ -4,6 +4,7 @@
All rights reserved.
*/
using Il2CppInspector.Next;
using System;
using System.Linq;
@@ -34,7 +35,7 @@ namespace Il2CppInspector
return (0, 0);
// Jump to Il2CppCodegenRegistration
if(image.Version < 21) {
if(image.Version < MetadataVersions.V210) {
image.Position = image.MapVATR((ulong)pCgr + 1);
metadata = image.ReadUInt32();
image.Position = image.MapVATR((ulong)pCgr + 6);

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

View File

@@ -5,12 +5,15 @@
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 VersionedSerialization;
namespace Il2CppInspector.Cpp.UnityHeaders
{
@@ -19,7 +22,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
public class UnityHeaders : IEquatable<UnityHeaders>
{
// Metadata version for which this group of headers are valid. Multiple headers may have the same metadata version
public double MetadataVersion { get; }
public StructVersion MetadataVersion { get; }
// Range of Unity versions for which this group of headers are valid
public UnityVersionRange VersionRange { get; }
@@ -114,7 +117,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
if (metadataVersion != binary.Image.Version)
continue;
if (metadataVersion == 21) {
if (metadataVersion == MetadataVersions.V210) {
/* Special version logic for metadata version 21 based on the Il2CppMetadataRegistration.fieldOffsets field */
var headerFieldOffsetsArePointers = r.VersionRange.Min.CompareTo("5.3.7") >= 0 && r.VersionRange.Min.CompareTo("5.4.0") != 0;
var binaryFieldOffsetsArePointers = binary.FieldOffsets == null;
@@ -138,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();
@@ -159,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)
@@ -168,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();
@@ -194,8 +197,8 @@ namespace Il2CppInspector.Cpp.UnityHeaders
}
// Get the metadata version from a type header resource name
private static double GetMetadataVersionFromFilename(string resourceName)
=> double.Parse(resourceName.Substring(typeof(UnityHeaders).Namespace.Length + 1).Split('-')[0], NumberFormatInfo.InvariantInfo);
private static StructVersion GetMetadataVersionFromFilename(string resourceName)
=> resourceName[(typeof(UnityHeaders).Namespace!.Length + 1)..].Split('-')[0];
// Equality comparisons
public static bool operator ==(UnityHeaders first, UnityHeaders second) {

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)});
}
}
@@ -528,11 +555,28 @@ namespace Il2CppInspector
// Note if uiAddr is a valid segment but filesz < memsz and the adjusted uiAddr falls between the range of filesz and memsz,
// an exception will be thrown. This area of memory is assumed to contain all zeroes.
public override uint MapVATR(ulong uiAddr) {
// Additions in the argument to MapVATR may cause an overflow which should be discarded for 32-bit files
if (!TryMapVATR(uiAddr, out var offset))
throw new InvalidOperationException("Failed to map virtual address");
return offset;
}
public override bool TryMapVATR(ulong uiAddr, out uint fileOffset)
{
// Additions in the argument to MapVATR may cause an overflow which should be discarded for 32-bit files
if (Bits == 32)
uiAddr &= 0xffff_ffff;
var program_header_table = this.PHT.First(x => uiAddr >= conv.ULong(x.p_vaddr) && uiAddr <= conv.ULong(conv.Add(x.p_vaddr, x.p_filesz)));
return (uint) (uiAddr - conv.ULong(conv.Sub(program_header_table.p_vaddr, program_header_table.p_offset)));
var phtEntry = PHT.FirstOrDefault(x => uiAddr >= conv.ULong(x.p_vaddr) && uiAddr <= conv.ULong(conv.Add(x.p_vaddr, x.p_filesz)));
if (phtEntry == null)
{
fileOffset = 0;
return false;
}
fileOffset = (uint)(uiAddr - conv.ULong(conv.Sub(phtEntry.p_vaddr, phtEntry.p_offset)));
return true;
}
public override ulong MapFileOffsetToVA(uint offset) {

View File

@@ -6,17 +6,20 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Il2CppInspector.Next;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization;
namespace Il2CppInspector
{
public interface IFileFormatStream
{
double Version { get; set; }
StructVersion Version { get; set; }
long Length { get; }
uint NumImages { get; }
string DefaultFilename { get; }
@@ -91,6 +94,9 @@ namespace Il2CppInspector
long[] ReadMappedWordArray(ulong uiAddr, int count);
List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new();
ulong ReadMappedUWord(ulong uiAddr);
ulong[] ReadMappedUWordArray(ulong uiAddr, int count);
void WriteEndianBytes(byte[] bytes);
void Write(long int64);
void Write(ulong uint64);
@@ -120,6 +126,30 @@ namespace Il2CppInspector
public void AddPrimitiveMapping(Type objType, Type streamType);
public void CopyTo(Stream stream);
public TType ReadMappedPrimitive<TType>(ulong addr) where TType : unmanaged;
public TType ReadPrimitive<TType>(long addr) where TType : unmanaged;
public TType ReadPrimitive<TType>() where TType : unmanaged;
public ImmutableArray<TType> ReadMappedPrimitiveArray<TType>(ulong addr, long count) where TType : unmanaged;
public ImmutableArray<TType> ReadPrimitiveArray<TType>(long addr, long count) where TType : unmanaged;
public ImmutableArray<TType> ReadPrimitiveArray<TType>(long count) where TType : unmanaged;
public TType ReadMappedVersionedObject<TType>(ulong addr) where TType : IReadable, new();
public TType ReadVersionedObject<TType>(long addr) where TType : IReadable, new();
public TType ReadVersionedObject<TType>() where TType : IReadable, new();
public ImmutableArray<TType> ReadMappedVersionedObjectArray<TType>(ulong addr, long count)
where TType : IReadable, new();
public ImmutableArray<TType> ReadVersionedObjectArray<TType>(long addr, long count)
where TType : IReadable, new();
public ImmutableArray<TType> ReadVersionedObjectArray<TType>(long count)
where TType : IReadable, new();
public ImmutableArray<TType> ReadMappedVersionedObjectPointerArray<TType>(ulong addr, int count)
where TType : IReadable, new();
}
public class FileFormatStream
@@ -146,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;
@@ -160,7 +190,7 @@ namespace Il2CppInspector
}
}
public abstract class FileFormatStream<T> : BinaryObjectStream, IFileFormatStream where T : FileFormatStream<T>
public abstract class FileFormatStream<T> : BinaryObjectStreamReader, IFileFormatStream where T : FileFormatStream<T>
{
public abstract string DefaultFilename { get; }
@@ -177,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;
@@ -254,7 +282,7 @@ namespace Il2CppInspector
public virtual uint MapVATR(ulong uiAddr) => (uint) uiAddr;
// Try to map an RVA to an offset in the file image
public bool TryMapVATR(ulong uiAddr, out uint fileOffset) {
public virtual bool TryMapVATR(ulong uiAddr, out uint fileOffset) {
try {
fileOffset = MapVATR(uiAddr);
return true;
@@ -322,5 +350,42 @@ namespace Il2CppInspector
array.Add(ReadMappedObject<U>(pointers[i]));
return array;
}
public TType ReadMappedPrimitive<TType>(ulong addr) where TType : unmanaged => ReadPrimitive<TType>(MapVATR(addr));
public ImmutableArray<TType> ReadMappedPrimitiveArray<TType>(ulong addr, long count) where TType : unmanaged
=> ReadPrimitiveArray<TType>(MapVATR(addr), count);
public TType ReadMappedVersionedObject<TType>(ulong addr) where TType : IReadable, new() => ReadVersionedObject<TType>(MapVATR(addr));
public ImmutableArray<TType> ReadMappedVersionedObjectArray<TType>(ulong addr, long count) where TType : IReadable, new()
=> ReadVersionedObjectArray<TType>(MapVATR(addr), count);
public ImmutableArray<TType> ReadMappedVersionedObjectPointerArray<TType>(ulong addr, int count)
where TType : IReadable, new()
{
var pointers = ReadMappedUWordArray(addr, count);
var builder = ImmutableArray.CreateBuilder<TType>((int)count);
for (long i = 0; i < count; i++)
builder.Add(ReadMappedVersionedObject<TType>(pointers[i]));
return builder.MoveToImmutable();
}
public ulong ReadMappedUWord(ulong uiAddr)
{
Position = MapVATR(uiAddr);
return ReadNUInt();
}
public ulong[] ReadMappedUWordArray(ulong uiAddr, int count)
{
Position = MapVATR(uiAddr);
var arr = new ulong[count];
for (int i = 0; i < count; i++)
arr[i] = ReadNUInt();
return arr;
}
}
}

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,102 +115,41 @@ 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
return true;
}
// Load DLL into memory and save it as a new PE stream
private void load() {
// Check that the process is running in the same word size as the DLL
// One way round this in future would be to spawn a new process of the correct word size
if ((Environment.Is64BitProcess && Bits == 32) || (!Environment.Is64BitProcess && Bits == 64))
throw new InvalidOperationException($"Cannot unpack a {Bits}-bit DLL from within a {(Environment.Is64BitProcess ? 64 : 32)}-bit process. Use the {Bits}-version of Il2CppInspector to unpack this DLL.");
// Get file path
// This error should never occur with the bundled CLI and GUI; only when used as a library by a 3rd party tool
if (LoadOptions == null || !(LoadOptions.BinaryFilePath is string dllPath))
throw new InvalidOperationException("To load a packed PE file, you must specify the DLL file path in LoadOptions");
// Attempt to load DLL and run startup functions
// NOTE: This can cause a CSE (AccessViolation) for certain types of protection
// so only try to unpack as the final load strategy
IntPtr hModule = LoadLibrary(dllPath);
if (hModule == IntPtr.Zero) {
var lastErrorCode = Marshal.GetLastWin32Error();
throw new InvalidOperationException($"Unable to load the DLL for unpacking: error code {lastErrorCode}");
}
// Maximum image size
var size = sections.Last().VirtualAddress + sections.Last().VirtualSize;
// Allocate memory for unpacked image
var peBytes = new byte[size];
// Copy relevant sections from unmanaged memory
foreach (var section in sections.Where(s => wantedSectionTypes.Keys.Contains(s.Characteristics)))
Marshal.Copy(IntPtr.Add(hModule, (int) section.VirtualAddress), peBytes, (int) section.VirtualAddress, (int) section.VirtualSize);
// Decrease reference count for unload
FreeLibrary(hModule);
// Rebase
pe.ImageBase = (ulong) hModule.ToInt64();
// Rewrite sections to match memory layout
foreach (var section in sections) {
section.PointerToRawData = section.VirtualAddress;
section.SizeOfRawData = section.VirtualSize;
}
// Truncate memory stream at start of COFF header
var endOfSignature = ReadUInt32(0x3C) + 4; // DOS header + 4-byte PE signature
SetLength(endOfSignature);
// Re-write the stream (the headers are only necessary in case the user wants to save)
Position = endOfSignature;
WriteObject(coff);
if (Bits == 32) WriteObject((PEOptHeader32) pe);
else WriteObject((PEOptHeader64) pe);
WriteArray(sections);
Write(peBytes, (int) Position, peBytes.Length - (int) Position);
IsModified = true;
}
// Raw file / unpacked file load strategies
public override IEnumerable<IFileFormatStream> TryNextLoadStrategy() {
// First load strategy: the regular file
yield return this;
// Second load strategy: load the DLL into memory to unpack it
if (mightBePacked) {
Console.WriteLine("IL2CPP binary appears to be packed - attempting to unpack and retrying");
StatusUpdate("Unpacking binary");
load();
yield return this;
}
}
public override uint[] GetFunctionTable() {
@@ -227,12 +167,17 @@ namespace Il2CppInspector
return addrs.ToArray();
}
public override IEnumerable<Export> GetExports() {
public override IEnumerable<Export> GetExports()
{
var exportDirectory = pe.DataDirectory[0];
if (exportDirectory.Size == 0)
return [];
// Get exports table
var ETStart = pe.DataDirectory[0].VirtualAddress + pe.ImageBase;
var exportTableStart = exportDirectory.VirtualAddress + pe.ImageBase;
// Get export RVAs
var exportDirectoryTable = ReadObject<PEExportDirectory>(MapVATR(ETStart));
var exportDirectoryTable = ReadObject<PEExportDirectory>(MapVATR(exportTableStart));
var exportCount = (int) exportDirectoryTable.NumberOfFunctions;
var exportAddresses = ReadArray<uint>(MapVATR(exportDirectoryTable.AddressOfFunctions + pe.ImageBase), exportCount);
var exports = exportAddresses.Select((a, i) => new Export {

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

@@ -38,7 +38,7 @@ namespace Il2CppInspector
Position = arch.Offset;
Endianness = Endianness.Little;
using var s = new BinaryObjectStream(ReadBytes((int) arch.Size));
using var s = new BinaryObjectStream(ReadBytes((int) arch.Size).ToArray());
return (IFileFormatStream) MachOReader32.Load(s, LoadOptions, OnStatusUpdate) ?? MachOReader64.Load(s, LoadOptions, OnStatusUpdate);
}
}

View File

@@ -4,9 +4,11 @@ using System.Diagnostics;
using System.IO;
using System.Linq;
using dnlib.DotNet;
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Il2CppInspector.Reflection;
using Il2CppInspector.Utils;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
@@ -14,7 +16,7 @@ namespace Il2CppInspector
{
private readonly Il2CppInspector _inspector;
private readonly Assembly _assembly;
private readonly BinaryObjectStream _data;
private readonly BinaryObjectStreamReader _data;
private readonly uint _start;
private readonly uint _end;
@@ -24,7 +26,7 @@ namespace Il2CppInspector
public uint Count { get; }
public CustomAttributeDataReader(Il2CppInspector inspector, Assembly assembly, BinaryObjectStream data, uint startOffset, uint endOffset)
public CustomAttributeDataReader(Il2CppInspector inspector, Assembly assembly, BinaryObjectStreamReader data, uint startOffset, uint endOffset)
{
_inspector = inspector;
_assembly = assembly;
@@ -143,9 +145,9 @@ namespace Il2CppInspector
}
private TypeInfo ConvertTypeDef(Il2CppTypeDefinition typeDef, Il2CppTypeEnum type)
=> typeDef == null
? _assembly.Model.GetTypeDefinitionFromTypeEnum(type)
: _assembly.Model.TypesByDefinitionIndex[Array.IndexOf(_inspector.TypeDefinitions, typeDef)];
=> typeDef.IsValid
? _assembly.Model.TypesByDefinitionIndex[_inspector.TypeDefinitions.IndexOf(typeDef)]
: _assembly.Model.GetTypeDefinitionFromTypeEnum(type);
private (TypeInfo, int) ReadCustomAttributeNamedArgumentClassAndIndex(TypeInfo attrInfo)
{

View File

@@ -5,13 +5,15 @@
All rights reserved.
*/
using System;
using System.Collections.Generic;
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Spectre.Console;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using VersionedSerialization;
namespace Il2CppInspector
{
@@ -34,16 +36,16 @@ namespace Il2CppInspector
public ulong CodeRegistrationPointer { get; private set; }
public ulong MetadataRegistrationPointer { get; private set; }
public ulong RegistrationFunctionPointer { get; private set; }
public Dictionary<string, ulong> CodeGenModulePointers { get; } = new Dictionary<string, ulong>();
public Dictionary<string, ulong> CodeGenModulePointers { get; } = new();
// Only for <=v24.1
public ulong[] GlobalMethodPointers { get; set; }
// Only for >=v24.2
public Dictionary<Il2CppCodeGenModule, ulong[]> ModuleMethodPointers { get; set; } = new Dictionary<Il2CppCodeGenModule, ulong[]>();
public Dictionary<Il2CppCodeGenModule, ulong[]> ModuleMethodPointers { get; set; } = new();
// Only for >=v24.2. In earlier versions, invoker indices are stored in Il2CppMethodDefinition in the metadata file
public Dictionary<Il2CppCodeGenModule, int[]> MethodInvokerIndices { get; set; } = new Dictionary<Il2CppCodeGenModule, int[]>();
public Dictionary<Il2CppCodeGenModule, ImmutableArray<int>> MethodInvokerIndices { get; set; } = new();
// NOTE: In versions <21 and earlier releases of v21, use FieldOffsets:
// global field index => field offset
@@ -51,7 +53,7 @@ namespace Il2CppInspector
// type index => RVA in image where the list of field offsets for the type start (4 bytes per field)
// Negative field offsets from start of each function
public uint[] FieldOffsets { get; private set; }
public ImmutableArray<uint> FieldOffsets { get; private set; }
// Pointers to field offsets
public long[] FieldOffsetPointers { get; private set; }
@@ -65,13 +67,13 @@ namespace Il2CppInspector
public ulong[] MethodInvokePointers { get; private set; }
// Version 16 and below: method references for vtable
public uint[] VTableMethodReferences { get; private set; }
public ImmutableArray<uint> VTableMethodReferences { get; private set; }
// Generic method specs for vtables
public Il2CppMethodSpec[] MethodSpecs { get; private set; }
public ImmutableArray<Il2CppMethodSpec> MethodSpecs { get; private set; }
// List of run-time concrete generic class and method signatures
public List<Il2CppGenericInst> GenericInstances { get; private set; }
public ImmutableArray<Il2CppGenericInst> GenericInstances { get; private set; }
// List of constructed generic method function pointers corresponding to each possible method instantiation
public Dictionary<Il2CppMethodSpec, ulong> GenericMethodPointers { get; } = new Dictionary<Il2CppMethodSpec, ulong>();
@@ -80,7 +82,7 @@ namespace Il2CppInspector
public Dictionary<Il2CppMethodSpec, int> GenericMethodInvokerIndices { get; } = new Dictionary<Il2CppMethodSpec, int>();
// Every type reference (TypeRef) sorted by index
public List<Il2CppType> TypeReferences { get; private set; }
public ImmutableArray<Il2CppType> TypeReferences { get; private set; }
// Every type reference index sorted by virtual address
public Dictionary<ulong, int> TypeReferenceIndicesByAddress { get; private set; }
@@ -89,7 +91,7 @@ namespace Il2CppInspector
// One assembly may contain multiple modules
public Dictionary<string, Il2CppCodeGenModule> Modules { get; private set; }
public List<Il2CppTypeDefinitionSizes> TypeDefinitionSizes { get; private set; }
public ImmutableArray<Il2CppTypeDefinitionSizes> TypeDefinitionSizes { get; private set; }
// Status update callback
private EventHandler<string> OnStatusUpdate { get; set; }
@@ -135,7 +137,7 @@ namespace Il2CppInspector
}
// Load binary without a global-metadata.dat available
public static Il2CppBinary Load(IFileFormatStream stream, double metadataVersion, EventHandler<string> statusCallback = null) {
public static Il2CppBinary Load(IFileFormatStream stream, StructVersion metadataVersion, EventHandler<string> statusCallback = null) {
foreach (var loadedImage in stream.TryNextLoadStrategy()) {
var inst = LoadImpl(stream, statusCallback);
if (inst.FindRegistrationStructs(metadataVersion))
@@ -167,7 +169,7 @@ namespace Il2CppInspector
}
// Initialize binary without a global-metadata.dat available
public bool FindRegistrationStructs(double metadataVersion) {
public bool FindRegistrationStructs(StructVersion metadataVersion) {
Image.Version = metadataVersion;
StatusUpdate("Searching for binary metadata");
@@ -190,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);
@@ -201,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");
}
@@ -226,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;
}
@@ -243,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);
}
@@ -273,33 +275,12 @@ 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.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
MetadataRegistration = Image.ReadMappedObject<Il2CppMetadataRegistration>(metadataRegistration);
// genericAdjustorThunks was inserted before invokerPointersCount in 24.5 and 27.1
// pointer expected if we need to bump version
if (Image.Version == 24.4 && CodeRegistration.invokerPointersCount > 0x50000)
{
Image.Version = 24.5;
CodeRegistration = Image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
}
if (Image.Version == 24.4 && CodeRegistration.reversePInvokeWrapperCount > 0x50000) {
Image.Version = 24.5;
codeRegistration -= 1 * pointerSize;
CodeRegistration = Image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
}
if (Image.Version is 29 or 31 && (long)CodeRegistration.genericMethodPointersCount - MetadataRegistration.genericMethodTableCount > 0x10000)
{
Image.Version += 0.1;
codeRegistration -= 2 * pointerSize;
CodeRegistration = Image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
}
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
MetadataRegistration = Image.ReadMappedVersionedObject<Il2CppMetadataRegistration>(metadataRegistration);
// Plugin hook to pre-process binary
isModified |= PluginHooks.PreProcessBinary(this).IsStreamModified;
@@ -313,36 +294,36 @@ namespace Il2CppInspector
* typeRefPointers must be a series of pointers in __const
* MethodInvokePointers must be a series of pointers in __text or .text, and in sequential order
*/
if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.typeDefinitionsSizesCount)
|| CodeRegistration.reversePInvokeWrapperCount > 0x10000
|| CodeRegistration.unresolvedVirtualCallCount > 0x4000 // >= 22
|| CodeRegistration.interopDataCount > 0x1000 // >= 23
|| (Image.Version <= 24.1 && CodeRegistration.invokerPointersCount > CodeRegistration.methodPointersCount))
if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.TypeDefinitionsSizesCount)
|| CodeRegistration.ReversePInvokeWrapperCount > 0x10000
|| CodeRegistration.UnresolvedVirtualCallCount > 0x4000 // >= 22
|| CodeRegistration.InteropDataCount > 0x1000 // >= 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.");
// The global method pointer list was deprecated in v24.2 in favour of Il2CppCodeGenModule
if (Image.Version <= 24.1)
GlobalMethodPointers = Image.ReadMappedArray<ulong>(CodeRegistration.pmethodPointers, (int) CodeRegistration.methodPointersCount);
if (Image.Version <= MetadataVersions.V241)
GlobalMethodPointers = Image.ReadMappedUWordArray(CodeRegistration.MethodPointers, (int) CodeRegistration.MethodPointersCount);
// After v24 method pointers and RGCTX data were stored in Il2CppCodeGenModules
if (Image.Version >= 24.2) {
if (Image.Version >= MetadataVersions.V242) {
Modules = new Dictionary<string, Il2CppCodeGenModule>();
// In v24.3, windowsRuntimeFactoryTable collides with codeGenModules. So far no samples have had windowsRuntimeFactoryCount > 0;
// if this changes we'll have to get smarter about disambiguating these two.
if (CodeRegistration.codeGenModulesCount == 0) {
Image.Version = 24.3;
CodeRegistration = Image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
if (CodeRegistration.CodeGenModulesCount == 0) {
Image.Version = MetadataVersions.V243;
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
}
// Array of pointers to Il2CppCodeGenModule
var codeGenModulePointers = Image.ReadMappedArray<ulong>(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount);
var modules = Image.ReadMappedObjectPointerArray<Il2CppCodeGenModule>(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount);
var codeGenModulePointers = Image.ReadMappedUWordArray(CodeRegistration.CodeGenModules, (int) CodeRegistration.CodeGenModulesCount);
var modules = Image.ReadMappedVersionedObjectPointerArray<Il2CppCodeGenModule>(CodeRegistration.CodeGenModules, (int) CodeRegistration.CodeGenModulesCount);
foreach (var mp in modules.Zip(codeGenModulePointers, (m, p) => new { Module = m, Pointer = p })) {
var module = mp.Module;
var name = Image.ReadMappedNullTerminatedString(module.moduleName);
var name = Image.ReadMappedNullTerminatedString(module.ModuleName);
Modules.Add(name, module);
CodeGenModulePointers.Add(name, mp.Pointer);
@@ -351,24 +332,32 @@ namespace Il2CppInspector
// the entire method pointer array will be NULL values, causing the methodPointer to be mapped to .bss
// and therefore out of scope of the binary image
try {
ModuleMethodPointers.Add(module, Image.ReadMappedArray<ulong>(module.methodPointers, (int) module.methodPointerCount));
ModuleMethodPointers.Add(module, Image.ReadMappedUWordArray(module.MethodPointers, (int) module.MethodPointerCount));
} catch (InvalidOperationException) {
ModuleMethodPointers.Add(module, new ulong[module.methodPointerCount]);
ModuleMethodPointers.Add(module, new ulong[module.MethodPointerCount]);
}
// Read method invoker pointer indices - one per method
MethodInvokerIndices.Add(module, Image.ReadMappedArray<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]]);
}
}
}
// Field offset data. Metadata <=21.x uses a value-type array; >=21.x uses a pointer array
// Versions from 22 onwards use an array of pointers in Binary.FieldOffsetData
bool fieldOffsetsArePointers = (Image.Version >= 22);
bool fieldOffsetsArePointers = (Image.Version >= MetadataVersions.V220);
// Some variants of 21 also use an array of pointers
if (Image.Version == 21) {
var fieldTest = Image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, 6);
if (Image.Version == MetadataVersions.V210) {
var fieldTest = Image.ReadMappedWordArray(MetadataRegistration.FieldOffsets, 6);
// We detect this by relying on the fact Module, Object, ValueType, Attribute, _Attribute and Int32
// are always the first six defined types, and that all but Int32 have no fields
@@ -377,29 +366,75 @@ namespace Il2CppInspector
// All older versions use values directly in the array
if (!fieldOffsetsArePointers)
FieldOffsets = Image.ReadMappedArray<uint>(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount);
FieldOffsets = Image.ReadMappedPrimitiveArray<uint>(MetadataRegistration.FieldOffsets, (int)MetadataRegistration.FieldOffsetsCount);
else
FieldOffsetPointers = Image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount);
FieldOffsetPointers = Image.ReadMappedWordArray(MetadataRegistration.FieldOffsets, (int)MetadataRegistration.FieldOffsetsCount);
// Type references (pointer array)
var typeRefPointers = Image.ReadMappedArray<ulong>(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount);
var typeRefPointers = Image.ReadMappedUWordArray(MetadataRegistration.Types, (int) MetadataRegistration.TypesCount);
TypeReferenceIndicesByAddress = typeRefPointers.Zip(Enumerable.Range(0, typeRefPointers.Length), (a, i) => new { a, i }).ToDictionary(x => x.a, x => x.i);
TypeReferences =
Image.Version >= 27.2
? Image.ReadMappedObjectPointerArray<Il2CppTypeV272>(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount)
.Cast<Il2CppType>()
.ToList()
: Image.ReadMappedObjectPointerArray<Il2CppType>(MetadataRegistration.ptypes, (int)MetadataRegistration.typesCount);
TypeReferences = Image.ReadMappedVersionedObjectPointerArray<Il2CppType>(MetadataRegistration.Types, (int)MetadataRegistration.TypesCount);
if (TypeReferences.Any(x =>
x.Type.IsTypeDefinitionEnum()
&& (uint)x.Data.KlassIndex >= (uint)Metadata.Types.Length))
{
// This is a memory-dumped binary.
// We need to fix the remapped type indices from their pointer form back to the indices.
var baseDefinitionPtr = ulong.MaxValue;
var baseGenericPtr = ulong.MaxValue;
foreach (var entry in TypeReferences)
{
if (entry.Type.IsTypeDefinitionEnum())
{
baseDefinitionPtr = Math.Min(baseDefinitionPtr, entry.Data.Type.PointerValue);
}
else if (entry.Type.IsGenericParameterEnum())
{
baseGenericPtr = Math.Min(baseGenericPtr, entry.Data.GenericParameterHandle.PointerValue);
}
}
var definitionSize = (ulong)Il2CppTypeDefinition.Size(Image.Version);
var genericParameterSize = (ulong)Il2CppGenericParameter.Size(Image.Version);
var builder = ImmutableArray.CreateBuilder<Il2CppType>(TypeReferences.Length);
for (var i = 0; i < TypeReferences.Length; i++)
{
var type = TypeReferences[i];
if (type.Type.IsTypeDefinitionEnum())
{
if (type.Data.Type.PointerValue >= baseDefinitionPtr)
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize;
Debug.Assert(Metadata!.Types.Length > type.Data.KlassIndex);
}
else if (type.Type.IsGenericParameterEnum())
{
if (type.Data.Type.PointerValue >= baseGenericPtr)
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize;
Debug.Assert(Metadata!.GenericParameters.Length > type.Data.KlassIndex);
}
Debug.Assert((long)type.Data.Value >= 0);
builder.Add(type);
}
TypeReferences = builder.MoveToImmutable();
}
// Custom attribute constructors (function pointers)
// This is managed in Il2CppInspector for metadata >= 27
if (Image.Version < 27) {
CustomAttributeGenerators = Image.ReadMappedArray<ulong>(CodeRegistration.customAttributeGenerators, (int) CodeRegistration.customAttributeCount);
if (Image.Version < MetadataVersions.V270) {
CustomAttributeGenerators = Image.ReadMappedUWordArray(CodeRegistration.CustomAttributeGenerators, (int) CodeRegistration.CustomAttributeCount);
}
// Method.Invoke function pointers
MethodInvokePointers = Image.ReadMappedArray<ulong>(CodeRegistration.invokerPointers, (int) CodeRegistration.invokerPointersCount);
MethodInvokePointers = Image.ReadMappedUWordArray(CodeRegistration.InvokerPointers, (int) CodeRegistration.InvokerPointersCount);
// TODO: Function pointers as shown below
// reversePInvokeWrappers
@@ -408,26 +443,26 @@ namespace Il2CppInspector
// >=22: unresolvedVirtualCallPointers
// >=23: interopData
if (Image.Version < 19) {
VTableMethodReferences = Image.ReadMappedArray<uint>(MetadataRegistration.methodReferences, (int)MetadataRegistration.methodReferencesCount);
if (Image.Version < MetadataVersions.V190) {
VTableMethodReferences = Image.ReadMappedPrimitiveArray<uint>(MetadataRegistration.MethodReferences, (int)MetadataRegistration.MethodReferencesCount);
}
// Generic type and method specs (open and closed constructed types)
MethodSpecs = Image.ReadMappedArray<Il2CppMethodSpec>(MetadataRegistration.methodSpecs, (int) MetadataRegistration.methodSpecsCount);
MethodSpecs = Image.ReadMappedVersionedObjectArray<Il2CppMethodSpec>(MetadataRegistration.MethodSpecs, (int) MetadataRegistration.MethodSpecsCount);
// Concrete generic class and method signatures
GenericInstances = Image.ReadMappedObjectPointerArray<Il2CppGenericInst>(MetadataRegistration.genericInsts, (int) MetadataRegistration.genericInstsCount);
GenericInstances = Image.ReadMappedVersionedObjectPointerArray<Il2CppGenericInst>(MetadataRegistration.GenericInsts, (int) MetadataRegistration.GenericInstsCount);
// Concrete generic method pointers
var genericMethodPointers = Image.ReadMappedArray<ulong>(CodeRegistration.genericMethodPointers, (int) CodeRegistration.genericMethodPointersCount);
var genericMethodTable = Image.ReadMappedArray<Il2CppGenericMethodFunctionsDefinitions>(MetadataRegistration.genericMethodTable, (int) MetadataRegistration.genericMethodTableCount);
var genericMethodPointers = Image.ReadMappedUWordArray(CodeRegistration.GenericMethodPointers, (int) CodeRegistration.GenericMethodPointersCount);
var genericMethodTable = Image.ReadMappedVersionedObjectArray<Il2CppGenericMethodFunctionsDefinitions>(MetadataRegistration.GenericMethodTable, (int) MetadataRegistration.GenericMethodTableCount);
foreach (var tableEntry in genericMethodTable) {
GenericMethodPointers.Add(MethodSpecs[tableEntry.genericMethodIndex], genericMethodPointers[tableEntry.indices.methodIndex]);
GenericMethodInvokerIndices.Add(MethodSpecs[tableEntry.genericMethodIndex], tableEntry.indices.invokerIndex);
GenericMethodPointers.Add(MethodSpecs[tableEntry.GenericMethodIndex], genericMethodPointers[tableEntry.Indices.MethodIndex]);
GenericMethodInvokerIndices.Add(MethodSpecs[tableEntry.GenericMethodIndex], tableEntry.Indices.InvokerIndex);
}
TypeDefinitionSizes = Image.ReadMappedObjectPointerArray<Il2CppTypeDefinitionSizes>(
MetadataRegistration.typeDefinitionsSizes, (int) MetadataRegistration.typeDefinitionsSizesCount);
TypeDefinitionSizes = Image.ReadMappedVersionedObjectPointerArray<Il2CppTypeDefinitionSizes>(
MetadataRegistration.TypeDefinitionsSizes, (int) MetadataRegistration.TypeDefinitionsSizesCount);
// Plugin hook to pre-process binary
isModified |= PluginHooks.PostProcessBinary(this).IsStreamModified;

View File

@@ -1,306 +0,0 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
// From class-internals.h / il2cpp-class-internals.h
public class Il2CppCodeRegistration
{
// Moved to Il2CppCodeGenModule in v24.2
[Version(Max = 24.1)]
public ulong methodPointersCount;
[Version(Max = 24.1)]
public ulong pmethodPointers;
public ulong reversePInvokeWrapperCount; // (was renamed from delegateWrappersFromNativeToManagedCount in v22)
public ulong reversePInvokeWrappers; // (was renamed from delegateWrappersFromNativeToManaged in v22)
// Removed in metadata v23
[Version(Max = 22)]
public ulong delegateWrappersFromManagedToNativeCount;
[Version(Max = 22)]
public ulong delegateWrappersFromManagedToNative;
[Version(Max = 22)]
public ulong marshalingFunctionsCount;
[Version(Max = 22)]
public ulong marshalingFunctions;
[Version(Min = 21, Max = 22)]
public ulong ccwMarshalingFunctionsCount;
[Version(Min = 21, Max = 22)]
public ulong ccwMarshalingFunctions;
public ulong genericMethodPointersCount;
public ulong genericMethodPointers;
[Version(Min = 24.5, Max = 24.5)]
[Version(Min = 27.1)]
public ulong genericAdjustorThunks;
public ulong invokerPointersCount;
public ulong invokerPointers;
// Removed in metadata v27
[Version(Max = 24.5)]
public long customAttributeCount;
[Version(Max = 24.5)]
public ulong customAttributeGenerators;
// Removed in metadata v23
[Version(Min = 21, Max = 22)]
public long guidCount;
[Version(Min = 21, Max = 22)]
public ulong guids; // Il2CppGuid
// Added in metadata v22
[Version(Min = 22, Max = 29)]
public ulong unresolvedVirtualCallCount;
[Version(Min = 29.1, Max = 29.1)]
[Version(Min = 31.1, Max = 31.1)]
public ulong unresolvedIndirectCallCount;
[Version(Min = 22)]
public ulong unresolvedVirtualCallPointers;
[Version(Min = 29.1, Max = 29.1)]
[Version(Min = 31.1, Max = 31.1)]
public ulong unresolvedInstanceCallPointers;
[Version(Min = 29.1, Max = 29.1)]
[Version(Min = 31.1, Max = 31.1)]
public ulong unresolvedStaticCallPointers;
// Added in metadata v23
[Version(Min = 23)]
public ulong interopDataCount;
[Version(Min = 23)]
public ulong interopData;
[Version(Min = 24.3)]
public ulong windowsRuntimeFactoryCount;
[Version(Min = 24.3)]
public ulong windowsRuntimeFactoryTable;
// Added in metadata v24.2 to replace methodPointers and methodPointersCount
[Version(Min = 24.2)]
public ulong codeGenModulesCount;
[Version(Min = 24.2)]
public ulong pcodeGenModules;
}
// Introduced in metadata v24.2 (replaces method pointers in Il2CppCodeRegistration)
public class Il2CppCodeGenModule
{
public ulong moduleName;
public ulong methodPointerCount;
public ulong methodPointers;
[Version(Min = 24.5, Max = 24.5)]
[Version(Min = 27.1)]
public long adjustorThunkCount;
[Version(Min = 24.5, Max = 24.5)]
[Version(Min = 27.1)]
public ulong adjustorThunks; //Pointer
public ulong invokerIndices;
public ulong reversePInvokeWrapperCount;
public ulong reversePInvokeWrapperIndices;
public ulong rgctxRangesCount;
public ulong rgctxRanges;
public ulong rgctxsCount;
public ulong rgctxs;
public ulong debuggerMetadata;
// Added in metadata v27
[Version(Min = 27, Max = 27.2)]
public ulong customAttributeCacheGenerator; // CustomAttributesCacheGenerator*
[Version(Min = 27)]
public ulong moduleInitializer; // Il2CppMethodPointer
[Version(Min = 27)]
public ulong staticConstructorTypeIndices; // TypeDefinitionIndex*
[Version(Min = 27)]
public ulong metadataRegistration; // Il2CppMetadataRegistration* // Per-assembly mode only
[Version(Min = 27)]
public ulong codeRegistration; // Il2CppCodeRegistration* // Per-assembly mode only
}
#pragma warning disable CS0649
public class Il2CppMetadataRegistration
{
public long genericClassesCount;
public ulong genericClasses;
public long genericInstsCount;
public ulong genericInsts;
public long genericMethodTableCount;
public ulong genericMethodTable; // Il2CppGenericMethodFunctionsDefinitions
public long typesCount;
public ulong ptypes;
public long methodSpecsCount;
public ulong methodSpecs;
[Version(Max = 16)]
public long methodReferencesCount;
[Version(Max = 16)]
public ulong methodReferences;
public long fieldOffsetsCount;
public ulong pfieldOffsets; // Changed from int32_t* to int32_t** after 5.4.0f3, before 5.5.0f3
public long typeDefinitionsSizesCount;
public ulong typeDefinitionsSizes;
[Version(Min = 19)]
public ulong metadataUsagesCount;
[Version(Min = 19)]
public ulong metadataUsages;
}
#pragma warning restore CS0649
// From blob.h / il2cpp-blob.h
public enum Il2CppTypeEnum
{
IL2CPP_TYPE_END = 0x00, /* End of List */
IL2CPP_TYPE_VOID = 0x01,
IL2CPP_TYPE_BOOLEAN = 0x02,
IL2CPP_TYPE_CHAR = 0x03,
IL2CPP_TYPE_I1 = 0x04,
IL2CPP_TYPE_U1 = 0x05,
IL2CPP_TYPE_I2 = 0x06,
IL2CPP_TYPE_U2 = 0x07,
IL2CPP_TYPE_I4 = 0x08,
IL2CPP_TYPE_U4 = 0x09,
IL2CPP_TYPE_I8 = 0x0a,
IL2CPP_TYPE_U8 = 0x0b,
IL2CPP_TYPE_R4 = 0x0c,
IL2CPP_TYPE_R8 = 0x0d,
IL2CPP_TYPE_STRING = 0x0e,
IL2CPP_TYPE_PTR = 0x0f, /* arg: <type> token */
IL2CPP_TYPE_BYREF = 0x10, /* arg: <type> token */
IL2CPP_TYPE_VALUETYPE = 0x11, /* arg: <type> token */
IL2CPP_TYPE_CLASS = 0x12, /* arg: <type> token */
IL2CPP_TYPE_VAR = 0x13, /* Generic parameter in a generic type definition, represented as number (compressed unsigned integer) number */
IL2CPP_TYPE_ARRAY = 0x14, /* type, rank, boundsCount, bound1, loCount, lo1 */
IL2CPP_TYPE_GENERICINST = 0x15, /* <type> <type-arg-count> <type-1> \x{2026} <type-n> */
IL2CPP_TYPE_TYPEDBYREF = 0x16,
IL2CPP_TYPE_I = 0x18,
IL2CPP_TYPE_U = 0x19,
IL2CPP_TYPE_FNPTR = 0x1b, /* arg: full method signature */
IL2CPP_TYPE_OBJECT = 0x1c,
IL2CPP_TYPE_SZARRAY = 0x1d, /* 0-based one-dim-array */
IL2CPP_TYPE_MVAR = 0x1e, /* Generic parameter in a generic method definition, represented as number (compressed unsigned integer) */
IL2CPP_TYPE_CMOD_REQD = 0x1f, /* arg: typedef or typeref token */
IL2CPP_TYPE_CMOD_OPT = 0x20, /* optional arg: typedef or typref token */
IL2CPP_TYPE_INTERNAL = 0x21, /* CLR internal type */
IL2CPP_TYPE_MODIFIER = 0x40, /* Or with the following types */
IL2CPP_TYPE_SENTINEL = 0x41, /* Sentinel for varargs method signature */
IL2CPP_TYPE_PINNED = 0x45, /* Local var that points to pinned object */
IL2CPP_TYPE_ENUM = 0x55, /* an enumeration */
IL2CPP_TYPE_IL2CPP_TYPE_INDEX = 0xff /* Type index metadata table */
}
// From metadata.h / il2cpp-runtime-metadata.h
public class Il2CppType
{
public ulong datapoint;
public ulong bits; // this should be private but we need it to be public for BinaryObjectReader to work
//public Union data { get; set; }
public virtual uint attrs => (uint) bits & 0xffff;
public virtual Il2CppTypeEnum type => (Il2CppTypeEnum)((bits >> 16) & 0xff);
public virtual uint num_mods => (uint) (bits >> 24) & 0x3f;
public virtual bool byref => ((bits >> 30) & 1) == 1;
public virtual bool pinned => ((bits >> 31) & 1) == 1;
public virtual bool valuetype => false;
/*
union
{
TypeDefinitionIndex klassIndex; // for VALUETYPE and CLASS (<v27; v27: at startup)
Il2CppMetadataTypeHandle typeHandle; // for VALUETYPE and CLASS (added in v27: at runtime)
const Il2CppType* type; // for PTR and SZARRAY
Il2CppArrayType* array; // for ARRAY
GenericParameterIndex genericParameterIndex; // for VAR and MVAR (<v27; v27: at startup)
Il2CppMetadataGenericParameterHandle genericParameterHandle; // for VAR and MVAR (added in v27: at runtime)
Il2CppGenericClass* generic_class; // for GENERICINST
}
*/
}
// Unity 2021.1 (v27.2): num_mods becomes 1 bit shorter, shifting byref and pinned left 1 bit, valuetype bit added
public class Il2CppTypeV272 : Il2CppType
{
public override uint num_mods => (uint) (bits >> 24) & 0x1f;
public override bool byref => ((bits >> 29) & 1) == 1;
public override bool pinned => ((bits >> 30) & 1) == 1;
public override bool valuetype => ((bits >> 31) & 1) == 1;
}
public class Il2CppGenericClass
{
[Version(Max = 24.5)]
public long typeDefinitionIndex; /* the generic type definition */
[Version(Min = 27)]
public ulong type; // Il2CppType* /* the generic type definition */
public Il2CppGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */
public ulong cached_class; /* if present, the Il2CppClass corresponding to the instantiation. */
}
public class Il2CppGenericContext
{
/* The instantiation corresponding to the class generic parameters */
public ulong class_inst;
/* The instantiation corresponding to the method generic parameters */
public ulong method_inst;
}
public class Il2CppGenericInst
{
public ulong type_argc;
public ulong type_argv;
}
public class Il2CppArrayType
{
public ulong etype;
public byte rank;
public byte numsizes;
public byte numlobounds;
public ulong sizes;
public ulong lobounds;
}
public class Il2CppMethodSpec
{
public int methodDefinitionIndex;
public int classIndexIndex;
public int methodIndexIndex;
}
public class Il2CppGenericMethodFunctionsDefinitions
{
public int genericMethodIndex;
public Il2CppGenericMethodIndices indices;
}
public class Il2CppGenericMethodIndices
{
public int methodIndex;
public int invokerIndex;
[Version(Min = 24.5, Max = 24.5)]
[Version(Min = 27.1)]
public int adjustorThunk;
}
public class Il2CppTypeDefinitionSizes
{
public uint instanceSize;
public int nativeSize;
public uint staticFieldsSize;
public uint threadStaticFieldsSize;
}
}

View File

@@ -4,14 +4,20 @@
All rights reserved.
*/
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;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using VersionedSerialization;
namespace Il2CppInspector
{
@@ -31,42 +37,44 @@ namespace Il2CppInspector
public List<MetadataUsage> MetadataUsages { get; }
// Shortcuts
public double Version => Math.Max(Metadata.Version, Binary.Image.Version);
public StructVersion Version => Metadata.Version > Binary.Image.Version
? Metadata.Version
: Binary.Image.Version;
public Dictionary<int, string> Strings => Metadata.Strings;
public string[] StringLiterals => Metadata.StringLiterals;
public Il2CppTypeDefinition[] TypeDefinitions => Metadata.Types;
public Il2CppAssemblyDefinition[] Assemblies => Metadata.Assemblies;
public Il2CppImageDefinition[] Images => Metadata.Images;
public Il2CppMethodDefinition[] Methods => Metadata.Methods;
public Il2CppParameterDefinition[] Params => Metadata.Params;
public Il2CppFieldDefinition[] Fields => Metadata.Fields;
public Il2CppPropertyDefinition[] Properties => Metadata.Properties;
public Il2CppEventDefinition[] Events => Metadata.Events;
public Il2CppGenericContainer[] GenericContainers => Metadata.GenericContainers;
public Il2CppGenericParameter[] GenericParameters => Metadata.GenericParameters;
public int[] GenericConstraintIndices => Metadata.GenericConstraintIndices;
public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges => Metadata.AttributeTypeRanges;
public Il2CppCustomAttributeDataRange[] AttributeDataRanges => Metadata.AttributeDataRanges;
public Il2CppInterfaceOffsetPair[] InterfaceOffsets => Metadata.InterfaceOffsets;
public int[] InterfaceUsageIndices => Metadata.InterfaceUsageIndices;
public int[] NestedTypeIndices => Metadata.NestedTypeIndices;
public int[] AttributeTypeIndices => Metadata.AttributeTypeIndices;
public uint[] VTableMethodIndices => Metadata.VTableMethodIndices;
public Il2CppFieldRef[] FieldRefs => Metadata.FieldRefs;
public ImmutableArray<Il2CppTypeDefinition> TypeDefinitions => Metadata.Types;
public ImmutableArray<Il2CppAssemblyDefinition> Assemblies => Metadata.Assemblies;
public ImmutableArray<Il2CppImageDefinition> Images => Metadata.Images;
public ImmutableArray<Il2CppMethodDefinition> Methods => Metadata.Methods;
public ImmutableArray<Il2CppParameterDefinition> Params => Metadata.Params;
public ImmutableArray<Il2CppFieldDefinition> Fields => Metadata.Fields;
public ImmutableArray<Il2CppPropertyDefinition> Properties => Metadata.Properties;
public ImmutableArray<Il2CppEventDefinition> Events => Metadata.Events;
public ImmutableArray<Il2CppGenericContainer> GenericContainers => Metadata.GenericContainers;
public ImmutableArray<Il2CppGenericParameter> GenericParameters => Metadata.GenericParameters;
public ImmutableArray<int> GenericConstraintIndices => Metadata.GenericConstraintIndices;
public ImmutableArray<Il2CppCustomAttributeTypeRange> AttributeTypeRanges => Metadata.AttributeTypeRanges;
public ImmutableArray<Il2CppCustomAttributeDataRange> AttributeDataRanges => Metadata.AttributeDataRanges;
public ImmutableArray<Il2CppInterfaceOffsetPair> InterfaceOffsets => Metadata.InterfaceOffsets;
public ImmutableArray<int> InterfaceUsageIndices => Metadata.InterfaceUsageIndices;
public ImmutableArray<int> NestedTypeIndices => Metadata.NestedTypeIndices;
public ImmutableArray<int> AttributeTypeIndices => Metadata.AttributeTypeIndices;
public ImmutableArray<uint> VTableMethodIndices => Metadata.VTableMethodIndices;
public ImmutableArray<Il2CppFieldRef> FieldRefs => Metadata.FieldRefs;
public Dictionary<int, (ulong, object)> FieldDefaultValue { get; } = new Dictionary<int, (ulong, object)>();
public Dictionary<int, (ulong, object)> ParameterDefaultValue { get; } = new Dictionary<int, (ulong, object)>();
public List<long> FieldOffsets { get; }
public List<Il2CppType> TypeReferences => Binary.TypeReferences;
public ImmutableArray<Il2CppType> TypeReferences => Binary.TypeReferences;
public Dictionary<ulong, int> TypeReferenceIndicesByAddress => Binary.TypeReferenceIndicesByAddress;
public List<Il2CppGenericInst> GenericInstances => Binary.GenericInstances;
public ImmutableArray<Il2CppGenericInst> GenericInstances => Binary.GenericInstances;
public Dictionary<string, Il2CppCodeGenModule> Modules => Binary.Modules;
public ulong[] CustomAttributeGenerators { get; }
public ulong[] MethodInvokePointers { get; }
public Il2CppMethodSpec[] MethodSpecs => Binary.MethodSpecs;
public ImmutableArray<Il2CppMethodSpec> MethodSpecs => Binary.MethodSpecs;
public Dictionary<Il2CppMethodSpec, ulong> GenericMethodPointers { get; }
public Dictionary<Il2CppMethodSpec, int> GenericMethodInvokerIndices => Binary.GenericMethodInvokerIndices;
public List<Il2CppTypeDefinitionSizes> TypeDefinitionSizes => Binary.TypeDefinitionSizes;
public ImmutableArray<Il2CppTypeDefinitionSizes> TypeDefinitionSizes => Binary.TypeDefinitionSizes;
// TODO: Finish all file access in the constructor and eliminate the need for this
public IFileFormatStream BinaryImage => Binary.Image;
@@ -77,7 +85,7 @@ namespace Il2CppInspector
return (0ul, null);
// Get pointer in binary to default value
var pValue = Metadata.Header.fieldAndParameterDefaultValueDataOffset + dataIndex;
var pValue = Metadata.Header.FieldAndParameterDefaultValueDataOffset + dataIndex;
var typeRef = TypeReferences[typeIndex];
// Default value is null
@@ -85,7 +93,7 @@ namespace Il2CppInspector
return (0ul, null);
Metadata.Position = pValue;
var value = BlobReader.GetConstantValueFromBlob(this, typeRef.type, Metadata);
var value = BlobReader.GetConstantValueFromBlob(this, typeRef.Type, Metadata);
return ((ulong) pValue, value);
}
@@ -93,21 +101,21 @@ namespace Il2CppInspector
private List<MetadataUsage> buildMetadataUsages()
{
// No metadata usages for versions < 19
if (Version < 19)
if (Version < MetadataVersions.V190)
return null;
// Metadata usages are lazily initialized during runtime for versions >= 27
if (Version >= 27)
if (Version >= MetadataVersions.V270)
return buildLateBindingMetadataUsages();
// Version >= 19 && < 27
var usages = new Dictionary<uint, MetadataUsage>();
var usages = new Dictionary<uint, uint>();
foreach (var metadataUsageList in Metadata.MetadataUsageLists)
{
for (var i = 0; i < metadataUsageList.count; i++)
for (var i = 0; i < metadataUsageList.Count; i++)
{
var metadataUsagePair = Metadata.MetadataUsagePairs[metadataUsageList.start + i];
usages.TryAdd(metadataUsagePair.destinationindex, MetadataUsage.FromEncodedIndex(this, metadataUsagePair.encodedSourceIndex));
var metadataUsagePair = Metadata.MetadataUsagePairs[metadataUsageList.Start + i];
usages.TryAdd(metadataUsagePair.DestinationIndex, metadataUsagePair.EncodedSourceIndex);
}
}
@@ -115,11 +123,13 @@ namespace Il2CppInspector
// Unfortunately the value supplied in MetadataRegistration.matadataUsagesCount seems to be incorrect,
// so we have to calculate the correct number of usages above before reading the usage address list from the binary
var count = usages.Keys.Max() + 1;
var addresses = Binary.Image.ReadMappedArray<ulong>(Binary.MetadataRegistration.metadataUsages, (int) count);
foreach (var usage in usages)
usage.Value.SetAddress(addresses[usage.Key]);
var addresses = Binary.Image.ReadMappedUWordArray(Binary.MetadataRegistration.MetadataUsages, (int) count);
return usages.Values.ToList();
var metadataUsages = new List<MetadataUsage>();
foreach (var (index, encodedUsage) in usages)
metadataUsages.Add(MetadataUsage.FromEncodedIndex(this, encodedUsage, addresses[index]));
return metadataUsages;
}
private List<MetadataUsage> buildLateBindingMetadataUsages()
@@ -142,10 +152,7 @@ namespace Il2CppInspector
if (CheckMetadataUsageSanity(usage)
&& BinaryImage.TryMapFileOffsetToVA(i * ((uint)BinaryImage.Bits / 8), out var va))
{
usage.SetAddress(va);
usages.Add(usage);
}
usages.Add(MetadataUsage.FromEncodedIndex(this, encodedToken, va));
}
}
@@ -155,7 +162,7 @@ namespace Il2CppInspector
{
return usage.Type switch
{
MetadataUsageType.TypeInfo or MetadataUsageType.Type => TypeReferences.Count > usage.SourceIndex,
MetadataUsageType.TypeInfo or MetadataUsageType.Type => TypeReferences.Length > usage.SourceIndex,
MetadataUsageType.MethodDef => Methods.Length > usage.SourceIndex,
MetadataUsageType.FieldInfo or MetadataUsageType.FieldRva => FieldRefs.Length > usage.SourceIndex,
MetadataUsageType.StringLiteral => StringLiterals.Length > usage.SourceIndex,
@@ -180,11 +187,11 @@ namespace Il2CppInspector
// Get all field default values
foreach (var fdv in Metadata.FieldDefaultValues)
FieldDefaultValue.Add(fdv.fieldIndex, ((ulong,object)) getDefaultValue(fdv.typeIndex, fdv.dataIndex));
FieldDefaultValue.Add(fdv.FieldIndex, ((ulong,object)) getDefaultValue(fdv.TypeIndex, fdv.DataIndex));
// Get all parameter default values
foreach (var pdv in Metadata.ParameterDefaultValues)
ParameterDefaultValue.Add(pdv.parameterIndex, ((ulong,object)) getDefaultValue(pdv.typeIndex, pdv.dataIndex));
ParameterDefaultValue.Add(pdv.ParameterIndex, ((ulong,object)) getDefaultValue(pdv.TypeIndex, pdv.DataIndex));
// Get all field offsets
if (Binary.FieldOffsets != null) {
@@ -197,19 +204,21 @@ namespace Il2CppInspector
for (var i = 0; i < TypeDefinitions.Length; i++) {
var def = TypeDefinitions[i];
var pFieldOffsets = Binary.FieldOffsetPointers[i];
if (pFieldOffsets != 0) {
bool available = true;
if (pFieldOffsets != 0)
{
// If the target address range is not mapped in the file, assume zeroes
try {
BinaryImage.Position = BinaryImage.MapVATR((ulong) pFieldOffsets);
if (BinaryImage.TryMapVATR((ulong)pFieldOffsets, out var fieldOffsetPosition))
{
BinaryImage.Position = fieldOffsetPosition;
var fieldOffsets = BinaryImage.ReadArray<uint>(def.FieldCount);
for (var fieldIndex = 0; fieldIndex < def.FieldCount; fieldIndex++)
offsets.Add(def.FieldIndex + fieldIndex, fieldOffsets[fieldIndex]);
}
catch (InvalidOperationException) {
available = false;
else
{
for (var fieldIndex = 0; fieldIndex < def.FieldCount; fieldIndex++)
offsets.Add(def.FieldIndex + fieldIndex, 0);
}
for (var f = 0; f < def.field_count; f++)
offsets.Add(def.fieldStart + f, available? BinaryImage.ReadUInt32() : 0);
}
}
@@ -217,20 +226,20 @@ namespace Il2CppInspector
}
// Build list of custom attribute generators
if (Version < 27)
if (Version < MetadataVersions.V270)
CustomAttributeGenerators = Binary.CustomAttributeGenerators;
else if (Version < 29)
else if (Version < MetadataVersions.V290)
{
var cagCount = Images.Sum(i => i.customAttributeCount);
var cagCount = Images.Sum(i => i.CustomAttributeCount);
CustomAttributeGenerators = new ulong[cagCount];
foreach (var image in Images)
{
// Get CodeGenModule for this image
var codeGenModule = Binary.Modules[Strings[image.nameIndex]];
var cags = BinaryImage.ReadMappedWordArray(codeGenModule.customAttributeCacheGenerator,
(int) image.customAttributeCount);
cags.CopyTo(CustomAttributeGenerators, image.customAttributeStart);
var codeGenModule = Binary.Modules[Strings[image.NameIndex]];
var cags = BinaryImage.ReadMappedWordArray(codeGenModule.CustomAttributeCacheGenerator,
(int) image.CustomAttributeCount);
cags.CopyTo(CustomAttributeGenerators, image.CustomAttributeStart);
}
}
else
@@ -243,7 +252,7 @@ namespace Il2CppInspector
// Get sorted list of function pointers from all sources
// TODO: This does not include IL2CPP API functions
var sortedFunctionPointers = (Version <= 24.1)?
var sortedFunctionPointers = (Version <= MetadataVersions.V241) ?
Binary.GlobalMethodPointers.Select(getDecodedAddress).ToList() :
Binary.ModuleMethodPointers.SelectMany(module => module.Value).Select(getDecodedAddress).ToList();
@@ -261,20 +270,20 @@ namespace Il2CppInspector
FunctionAddresses.Add(sortedFunctionPointers[^1], sortedFunctionPointers[^1]);
// Organize custom attribute indices
if (Version >= 24.1) {
if (Version >= MetadataVersions.V241) {
AttributeIndicesByToken = [];
foreach (var image in Images)
{
var attsByToken = new Dictionary<uint, int>();
for (int i = 0; i < image.customAttributeCount; i++)
for (int i = 0; i < image.CustomAttributeCount; i++)
{
var index = image.customAttributeStart + i;
var token = Version >= 29 ? AttributeDataRanges[index].token : AttributeTypeRanges[index].token;
var index = image.CustomAttributeStart + i;
var token = Version >= MetadataVersions.V290 ? AttributeDataRanges[index].Token : AttributeTypeRanges[index].Token;
attsByToken.Add(token, index);
}
if (attsByToken.Count > 0)
AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken);
AttributeIndicesByToken.Add(image.CustomAttributeStart, attsByToken);
}
}
@@ -288,20 +297,20 @@ namespace Il2CppInspector
// Get a method pointer if available
public (ulong Start, ulong End)? GetMethodPointer(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) {
// Find method pointer
if (methodDef.methodIndex < 0)
if (methodDef.MethodIndex < 0)
return null;
ulong start = 0;
// Global method pointer array
if (Version <= 24.1) {
start = Binary.GlobalMethodPointers[methodDef.methodIndex];
if (Version <= MetadataVersions.V241) {
start = Binary.GlobalMethodPointers[methodDef.MethodIndex];
}
// Per-module method pointer array uses the bottom 24 bits of the method's metadata token
// Derived from il2cpp::vm::MetadataCache::GetMethodPointer
if (Version >= 24.2) {
var method = (methodDef.token & 0xffffff);
if (Version >= MetadataVersions.V242) {
var method = (methodDef.Token & 0xffffff);
if (method == 0)
return null;
@@ -335,19 +344,19 @@ namespace Il2CppInspector
// Get a method invoker index from a method definition
public int GetInvokerIndex(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) {
if (Version <= 24.1) {
return methodDef.invokerIndex;
if (Version <= MetadataVersions.V241) {
return methodDef.InvokerIndex;
}
// Version >= 24.2
var methodInModule = (methodDef.token & 0xffffff);
return Binary.MethodInvokerIndices[module][methodInModule - 1];
var methodInModule = (methodDef.Token & 0xffffff);
return Binary.MethodInvokerIndices[module][(int)methodInModule - 1];
}
public MetadataUsage[] GetVTable(Il2CppTypeDefinition definition) {
MetadataUsage[] res = new MetadataUsage[definition.vtable_count];
for (int i = 0; i < definition.vtable_count; i++) {
var encodedIndex = VTableMethodIndices[definition.vtableStart + i];
MetadataUsage[] res = new MetadataUsage[definition.VTableCount];
for (int i = 0; i < definition.VTableCount; i++) {
var encodedIndex = VTableMethodIndices[definition.VTableIndex + i];
MetadataUsage usage = MetadataUsage.FromEncodedIndex(this, encodedIndex);
if (usage.SourceIndex != 0)
res[i] = usage;
@@ -402,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();
@@ -420,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();
@@ -523,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;
@@ -551,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

@@ -9,6 +9,9 @@ using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using VersionedSerialization;
namespace Il2CppInspector
{
@@ -120,7 +123,7 @@ namespace Il2CppInspector
// Find CodeRegistration
// >= 24.2
if (metadata.Version >= 24.2) {
if (metadata.Version >= MetadataVersions.V242) {
// < 27: mscorlib.dll is always the first CodeGenModule
// >= 27: mscorlib.dll is always the last CodeGenModule (Assembly-CSharp.dll is always the first but non-Unity builds don't have this DLL)
@@ -137,7 +140,7 @@ namespace Il2CppInspector
// Unwind from string pointer -> CodeGenModule -> CodeGenModules + x
foreach (var potentialCodeGenModules in FindAllPointerChains(imageBytes, va, 2))
{
if (metadata.Version >= 27)
if (metadata.Version >= MetadataVersions.V270)
{
for (int i = imagesCount - 1; i >= 0; i--)
{
@@ -145,7 +148,7 @@ namespace Il2CppInspector
potentialCodeGenModules - (ulong) i * ptrSize, 1))
{
var expectedImageCountPtr = potentialCodeRegistrationPtr - ptrSize;
var expectedImageCount = ptrSize == 4 ? Image.ReadMappedInt32(expectedImageCountPtr) : Image.ReadMappedInt64(expectedImageCountPtr);
var expectedImageCount = Image.ReadMappedWord(expectedImageCountPtr);
if (expectedImageCount == imagesCount)
return potentialCodeRegistrationPtr;
}
@@ -203,24 +206,43 @@ namespace Il2CppInspector
return (0, 0);
var codeGenEndPtr = codeRegVa + ptrSize;
// pCodeGenModules is the last field in CodeRegistration so we subtract the size of one pointer from the struct size
codeRegistration = codeRegVa - ((ulong) metadata.Sizeof(typeof(Il2CppCodeRegistration), Image.Version, Image.Bits / 8) - ptrSize);
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);
// In v24.3, windowsRuntimeFactoryTable collides with codeGenModules. So far no samples have had windowsRuntimeFactoryCount > 0;
// if this changes we'll have to get smarter about disambiguating these two.
var cr = Image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
var cr = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
if (Image.Version == 24.2 && cr.interopDataCount == 0) {
Image.Version = 24.3;
codeRegistration -= ptrSize * 2; // two extra words for WindowsRuntimeFactory
if (Image.Version == MetadataVersions.V242 && cr.InteropDataCount == 0) {
Image.Version = MetadataVersions.V243;
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);
}
if (Image.Version == 27 && cr.reversePInvokeWrapperCount > 0x30000)
if (Image.Version == MetadataVersions.V270 && cr.ReversePInvokeWrapperCount > 0x30000)
{
// If reversePInvokeWrapperCount is a pointer, then it's because we're actually on 27.1 and there's a genericAdjustorThunks pointer interfering.
// We need to bump version to 27.1 and back up one more pointer.
Image.Version = 27.1;
codeRegistration -= ptrSize;
Image.Version = MetadataVersions.V271;
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);
cr = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
}
// genericAdjustorThunks was inserted before invokerPointersCount in 24.5 and 27.1
// pointer expected if we need to bump version
if (Image.Version == MetadataVersions.V244 &&
(cr.InvokerPointersCount > 0x50000 || cr.ReversePInvokeWrapperCount > cr.ReversePInvokeWrappers))
{
Image.Version = MetadataVersions.V245;
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);
cr = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
}
if ((Image.Version == MetadataVersions.V290 || Image.Version == MetadataVersions.V310) &&
cr.GenericMethodPointersCount >= cr.GenericMethodPointers)
{
Image.Version = new StructVersion(Image.Version.Major, 0, MetadataVersions.Tag2022);
codeRegistration = codeGenEndPtr - (ulong)Il2CppCodeRegistration.Size(Image.Version, Image.Bits == 32);
}
}
@@ -228,18 +250,15 @@ namespace Il2CppInspector
// <= 24.1
else {
// The first item in CodeRegistration is the total number of method pointers
vas = FindAllMappedWords(imageBytes, (ulong) metadata.Methods.Count(m => (uint) m.methodIndex != 0xffff_ffff));
if (!vas.Any())
return (0, 0);
vas = FindAllMappedWords(imageBytes, (ulong) metadata.Methods.Count(m => (uint) m.MethodIndex != 0xffff_ffff));
// The count of method pointers will be followed some bytes later by
// the count of custom attribute generators; the distance between them
// depends on the il2cpp version so we just use ReadMappedObject to simplify the math
foreach (var va in vas) {
var cr = Image.ReadMappedObject<Il2CppCodeRegistration>(va);
var cr = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(va);
if (cr.customAttributeCount == metadata.AttributeTypeRanges.Length)
if (cr.CustomAttributeCount == metadata.AttributeTypeRanges.Length)
codeRegistration = va;
}
@@ -253,16 +272,17 @@ namespace Il2CppInspector
// Find TypeDefinitionsSizesCount (4th last field) then work back to the start of the struct
// This saves us from guessing where metadataUsagesCount is later
var mrSize = (ulong) metadata.Sizeof(typeof(Il2CppMetadataRegistration), Image.Version, Image.Bits / 8);
var mrSize = (ulong)Il2CppMetadataRegistration.Size(Image.Version, Image.Bits == 32);
var typesLength = (ulong) metadata.Types.Length;
vas = FindAllMappedWords(imageBytes, typesLength).Select(a => a - mrSize + ptrSize * 4);
// >= 19 && < 27
if (Image.Version < 27)
foreach (var va in vas) {
var mr = Image.ReadMappedObject<Il2CppMetadataRegistration>(va);
if (mr.metadataUsagesCount == (ulong) metadata.MetadataUsageLists.Length)
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;
}
@@ -271,22 +291,17 @@ namespace Il2CppInspector
// Synonyms: copying, piracy, theft, strealing, infringement of copyright
// >= 27
else {
// We're going to just sanity check all of the fields
// All counts should be under a certain threshold
// All pointers should be mappable to the binary
var mrFieldCount = mrSize / (ulong) (Image.Bits / 8);
foreach (var va in vas) {
var mrWords = Image.ReadMappedWordArray(va, (int) mrFieldCount);
// Even field indices are counts, odd field indices are pointers
bool ok = true;
for (var i = 0; i < mrWords.Length && ok; i++) {
ok = i % 2 == 0 || Image.TryMapVATR((ulong) mrWords[i], out _);
}
if (ok)
else
{
foreach (var va in vas)
{
var mr = Image.ReadMappedVersionedObject<Il2CppMetadataRegistration>(va);
if (mr.TypeDefinitionsSizesCount == metadata.Types.Length
&& mr.FieldOffsetsCount == metadata.Types.Length)
{
metadataRegistration = va;
break;
}
}
}
if (metadataRegistration == 0)

View File

@@ -7,41 +7,45 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection;
using Il2CppInspector.Next;
using Il2CppInspector.Next.Metadata;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization;
namespace Il2CppInspector
{
public class Metadata : BinaryObjectStream
public class Metadata : BinaryObjectStreamReader
{
public Il2CppGlobalMetadataHeader Header { get; set; }
public Il2CppAssemblyDefinition[] Assemblies { get; set; }
public Il2CppImageDefinition[] Images { get; set; }
public Il2CppTypeDefinition[] Types { get; set; }
public Il2CppMethodDefinition[] Methods { get; set; }
public Il2CppParameterDefinition[] Params { get; set; }
public Il2CppFieldDefinition[] Fields { get; set; }
public Il2CppFieldDefaultValue[] FieldDefaultValues { get; set; }
public Il2CppParameterDefaultValue[] ParameterDefaultValues { get; set; }
public Il2CppPropertyDefinition[] Properties { get; set; }
public Il2CppEventDefinition[] Events { get; set; }
public Il2CppGenericContainer[] GenericContainers { get; set; }
public Il2CppGenericParameter[] GenericParameters { get; set; }
public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges { get; set; }
public Il2CppCustomAttributeDataRange[] AttributeDataRanges { get; set; }
public Il2CppInterfaceOffsetPair[] InterfaceOffsets { get; set; }
public Il2CppMetadataUsageList[] MetadataUsageLists { get; set; }
public Il2CppMetadataUsagePair[] MetadataUsagePairs { get; set; }
public Il2CppFieldRef[] FieldRefs { get; set; }
public ImmutableArray<Il2CppAssemblyDefinition> Assemblies { get; set; }
public ImmutableArray<Il2CppImageDefinition> Images { get; set; }
public ImmutableArray<Il2CppTypeDefinition> Types { get; set; }
public ImmutableArray<Il2CppMethodDefinition> Methods { get; set; }
public ImmutableArray<Il2CppParameterDefinition> Params { get; set; }
public ImmutableArray<Il2CppFieldDefinition> Fields { get; set; }
public ImmutableArray<Il2CppFieldDefaultValue> FieldDefaultValues { get; set; }
public ImmutableArray<Il2CppParameterDefaultValue> ParameterDefaultValues { get; set; }
public ImmutableArray<Il2CppPropertyDefinition> Properties { get; set; }
public ImmutableArray<Il2CppEventDefinition> Events { get; set; }
public ImmutableArray<Il2CppGenericContainer> GenericContainers { get; set; }
public ImmutableArray<Il2CppGenericParameter> GenericParameters { get; set; }
public ImmutableArray<Il2CppCustomAttributeTypeRange> AttributeTypeRanges { get; set; }
public ImmutableArray<Il2CppCustomAttributeDataRange> AttributeDataRanges { get; set; }
public ImmutableArray<Il2CppInterfaceOffsetPair> InterfaceOffsets { get; set; }
public ImmutableArray<Il2CppMetadataUsageList> MetadataUsageLists { get; set; }
public ImmutableArray<Il2CppMetadataUsagePair> MetadataUsagePairs { get; set; }
public ImmutableArray<Il2CppFieldRef> FieldRefs { get; set; }
public int[] InterfaceUsageIndices { get; set; }
public int[] NestedTypeIndices { get; set; }
public int[] AttributeTypeIndices { get; set; }
public int[] GenericConstraintIndices { get; set; }
public uint[] VTableMethodIndices { get; set; }
public ImmutableArray<int> InterfaceUsageIndices { get; set; }
public ImmutableArray<int> NestedTypeIndices { get; set; }
public ImmutableArray<int> AttributeTypeIndices { get; set; }
public ImmutableArray<int> GenericConstraintIndices { get; set; }
public ImmutableArray<uint> VTableMethodIndices { get; set; }
public string[] StringLiterals { get; set; }
public Dictionary<int, string> Strings { get; private set; } = new Dictionary<int, string>();
@@ -78,22 +82,22 @@ namespace Il2CppInspector
StatusUpdate("Processing metadata");
// Read metadata header
Header = ReadObject<Il2CppGlobalMetadataHeader>(0);
Header = ReadVersionedObject<Il2CppGlobalMetadataHeader>(0);
// Check for correct magic bytes
if (Header.signature != Il2CppConstants.MetadataSignature) {
if (!Header.SanityValid) {
throw new InvalidOperationException("The supplied metadata file is not valid.");
}
// Set object versioning for Bin2Object from metadata version
Version = Header.version;
Version = new StructVersion(Header.Version);
if (Version < 16 || Version > 31) {
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.version}).");
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V350) {
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 = ReadObject<Il2CppGlobalMetadataHeader>(0);
Header = ReadVersionedObject<Il2CppGlobalMetadataHeader>(0);
// Sanity checking
// Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata always writes the metadata information in the same order it appears in the header,
@@ -105,109 +109,90 @@ namespace Il2CppInspector
// 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;
var realHeaderLength = Header.StringLiteralOffset;
if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) {
if (Version == 24.0) {
Version = 24.2;
Header = ReadObject<Il2CppGlobalMetadataHeader>(0);
if (realHeaderLength != Sizeof<Il2CppGlobalMetadataHeader>()) {
if (Version == MetadataVersions.V240) {
Version = MetadataVersions.V242;
Header = ReadVersionedObject<Il2CppGlobalMetadataHeader>(0);
}
}
if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) {
if (realHeaderLength != Sizeof<Il2CppGlobalMetadataHeader>()) {
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 >= 16)
Images = ReadArray<Il2CppImageDefinition>(Header.imagesOffset, Header.imagesCount / Sizeof(typeof(Il2CppImageDefinition)));
if (Version >= MetadataVersions.V160)
Images = ReadVersionedObjectArray<Il2CppImageDefinition>(Header.ImagesOffset, Header.ImagesSize / Sizeof<Il2CppImageDefinition>());
// As an additional sanity check, all images in the metadata should have Mono.Cecil.MetadataToken == 1
// In metadata v24.1, two extra fields were added which will cause the below test to fail.
// In that case, we can then adjust the version number and reload
// Tokens were introduced in v19 - we don't bother testing earlier versions
if (Version >= 19 && Images.Any(x => x.token != 1))
if (Version == 24.0) {
Version = 24.1;
if (Version >= MetadataVersions.V190 && Images.Any(x => x.Token != 1))
if (Version == MetadataVersions.V240) {
Version = MetadataVersions.V241;
// No need to re-read the header, it's the same for both sub-versions
Images = ReadArray<Il2CppImageDefinition>(Header.imagesOffset, Header.imagesCount / Sizeof(typeof(Il2CppImageDefinition)));
Images = ReadVersionedObjectArray<Il2CppImageDefinition>(Header.ImagesOffset, Header.ImagesSize / Sizeof<Il2CppImageDefinition>());
if (Images.Any(x => x.token != 1))
if (Images.Any(x => x.Token != 1))
throw new InvalidOperationException("Could not verify the integrity of the metadata file image list");
}
Types = ReadArray<Il2CppTypeDefinition>(Header.typeDefinitionsOffset, Header.typeDefinitionsCount / Sizeof(typeof(Il2CppTypeDefinition)));
Methods = ReadArray<Il2CppMethodDefinition>(Header.methodsOffset, Header.methodsCount / Sizeof(typeof(Il2CppMethodDefinition)));
Params = ReadArray<Il2CppParameterDefinition>(Header.parametersOffset, Header.parametersCount / Sizeof(typeof(Il2CppParameterDefinition)));
Fields = ReadArray<Il2CppFieldDefinition>(Header.fieldsOffset, Header.fieldsCount / Sizeof(typeof(Il2CppFieldDefinition)));
FieldDefaultValues = ReadArray<Il2CppFieldDefaultValue>(Header.fieldDefaultValuesOffset, Header.fieldDefaultValuesCount / Sizeof(typeof(Il2CppFieldDefaultValue)));
Properties = ReadArray<Il2CppPropertyDefinition>(Header.propertiesOffset, Header.propertiesCount / Sizeof(typeof(Il2CppPropertyDefinition)));
Events = ReadArray<Il2CppEventDefinition>(Header.eventsOffset, Header.eventsCount / Sizeof(typeof(Il2CppEventDefinition)));
InterfaceUsageIndices = ReadArray<int>(Header.interfacesOffset, Header.interfacesCount / sizeof(int));
NestedTypeIndices = ReadArray<int>(Header.nestedTypesOffset, Header.nestedTypesCount / sizeof(int));
GenericContainers = ReadArray<Il2CppGenericContainer>(Header.genericContainersOffset, Header.genericContainersCount / Sizeof(typeof(Il2CppGenericContainer)));
GenericParameters = ReadArray<Il2CppGenericParameter>(Header.genericParametersOffset, Header.genericParametersCount / Sizeof(typeof(Il2CppGenericParameter)));
GenericConstraintIndices = ReadArray<int>(Header.genericParameterConstraintsOffset, Header.genericParameterConstraintsCount / sizeof(int));
InterfaceOffsets = ReadArray<Il2CppInterfaceOffsetPair>(Header.interfaceOffsetsOffset, Header.interfaceOffsetsCount / Sizeof(typeof(Il2CppInterfaceOffsetPair)));
VTableMethodIndices = ReadArray<uint>(Header.vtableMethodsOffset, Header.vtableMethodsCount / sizeof(uint));
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));
if (Version >= 16) {
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
var assemblyCount = Header.assembliesCount / Sizeof(typeof(Il2CppAssemblyDefinition));
var assemblyCount = Header.AssembliesSize / Sizeof<Il2CppAssemblyDefinition>();
var changedAssemblyDefStruct = false;
if ((Version == 24.1 || Version == 24.2 || Version == 24.3) && assemblyCount < Images.Length)
if ((Version == MetadataVersions.V241 || Version == MetadataVersions.V242 || Version == MetadataVersions.V243) && assemblyCount < Images.Length)
{
if (Version == 24.1)
if (Version == MetadataVersions.V241)
changedAssemblyDefStruct = true;
Version = 24.4;
Version = MetadataVersions.V244;
}
Assemblies = ReadArray<Il2CppAssemblyDefinition>(Header.assembliesOffset, Images.Length);
Assemblies = ReadVersionedObjectArray<Il2CppAssemblyDefinition>(Header.AssembliesOffset, Images.Length);
if (changedAssemblyDefStruct)
Version = 24.1;
Version = MetadataVersions.V241;
ParameterDefaultValues = ReadArray<Il2CppParameterDefaultValue>(Header.parameterDefaultValuesOffset, Header.parameterDefaultValuesCount / Sizeof(typeof(Il2CppParameterDefaultValue)));
ParameterDefaultValues = ReadVersionedObjectArray<Il2CppParameterDefaultValue>(Header.ParameterDefaultValuesOffset, Header.ParameterDefaultValuesSize / Sizeof<Il2CppParameterDefaultValue>());
}
if (Version >= 19 && Version < 27) {
MetadataUsageLists = ReadArray<Il2CppMetadataUsageList>(Header.metadataUsageListsOffset, Header.metadataUsageListsCount / Sizeof(typeof(Il2CppMetadataUsageList)));
MetadataUsagePairs = ReadArray<Il2CppMetadataUsagePair>(Header.metadataUsagePairsOffset, Header.metadataUsagePairsCount / Sizeof(typeof(Il2CppMetadataUsagePair)));
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 >= 19) {
FieldRefs = ReadArray<Il2CppFieldRef>(Header.fieldRefsOffset, Header.fieldRefsCount / Sizeof(typeof(Il2CppFieldRef)));
if (Version >= MetadataVersions.V190) {
FieldRefs = ReadVersionedObjectArray<Il2CppFieldRef>(Header.FieldRefsOffset, Header.FieldRefsSize / Sizeof<Il2CppFieldRef>());
}
if (Version >= 21 && Version < 29) {
AttributeTypeIndices = ReadArray<int>(Header.attributeTypesOffset, Header.attributeTypesCount / sizeof(int));
AttributeTypeRanges = ReadArray<Il2CppCustomAttributeTypeRange>(Header.attributesInfoOffset, Header.attributesInfoCount / Sizeof(typeof(Il2CppCustomAttributeTypeRange)));
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 >= 29)
if (Version >= MetadataVersions.V290)
{
AttributeDataRanges = ReadArray<Il2CppCustomAttributeDataRange>(Header.attributeDataRangeOffset,
Header.attributeDataRangeSize / Sizeof(typeof(Il2CppCustomAttributeDataRange)));
}
if (Version is 29 or 31)
{
// 29.2/31.2 added a new isUnmanagedCallersOnly flag to Il2CppMethodDefinition.
// This offsets all subsequent entries by one - we can detect this by checking the
// top token byte (which should always be 0x06).
if (Methods.Length >= 2)
{
var secondToken = Methods[1].token;
if (secondToken >> 24 != 0x6)
{
Version += 0.2;
Methods = ReadArray<Il2CppMethodDefinition>(Header.methodsOffset,
Header.methodsCount / Sizeof(typeof(Il2CppMethodDefinition)));
}
}
AttributeDataRanges = ReadVersionedObjectArray<Il2CppCustomAttributeDataRange>(Header.AttributeDataRangeOffset,
Header.AttributeDataRangeSize / Sizeof<Il2CppCustomAttributeDataRange>());
}
// Get all metadata strings
@@ -216,10 +201,10 @@ namespace Il2CppInspector
Strings = pluginGetStringsResult.Strings;
else {
Position = Header.stringOffset;
Position = Header.StringOffset;
while (Position < Header.stringOffset + Header.stringCount)
Strings.Add((int) Position - Header.stringOffset, ReadNullTerminatedString());
while (Position < Header.StringOffset + Header.StringSize)
Strings.Add((int) Position - Header.StringOffset, ReadNullTerminatedString());
}
// Get all string literals
@@ -228,11 +213,28 @@ namespace Il2CppInspector
StringLiterals = pluginGetStringLiteralsResult.StringLiterals.ToArray();
else {
var stringLiteralList = ReadArray<Il2CppStringLiteral>(Header.stringLiteralOffset, Header.stringLiteralCount / Sizeof(typeof(Il2CppStringLiteral)));
var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>());
StringLiterals = new string[stringLiteralList.Length];
for (var i = 0; i < stringLiteralList.Length; i++)
StringLiterals[i] = ReadFixedLengthString(Header.stringLiteralDataOffset + stringLiteralList[i].dataIndex, stringLiteralList[i].length);
if (Version >= MetadataVersions.V350)
{
StringLiterals = new string[stringLiteralList.Length - 1];
for (var i = 0; i < stringLiteralList.Length; i++)
{
var currentStringDataIndex = stringLiteralList[i].DataIndex;
var nextStringDataIndex = stringLiteralList[i + 1].DataIndex;
var stringLength = nextStringDataIndex - currentStringDataIndex;
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + currentStringDataIndex, stringLength);
}
}
else
{
StringLiterals = new string[stringLiteralList.Length];
for (var i = 0; i < stringLiteralList.Length; i++)
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length);
}
}
// Post-processing hook
@@ -246,40 +248,6 @@ namespace Il2CppInspector
CopyTo(outFile);
}
public int Sizeof(Type type) => Sizeof(type, Version);
public int Sizeof(Type type, double metadataVersion, int longSizeBytes = 8) {
if (Reader.ObjectMappings.TryGetValue(type, out var streamType))
type = streamType;
int size = 0;
foreach (var i in type.GetTypeInfo().GetFields())
{
// Only process fields for our selected object versioning (always process if none supplied)
var versions = i.GetCustomAttributes<VersionAttribute>(false).Select(v => (v.Min, v.Max)).ToList();
if (versions.Any() && !versions.Any(v => (v.Min <= metadataVersion || v.Min == -1) && (v.Max >= metadataVersion || v.Max == -1)))
continue;
if (i.FieldType == typeof(long) || i.FieldType == typeof(ulong))
size += longSizeBytes;
else if (i.FieldType == typeof(int) || i.FieldType == typeof(uint))
size += 4;
else if (i.FieldType == typeof(short) || i.FieldType == typeof(ushort))
size += 2;
// Fixed-length array
else if (i.FieldType.IsArray) {
var attr = i.GetCustomAttribute<ArrayLengthAttribute>(false) ??
throw new InvalidOperationException("Array field " + i.Name + " must have ArrayLength attribute");
size += attr.FixedSize;
}
// Embedded object
else
size += Sizeof(i.FieldType, metadataVersion);
}
return size;
}
public int Sizeof<T>() where T : IReadable => T.Size(Version, Is32Bit);
}
}

View File

@@ -1,483 +0,0 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
// Unity 4.6.1p5 - first release, no global-metadata.dat
// Unity 5.2.0f3 -> v15
// Unity 5.3.0f4 -> v16
// Unity 5.3.2f1 -> v19
// Unity 5.3.3f1 -> v20
// Unity 5.3.5f1 -> v21
// Unity 5.5.0f3 -> v22
// Unity 5.6.0f3 -> v23
// Unity 2017.1.0f3 -> v24
// Unity 2018.3.0f2 -> v24.1
// Unity 2019.1.0f2 -> v24.2
// Unity 2019.3.7f1 -> v24.3
// Unity 2019.4.15f1 -> v24.4
// Unity 2019.4.21f1 -> v24.5
// Unity 2020.1.0f1 -> v24.3
// Unity 2020.1.11f1 -> v24.4
// Unity 2020.2.0f1 -> v27
// Unity 2020.2.4f1 -> v27.1
// Unity 2021.1.0f1 -> v27.2
// https://unity3d.com/get-unity/download/archive
// Metadata version is written at the end of Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata or WriteMetadata (Unity.IL2CPP.dll)
// From il2cpp-metadata.h
#pragma warning disable CS0649
public class Il2CppGlobalMetadataHeader
{
public uint signature;
public int version;
public int stringLiteralOffset; // string data for managed code
public int stringLiteralCount;
public int stringLiteralDataOffset;
public int stringLiteralDataCount;
public int stringOffset; // string data for metadata
public int stringCount;
public int eventsOffset; // Il2CppEventDefinition
public int eventsCount;
public int propertiesOffset; // Il2CppPropertyDefinition
public int propertiesCount;
public int methodsOffset; // Il2CppMethodDefinition
public int methodsCount;
[Version(Min = 16)]
public int parameterDefaultValuesOffset; // Il2CppParameterDefaultValue
[Version(Min = 16)]
public int parameterDefaultValuesCount;
public int fieldDefaultValuesOffset; // Il2CppFieldDefaultValue
public int fieldDefaultValuesCount;
public int fieldAndParameterDefaultValueDataOffset; // uint8_t
public int fieldAndParameterDefaultValueDataCount;
[Version(Min = 16)]
public int fieldMarshaledSizesOffset; // Il2CppFieldMarshaledSize
[Version(Min = 16)]
public int fieldMarshaledSizesCount;
public int parametersOffset; // Il2CppParameterDefinition
public int parametersCount;
public int fieldsOffset; // Il2CppFieldDefinition
public int fieldsCount;
public int genericParametersOffset; // Il2CppGenericParameter
public int genericParametersCount;
public int genericParameterConstraintsOffset; // TypeIndex
public int genericParameterConstraintsCount;
public int genericContainersOffset; // Il2CppGenericContainer
public int genericContainersCount;
public int nestedTypesOffset; // TypeDefinitionIndex
public int nestedTypesCount;
public int interfacesOffset; // TypeIndex
public int interfacesCount;
public int vtableMethodsOffset; // EncodedMethodIndex
public int vtableMethodsCount;
public int interfaceOffsetsOffset; // Il2CppInterfaceOffsetPair
public int interfaceOffsetsCount;
public int typeDefinitionsOffset; // Il2CppTypeDefinition
public int typeDefinitionsCount;
[Version(Max = 24.1)]
public int rgctxEntriesOffset; // Il2CppRGCTXDefinition
[Version(Max = 24.1)]
public int rgctxEntriesCount;
[Version(Min = 16)]
public int imagesOffset; // Il2CppImageDefinition
[Version(Min = 16)]
public int imagesCount;
[Version(Min = 16)]
public int assembliesOffset; // Il2CppAssemblyDefinition
[Version(Min = 16)]
public int assembliesCount;
[Version(Min = 19, Max = 24.5)]
public int metadataUsageListsOffset; // Il2CppMetadataUsageList
[Version(Min = 19, Max = 24.5)]
public int metadataUsageListsCount;
[Version(Min = 19, Max = 24.5)]
public int metadataUsagePairsOffset; // Il2CppMetadataUsagePair
[Version(Min = 19, Max = 24.5)]
public int metadataUsagePairsCount;
[Version(Min = 19)]
public int fieldRefsOffset; // Il2CppFieldRef
[Version(Min = 19)]
public int fieldRefsCount;
[Version(Min = 20)]
public int referencedAssembliesOffset; // int32_t
[Version(Min = 20)]
public int referencedAssembliesCount;
[Version(Min = 21, Max = 27.2)]
public int attributesInfoOffset; // Il2CppCustomAttributeTypeRange
[Version(Min = 21, Max = 27.2)]
public int attributesInfoCount;
[Version(Min = 21, Max = 27.2)]
public int attributeTypesOffset; // TypeIndex
[Version(Min = 21, Max = 27.2)]
public int attributeTypesCount;
[Version(Min = 29)]
public uint attributeDataOffset;
[Version(Min = 29)]
public int attributeDataSize;
[Version(Min = 29)]
public uint attributeDataRangeOffset;
[Version(Min = 29)]
public int attributeDataRangeSize;
// Added in metadata v22
[Version(Min = 22)]
public int unresolvedVirtualCallParameterTypesOffset; // TypeIndex
[Version(Min = 22)]
public int unresolvedVirtualCallParameterTypesCount;
[Version(Min = 22)]
public int unresolvedVirtualCallParameterRangesOffset; // Il2CppRange
[Version(Min = 22)]
public int unresolvedVirtualCallParameterRangesCount;
// Added in metadata v23
[Version(Min = 23)]
public int windowsRuntimeTypeNamesOffset; // Il2CppWindowsRuntimeTypeNamePair
[Version(Min = 23)]
public int windowsRuntimeTypeNamesSize;
// Added in metadata v27
[Version(Min = 27)]
public int windowsRuntimeStringsOffset; // const char*
[Version(Min = 27)]
public int windowsRuntimeStringsSize;
// Added in metadata v24
[Version(Min = 24)]
public int exportedTypeDefinitionsOffset; // TypeDefinitionIndex
[Version(Min = 24)]
public int exportedTypeDefinitionsCount;
}
public class Il2CppImageDefinition
{
public int nameIndex;
public int assemblyIndex;
public int typeStart;
public uint typeCount;
[Version(Min = 24)]
public int exportedTypeStart;
[Version(Min = 24)]
public uint exportedTypeCount;
public int entryPointIndex;
[Version(Min = 19)]
public uint token;
[Version(Min = 24.1)]
public int customAttributeStart;
[Version(Min = 24.1)]
public uint customAttributeCount;
}
#pragma warning restore CS0649
// Renamed from Il2CppAssembly somewhere after Unity 2017.2f3 up to Unity 2018.2.0f2
public class Il2CppAssemblyDefinition
{
// They moved the position of aname in v16 from the top to the bottom of the struct
public Il2CppAssemblyNameDefinition aname => aname_pre16 ?? aname_post16;
[Version(Max = 15)]
public Il2CppAssemblyNameDefinition aname_pre16;
public int imageIndex;
[Version(Min = 24.1)]
public uint token;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 20)]
public int referencedAssemblyStart;
[Version(Min = 20)]
public int referencedAssemblyCount;
[Version(Min = 16)]
public Il2CppAssemblyNameDefinition aname_post16;
}
// Renamed from Il2CppAssemblyName somewhere after Unity 2017.2f3 up to Unity 2018.2.0f2
public class Il2CppAssemblyNameDefinition
{
// They moved the position of publicKeyToken in v16 from the middle to the bottom of the struct
public byte[] publicKeyToken => publicKeyToken_post16;
public int nameIndex;
public int cultureIndex;
[Version(Max = 24.3)]
public int hashValueIndex;
public int publicKeyIndex;
[Version(Max = 15), ArrayLength(FixedSize = 8)]
public byte[] publicKeyToken_pre16;
public uint hash_alg;
public int hash_len;
public uint flags;
public int major;
public int minor;
public int build;
public int revision;
[Version(Min = 16), ArrayLength(FixedSize = 8)]
public byte[] publicKeyToken_post16;
}
public class Il2CppTypeDefinition
{
public int nameIndex;
public int namespaceIndex;
// Removed in metadata v24.1
[Version(Max = 24.0)]
public int customAttributeIndex;
public int byvalTypeIndex;
[Version(Max = 24.5)]
public int byrefTypeIndex;
public int declaringTypeIndex;
public int parentIndex;
public int elementTypeIndex; // we can probably remove this one. Only used for enums
[Version(Max = 24.1)]
public int rgctxStartIndex;
[Version(Max = 24.1)]
public int rgctxCount;
public int genericContainerIndex;
// Removed in metadata v23
[Version(Max = 22)]
public int delegateWrapperFromManagedToNativeIndex; // (was renamed to reversePInvokeWrapperIndex in v22)
[Version(Max = 22)]
public int marshalingFunctionsIndex;
[Version(Min = 21, Max = 22)]
public int ccwFunctionIndex;
[Version(Min = 21, Max = 22)]
public int guidIndex;
public uint flags;
public int fieldStart;
public int methodStart;
public int eventStart;
public int propertyStart;
public int nestedTypesStart;
public int interfacesStart;
public int vtableStart;
public int interfaceOffsetsStart;
public ushort method_count;
public ushort property_count;
public ushort field_count;
public ushort event_count;
public ushort nested_type_count;
public ushort vtable_count;
public ushort interfaces_count;
public ushort interface_offsets_count;
// bitfield to portably encode boolean values as single bits
// 01 - valuetype;
// 02 - enumtype;
// 03 - has_finalize;
// 04 - has_cctor;
// 05 - is_blittable;
// 06 - is_import; (from v22: is_import_or_windows_runtime)
// 07-10 - One of nine possible PackingSize values (0, 1, 2, 4, 8, 16, 32, 64, or 128)
public uint bitfield;
[Version(Min = 19)]
public uint token;
}
public class Il2CppMethodDefinition
{
public int nameIndex;
[Version(Min = 16)]
public int declaringType;
public int returnType;
[Version(Min = 31)]
public int returnParameterToken;
public int parameterStart;
[Version(Max = 24.0)]
public int customAttributeIndex;
public int genericContainerIndex;
[Version(Max = 24.1)]
public int methodIndex;
[Version(Max = 24.1)]
public int invokerIndex;
[Version(Max = 24.1)]
public int reversePInvokeWrapperIndex; // (was renamed from delegateWrapperIndex in v22)
[Version(Max = 24.1)]
public int rgctxStartIndex;
[Version(Max = 24.1)]
public int rgctxCount;
public uint token;
public ushort flags;
public ushort iflags;
public ushort slot;
public ushort parameterCount;
[Version(Min = 29.2, Max = 29.2)]
[Version(Min = 31.2, Max = 31.2)]
public byte isUnmanagedCallersOnly;
}
public class Il2CppParameterDefinition
{
public int nameIndex;
public uint token;
[Version(Max = 24.0)]
public int customAttributeIndex;
public int typeIndex;
}
public class Il2CppParameterDefaultValue
{
public int parameterIndex;
public int typeIndex;
public int dataIndex;
}
public class Il2CppFieldDefinition
{
public int nameIndex;
public int typeIndex;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 19)]
public uint token;
}
public class Il2CppFieldDefaultValue
{
public int fieldIndex;
public int typeIndex;
public int dataIndex;
}
public class Il2CppPropertyDefinition
{
public int nameIndex;
public int get;
public int set;
public uint attrs;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 19)]
public uint token;
}
public class Il2CppEventDefinition
{
public int nameIndex;
public int typeIndex;
public int add;
public int remove;
public int raise;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 19)]
public uint token;
}
public class Il2CppGenericContainer
{
/* index of the generic type definition or the generic method definition corresponding to this container */
public int ownerIndex; // either index into Il2CppClass metadata array or Il2CppMethodDefinition array
public int type_argc;
/* If true, we're a generic method, otherwise a generic type definition. */
public int is_method;
/* Our type parameters. */
public uint genericParameterStart; // GenericParameterIndex
}
public class Il2CppGenericParameter
{
public int ownerIndex; /* Type or method this parameter was defined in. */ // GenericContainerIndex
public int nameIndex; // StringIndex
public short constraintsStart; // GenericParameterConstraintIndex
public short constraintsCount;
public ushort num; // Generic parameter position
public ushort flags; // GenericParameterAttributes
}
public class Il2CppCustomAttributeTypeRange
{
[Version(Min = 24.1)]
public uint token;
public int start;
public int count;
}
public class Il2CppInterfaceOffsetPair
{
public int interfaceTypeIndex;
public int offset;
}
// Removed in metadata v27
public class Il2CppMetadataUsageList
{
public uint start;
public uint count;
}
// Removed in metadata v27
public class Il2CppMetadataUsagePair
{
public uint destinationindex;
public uint encodedSourceIndex;
}
public class Il2CppStringLiteral
{
public int length;
public int dataIndex;
}
public class Il2CppFieldRef
{
public int typeIndex;
public int fieldIndex; // local offset into type fields
}
public class Il2CppCustomAttributeDataRange
{
public uint token;
public uint startOffset;
}
}

View File

@@ -6,6 +6,8 @@
All rights reserved.
*/
using Il2CppInspector.Next;
namespace Il2CppInspector
{
public enum MetadataUsageType
@@ -19,11 +21,13 @@ namespace Il2CppInspector
FieldRva = 7
}
public class MetadataUsage
public record struct MetadataUsage
{
public MetadataUsageType Type { get; }
public int SourceIndex { get; }
public ulong VirtualAddress { get; private set; }
public ulong VirtualAddress { get; }
public readonly bool IsValid => Type != 0;
public MetadataUsage(MetadataUsageType type, int sourceIndex, ulong virtualAddress = 0) {
Type = type;
@@ -34,10 +38,10 @@ namespace Il2CppInspector
public static MetadataUsage FromEncodedIndex(Il2CppInspector package, uint encodedIndex, ulong virtualAddress = 0) {
uint index;
MetadataUsageType usageType;
if (package.Version < 19) {
if (package.Version < MetadataVersions.V190) {
/* These encoded indices appear only in vtables, and are decoded by IsGenericMethodIndex/GetDecodedMethodIndex */
var isGeneric = encodedIndex & 0x80000000;
index = package.Binary.VTableMethodReferences[encodedIndex & 0x7FFFFFFF];
index = package.Binary.VTableMethodReferences[(int)(encodedIndex & 0x7FFFFFFF)];
usageType = (isGeneric != 0) ? MetadataUsageType.MethodRef : MetadataUsageType.MethodDef;
} else {
/* These encoded indices appear in metadata usages, and are decoded by GetEncodedIndexType/GetDecodedMethodIndex */
@@ -46,12 +50,10 @@ namespace Il2CppInspector
index = encodedIndex & 0x1FFFFFFF;
// From v27 the bottom bit is set to indicate the usage token hasn't been replaced with a pointer at runtime yet
if (package.Version >= 27)
if (package.Version >= MetadataVersions.V270)
index >>= 1;
}
return new MetadataUsage(usageType, (int)index, virtualAddress);
}
public void SetAddress(ulong virtualAddress) => VirtualAddress = virtualAddress;
}
}

View File

@@ -1,7 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<AssemblyName>Il2CppInspector.Common</AssemblyName>
<Authors>Katy Coe</Authors>
<Version>2023.1</Version>
@@ -38,15 +40,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="dnlib" Version="4.3.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.3.1" />
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
<NoWarn>NU1605</NoWarn>
</PackageReference>
<PackageReference Include="dnlib" Version="4.4.0" />
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
<PackageReference Include="Spectre.Console" Version="0.50.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bin2Object\Bin2Object\Bin2Object.csproj" PrivateAssets="all" />
<ProjectReference Include="..\VersionedSerialization.Generator\VersionedSerialization.Generator.csproj" ReferenceOutputAssembly="false" OutputItemType="Analyzer" />
<ProjectReference Include="..\VersionedSerialization\VersionedSerialization.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -10,6 +10,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using Il2CppInspector.Cpp;
using Il2CppInspector.Next;
using Il2CppInspector.Reflection;
namespace Il2CppInspector.Model
@@ -123,9 +124,9 @@ namespace Il2CppInspector.Model
Add(binary.CodeRegistrationPointer, binary.CodeRegistration);
Add(binary.MetadataRegistrationPointer, binary.MetadataRegistration);
if (Model.Package.Version >= 24.2) {
if (Model.Package.Version >= MetadataVersions.V242) {
// TODO: Add some kind of AppArray composite type for arrays as we'll be adding more later
Add(binary.CodeRegistration.pcodeGenModules, binary.CodeGenModulePointers);
Add(binary.CodeRegistration.CodeGenModules, binary.CodeGenModulePointers);
foreach (var ptr in binary.CodeGenModulePointers)
Add(ptr.Value, binary.Modules[ptr.Key]);

View File

@@ -12,7 +12,9 @@ using System.Linq;
using Aron.Weiler;
using Il2CppInspector.Cpp;
using Il2CppInspector.Cpp.UnityHeaders;
using Il2CppInspector.Next;
using Il2CppInspector.Reflection;
using Spectre.Console;
namespace Il2CppInspector.Model
{
@@ -39,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>();
@@ -54,7 +59,7 @@ namespace Il2CppInspector.Model
public Dictionary<ulong, (FieldInfo Field, string Value)> Fields { get; } = [];
public Dictionary<ulong, (FieldInfo Field, string Value)> FieldRvas { get; } = [];
public bool StringIndexesAreOrdinals => Package.Version < 19;
public bool StringIndexesAreOrdinals => Package.Version < MetadataVersions.V190;
// The .NET type model for the application
public TypeModel TypeModel { get; }
@@ -104,7 +109,8 @@ namespace Il2CppInspector.Model
}
// Initialize
public AppModel(TypeModel model, bool makeDefaultBuild = true) {
public AppModel(TypeModel model, bool makeDefaultBuild = true)
{
// Save .NET type model
TypeModel = model;
@@ -147,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
@@ -234,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
@@ -245,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.
@@ -252,11 +269,11 @@ namespace Il2CppInspector.Model
case MetadataUsageType.FieldInfo or MetadataUsageType.FieldRva:
var fieldRef = TypeModel.Package.FieldRefs[usage.SourceIndex];
var fieldType = TypeModel.GetMetadataUsageType(usage);
var field = fieldType.DeclaredFields.First(f => f.Index == fieldType.Definition.fieldStart + fieldRef.fieldIndex);
var field = fieldType.DeclaredFields.First(f => f.Index == fieldType.Definition.FieldIndex + fieldRef.FieldIndex);
var value = field.HasFieldRVA
? Convert.ToHexString(Package.Metadata.ReadBytes(
(long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.nativeSize))
(long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.NativeSize))
: "";
@@ -270,7 +287,7 @@ namespace Il2CppInspector.Model
}
// Add string literals for metadata <19 to the model
if (Package.Version < 19) {
if (Package.Version < MetadataVersions.V190) {
/* Version < 19 calls `il2cpp_codegen_string_literal_from_index` to get string literals.
* Unfortunately, metadata references are just loose globals in Il2CppMetadataUsage.cpp
* so we can't automatically name those. Next best thing is to define an enum for the strings. */
@@ -292,6 +309,8 @@ namespace Il2CppInspector.Model
declarationGenerator.IncludeType(type);
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
RequiredForwardDefinitions = declarationGenerator.GenerateRequiredForwardDefinitions();
// Restore stdout
Console.SetOut(stdout);

View File

@@ -0,0 +1,15 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppArrayType
{
public Pointer<Il2CppType> ElementType;
public byte Rank;
public byte NumSizes;
public byte NumLowerBound;
public PrimitivePointer<int> Sizes;
public PrimitivePointer<int> LoBounds;
}

View File

@@ -0,0 +1,55 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppCodeGenModule
{
public PrimitivePointer<byte> ModuleName; // const char*
[NativeInteger]
public uint MethodPointerCount;
public Pointer<Il2CppMethodPointer> MethodPointers;
[NativeInteger]
[VersionCondition(EqualTo = "24.5")]
[VersionCondition(GreaterThan = "27.1")]
public uint AdjustorThunksCount;
[VersionCondition(EqualTo = "24.5")]
[VersionCondition(GreaterThan = "27.1")]
public Pointer<Il2CppTokenAdjustorThunkPair> AdjustorThunks;
public PrimitivePointer<int> InvokerIndices; // int*
[NativeInteger]
public uint ReversePInvokeWrapperCount;
public Pointer<Il2CppTokenIndexMethodTuple> ReversePInvokeWrapperIndices;
[NativeInteger]
public uint RgctxRangesCount;
public Pointer<Il2CppTokenRangePair> RgctxRanges;
[NativeInteger]
public uint RgctxsCount;
public Pointer<Il2CppRgctxDefinition> Rgctxs;
public PrimitivePointer<byte> DebuggerMetadata; // Pointer<Il2CppDebuggerMetadataRegistration> DebuggerMetadata;
[VersionCondition(GreaterThan = "27.0", LessThan = "27.2")]
public Pointer<Il2CppMethodPointer> CustomAttributeCacheGenerator;
[VersionCondition(GreaterThan = "27.0")]
public Il2CppMethodPointer ModuleInitializer;
[VersionCondition(GreaterThan = "27.0")]
public PrimitivePointer<int> StaticConstructorTypeIndices; // TypeDefinitionIndex*
[VersionCondition(GreaterThan = "27.0")]
public PrimitivePointer<byte> MetadataRegistration; // Pointer<Il2CppMetadataRegistration>
[VersionCondition(GreaterThan = "27.0")]
public PrimitivePointer<byte> CodeRegistration; // Pointer<Il2CppCodeRegistration>
}

View File

@@ -0,0 +1,116 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using InvokerMethod = Il2CppMethodPointer;
[VersionedStruct]
public partial record struct Il2CppCodeRegistration
{
[NativeInteger]
[VersionCondition(LessThan = "24.1")]
public uint MethodPointersCount;
[VersionCondition(LessThan = "24.1")]
public Pointer<Il2CppMethodPointer> MethodPointers;
[NativeInteger]
public uint ReversePInvokeWrapperCount;
public Pointer<Il2CppMethodPointer> ReversePInvokeWrappers;
[NativeInteger]
[VersionCondition(LessThan = "22.0")]
public uint DelegateWrappersFromManagedToNativeCount;
[VersionCondition(LessThan = "22.0")]
public Pointer<Il2CppMethodPointer> DelegateWrappersFromManagedToNative;
[NativeInteger]
[VersionCondition(LessThan = "22.0")]
public uint MarshalingFunctionsCount;
[VersionCondition(LessThan = "22.0")]
public Pointer<Il2CppMethodPointer> MarshalingFunctions;
[NativeInteger]
[VersionCondition(GreaterThan = "21.0", LessThan = "22.0")]
public uint CcwMarshalingFunctionsCount;
[VersionCondition(GreaterThan = "21.0", LessThan = "22.0")]
public Pointer<Il2CppMethodPointer> CcwMarshalingFunctions;
[NativeInteger]
public uint GenericMethodPointersCount;
public Pointer<Il2CppMethodPointer> GenericMethodPointers;
[VersionCondition(EqualTo = "24.5")]
[VersionCondition(GreaterThan = "27.1")]
public Pointer<Il2CppMethodPointer> GenericAdjustorThunks;
[NativeInteger]
public uint InvokerPointersCount;
public Pointer<InvokerMethod> InvokerPointers;
[NativeInteger]
[VersionCondition(LessThan = "24.5")]
public int CustomAttributeCount;
[VersionCondition(LessThan = "24.5")]
public Pointer<Il2CppMethodPointer> CustomAttributeGenerators;
[NativeInteger]
[VersionCondition(GreaterThan = "21.0", LessThan = "22.0")]
public int GuidCount;
[VersionCondition(GreaterThan = "21.0", LessThan = "22.0")]
public Pointer<Il2CppGuid> Guids;
[NativeInteger]
[VersionCondition(GreaterThan = "22.0", LessThan = "27.2")]
[VersionCondition(EqualTo = "29.0", IncludingTag = "")]
[VersionCondition(EqualTo = "31.0", IncludingTag = "")]
public int UnresolvedVirtualCallCount;
[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")]
public Pointer<Il2CppMethodPointer> UnresolvedVirtualCallPointers;
[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]
[VersionCondition(GreaterThan = "23.0")]
public uint InteropDataCount;
[VersionCondition(GreaterThan = "23.0")]
public Pointer<Il2CppInteropData> InteropData;
[NativeInteger]
[VersionCondition(GreaterThan = "24.3")]
public uint WindowsRuntimeFactoryCount;
[VersionCondition(GreaterThan = "24.3")]
public Pointer<Il2CppWindowsRuntimeFactoryTableEntry> WindowsRuntimeFactoryTable;
[NativeInteger]
[VersionCondition(GreaterThan = "24.2")]
public uint CodeGenModulesCount;
[VersionCondition(GreaterThan = "24.2")]
public Pointer<Pointer<Il2CppCodeGenModule>> CodeGenModules;
}

View File

@@ -0,0 +1,18 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppGenericClass
{
[NativeInteger]
[VersionCondition(LessThan = "24.5")]
public int TypeDefinitionIndex;
[VersionCondition(GreaterThan = "27.0")]
public Pointer<Il2CppType> Type;
public Il2CppGenericContext Context;
public PrimitivePointer<byte> CachedClass; // Il2CppClass*, optional
}

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppGenericContext
{
public Pointer<Il2CppGenericInst> ClassInst;
public Pointer<Il2CppGenericInst> MethodInst;
}

View File

@@ -0,0 +1,14 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppGenericInst
{
public readonly bool Valid => TypeArgc > 0;
[NativeInteger]
public uint TypeArgc;
public Pointer<Pointer<Il2CppType>> TypeArgv;
}

View File

@@ -0,0 +1,12 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using GenericMethodIndex = int;
[VersionedStruct]
public partial record struct Il2CppGenericMethodFunctionsDefinitions
{
public GenericMethodIndex GenericMethodIndex;
public Il2CppGenericMethodIndices Indices;
}

View File

@@ -0,0 +1,16 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using MethodIndex = int;
[VersionedStruct]
public partial record struct Il2CppGenericMethodIndices
{
public MethodIndex MethodIndex;
public MethodIndex InvokerIndex;
[VersionCondition(EqualTo = "24.5")]
[VersionCondition(GreaterThan = "27.1")]
public MethodIndex AdjustorThunkIndex;
}

View File

@@ -0,0 +1,21 @@
using VersionedSerialization;
namespace Il2CppInspector.Next.BinaryMetadata;
public record struct Il2CppGuid : IReadable
{
public Guid Value;
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
var guid = reader.ReadBytes(16);
Value = new Guid(guid, false);
}
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
return 16;
}
public static implicit operator Guid(Il2CppGuid value) => value.Value;
}

View File

@@ -0,0 +1,20 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using PInvokeMarshalToNativeFunc = Il2CppMethodPointer;
using PInvokeMarshalFromNativeFunc = Il2CppMethodPointer;
using PInvokeMarshalCleanupFunc = Il2CppMethodPointer;
using CreateCCWFunc = Il2CppMethodPointer;
[VersionedStruct]
public partial record struct Il2CppInteropData
{
public Il2CppMethodPointer DelegatePInvokeWrapperFunction;
public PInvokeMarshalToNativeFunc PInvokeMarshalToNativeFunction;
public PInvokeMarshalFromNativeFunc PInvokeMarshalFromNativeFunction;
public PInvokeMarshalCleanupFunc PInvokeMarshalCleanupFunction;
public CreateCCWFunc CreateCCWFunction;
public Pointer<Il2CppGuid> Guid;
public Pointer<Il2CppType> Type;
}

View File

@@ -0,0 +1,59 @@
using Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using FieldIndex = int;
using TypeDefinitionIndex = int;
[VersionedStruct]
public partial record struct Il2CppMetadataRegistration
{
[NativeInteger]
public int GenericClassesCount;
public Pointer<Pointer<Il2CppGenericClass>> GenericClasses;
[NativeInteger]
public int GenericInstsCount;
public Pointer<Pointer<Il2CppGenericInst>> GenericInsts;
[NativeInteger]
public int GenericMethodTableCount;
public Pointer<Il2CppGenericMethodFunctionsDefinitions> GenericMethodTable;
[NativeInteger]
public int TypesCount;
public Pointer<Pointer<Il2CppType>> Types;
[NativeInteger]
public int MethodSpecsCount;
public Pointer<Il2CppMethodSpec> MethodSpecs;
[NativeInteger]
[VersionCondition(LessThan = "16.0")]
public int MethodReferencesCount;
[VersionCondition(LessThan = "16.0")]
public PrimitivePointer<PrimitivePointer<uint>> MethodReferences; // uint**
[NativeInteger]
public FieldIndex FieldOffsetsCount;
public PrimitivePointer<PrimitivePointer<int>> FieldOffsets; // int**
[NativeInteger]
public TypeDefinitionIndex TypeDefinitionsSizesCount;
public Pointer<Pointer<Il2CppTypeDefinitionSizes>> TypeDefinitionsSizes;
[NativeInteger]
[VersionCondition(GreaterThan = "19.0")]
public ulong MetadataUsagesCount;
[VersionCondition(GreaterThan = "19.0")]
public Pointer<Pointer<Il2CppMetadataUsage>> MetadataUsages;
}

View File

@@ -0,0 +1,19 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppMethodPointer(ulong addr = 0)
{
public static readonly Il2CppMethodPointer Null = new();
[NativeInteger]
public ulong Value { get; set; } = addr;
public readonly bool IsNull => Value == 0;
public readonly override string ToString() => $"0x{Value:X}";
public static implicit operator ulong(Il2CppMethodPointer ptr) => ptr.Value;
public static implicit operator Il2CppMethodPointer(ulong ptr) => new(ptr);
}

View File

@@ -0,0 +1,14 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using MethodIndex = int;
using GenericInstIndex = int;
[VersionedStruct]
public partial record struct Il2CppMethodSpec
{
public MethodIndex MethodDefinitionIndex;
public GenericInstIndex ClassIndexIndex;
public GenericInstIndex MethodIndexIndex;
}

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppRange
{
public int Start;
public int Length;
}

View File

@@ -0,0 +1,13 @@
using Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppRgctxConstrainedData
{
public TypeIndex TypeIndex;
public Il2CppMetadataUsage EncodedMethodIndex;
}

View File

@@ -0,0 +1,12 @@
// ReSharper disable InconsistentNaming
namespace Il2CppInspector.Next.BinaryMetadata;
public enum Il2CppRgctxDataType
{
IL2CPP_RGCTX_DATA_INVALID,
IL2CPP_RGCTX_DATA_TYPE,
IL2CPP_RGCTX_DATA_CLASS,
IL2CPP_RGCTX_DATA_METHOD,
IL2CPP_RGCTX_DATA_ARRAY,
IL2CPP_RGCTX_DATA_CONSTRAINED,
}

View File

@@ -0,0 +1,15 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppRgctxDefinition
{
[NativeInteger]
public Il2CppRgctxDataType Type;
public PrimitivePointer<byte> Data; // void*
public readonly Pointer<Il2CppRgctxDefinitionData> Definition => Data.PointerValue;
public readonly Pointer<Il2CppRgctxConstrainedData> Constrained => Data.PointerValue;
}

View File

@@ -0,0 +1,15 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
using MethodIndex = int;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppRgctxDefinitionData
{
public int Value;
public readonly MethodIndex MethodIndex => Value;
public readonly TypeIndex TypeIndex => Value;
}

View File

@@ -0,0 +1,12 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppTokenAdjustorThunkPair
{
[NativeInteger]
public uint Token;
public Il2CppMethodPointer AdjustorThunk;
}

View File

@@ -0,0 +1,14 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppTokenIndexMethodTuple
{
public uint Token;
public int Index;
public PrimitivePointer<Il2CppMethodPointer> Method; // void**
public uint GenericMethodIndex;
}

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppTokenRangePair
{
public uint Token;
public Il2CppRange Range;
}

View File

@@ -0,0 +1,111 @@
using Il2CppInspector.Next.Metadata;
using System.Reflection;
using VersionedSerialization;
namespace Il2CppInspector.Next.BinaryMetadata;
using TypeDefinitionIndex = int;
using GenericParameterIndex = int;
using Il2CppMetadataTypeHandle = Pointer<Il2CppTypeDefinition>;
using Il2CppMetadataGenericParameterHandle = Pointer<Il2CppGenericParameter>;
public record struct Il2CppType : IReadable
{
public record struct DataUnion : IReadable
{
public ulong Value;
public readonly TypeDefinitionIndex KlassIndex => (int)Value;
public readonly Il2CppMetadataTypeHandle TypeHandle => Value;
public readonly Pointer<Il2CppType> Type => Value;
public readonly Pointer<Il2CppArrayType> ArrayType => Value;
public readonly GenericParameterIndex GenericParameterIndex => (int)Value;
public readonly Il2CppMetadataGenericParameterHandle GenericParameterHandle => Value;
public readonly Pointer<Il2CppGenericClass> GenericClass => Value;
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
Value = reader.ReadNUInt();
}
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
return is32Bit ? 4 : 8;
}
}
public DataUnion Data;
public uint Value;
public TypeAttributes Attrs
{
readonly get => (TypeAttributes)(Value & 0xFFFF);
set => Value = (Value & 0xFFFF0000) | (uint)value;
}
public Il2CppTypeEnum Type
{
readonly get => (Il2CppTypeEnum)((Value >> 16) & 0b11111111);
set => Value = (Value & 0xFF00FFFF) | ((uint)value) << 16;
}
public uint NumModifiers
{
readonly get => (Value >> 24) & 0b11111;
set => Value = (Value & 0xE0FFFFFF) | value << 24;
}
public bool ByRef
{
readonly get => ((Value >> 29) & 1) == 1;
set => Value = (Value & 0xDFFFFFFF) | (value ? 1u : 0u) << 29;
}
public bool Pinned
{
readonly get => ((Value >> 30) & 1) == 1;
set => Value = (Value & 0xBFFFFFFF) | (value ? 1u : 0u) << 30;
}
public bool ValueType
{
readonly get => ((Value >> 31) & 1) == 1;
set => Value = (Value & 0x7FFFFFFF) | (value ? 1u : 0u) << 31;
}
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
Data.Read(ref reader, version);
Value = reader.ReadPrimitive<uint>();
if (MetadataVersions.V272 > version)
{
// Versions pre-27.2 had NumModifiers at 6 bits and no ValueType bit
var numModifiers = (Value >> 24) & 0b111111;
// If NumModifiers > 31, we throw here (as the old behavior isn't implemented
if (numModifiers > 31)
throw new InvalidOperationException(
"Versions pre-27.2 with a type having more than 31 modifiers are not supported yet");
// Else, we do some bit-juggling to convert the old value into the new format:
Value =
(Value & 0xFFFFFF) | // Attributes + Type
(((Value >> 24) & 0b111111) << 24) | // 5 Bits for the modifiers
(((Value >> 30) & 1) << 29) | // Shifted ByRef
(((Value >> 31) & 1) << 30) | // Shifted Pinned
0; // 0 ValueType
}
}
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
return DataUnion.Size(version, is32Bit) + sizeof(uint);
}
public static Il2CppType FromTypeEnum(Il2CppTypeEnum type)
=> new()
{
Value = (uint)type << 16
};
}

View File

@@ -0,0 +1,12 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppTypeDefinitionSizes
{
public uint InstanceSize;
public int NativeSize;
public uint StaticFieldsSize;
public uint ThreadStaticFieldsSize;
}

View File

@@ -0,0 +1,76 @@
// ReSharper disable InconsistentNaming
namespace Il2CppInspector.Next.BinaryMetadata;
public enum Il2CppTypeEnum : byte
{
Il2CPP_TYPE_END,
IL2CPP_TYPE_VOID,
IL2CPP_TYPE_BOOLEAN,
IL2CPP_TYPE_CHAR,
IL2CPP_TYPE_I1,
IL2CPP_TYPE_U1,
IL2CPP_TYPE_I2,
IL2CPP_TYPE_U2,
IL2CPP_TYPE_I4,
IL2CPP_TYPE_U4,
IL2CPP_TYPE_I8,
IL2CPP_TYPE_U8,
IL2CPP_TYPE_R4,
IL2CPP_TYPE_R8,
IL2CPP_TYPE_STRING,
IL2CPP_TYPE_PTR,
IL2CPP_TYPE_BYREF,
IL2CPP_TYPE_VALUETYPE,
IL2CPP_TYPE_CLASS,
IL2CPP_TYPE_VAR,
IL2CPP_TYPE_ARRAY,
IL2CPP_TYPE_GENERICINST,
IL2CPP_TYPE_TYPEDBYREF,
IL2CPP_TYPE_I = 0x18,
IL2CPP_TYPE_U,
IL2CPP_TYPE_FNPTR = 0x1b,
IL2CPP_TYPE_OBJECT,
IL2CPP_TYPE_SZARRAY,
IL2CPP_TYPE_MVAR,
IL2CPP_TYPE_CMOD_REQD,
IL2CPP_TYPE_CMOD_OPT,
IL2CPP_TYPE_INTERNAL,
IL2CPP_TYPE_MODIFIER = 0x40,
IL2CPP_TYPE_SENTINEL = 0x41,
IL2CPP_TYPE_PINNED = 0x45,
IL2CPP_TYPE_ENUM = 0x55,
IL2CPP_TYPE_IL2CPP_TYPE_INDEX = 0xff
}
public static class Il2CppTypeEnumExtensions
{
public static bool IsTypeDefinitionEnum(this Il2CppTypeEnum value)
=> value
is Il2CppTypeEnum.IL2CPP_TYPE_VOID
or Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN
or Il2CppTypeEnum.IL2CPP_TYPE_CHAR
or Il2CppTypeEnum.IL2CPP_TYPE_I1
or Il2CppTypeEnum.IL2CPP_TYPE_U1
or Il2CppTypeEnum.IL2CPP_TYPE_I2
or Il2CppTypeEnum.IL2CPP_TYPE_U2
or Il2CppTypeEnum.IL2CPP_TYPE_I4
or Il2CppTypeEnum.IL2CPP_TYPE_U4
or Il2CppTypeEnum.IL2CPP_TYPE_I8
or Il2CppTypeEnum.IL2CPP_TYPE_U8
or Il2CppTypeEnum.IL2CPP_TYPE_R4
or Il2CppTypeEnum.IL2CPP_TYPE_R8
or Il2CppTypeEnum.IL2CPP_TYPE_STRING
or Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE
or Il2CppTypeEnum.IL2CPP_TYPE_CLASS
or Il2CppTypeEnum.IL2CPP_TYPE_I
or Il2CppTypeEnum.IL2CPP_TYPE_U
or Il2CppTypeEnum.IL2CPP_TYPE_OBJECT
or Il2CppTypeEnum.IL2CPP_TYPE_TYPEDBYREF;
public static bool IsGenericParameterEnum(this Il2CppTypeEnum value)
=> value
is Il2CppTypeEnum.IL2CPP_TYPE_VAR
or Il2CppTypeEnum.IL2CPP_TYPE_MVAR;
}

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.BinaryMetadata;
[VersionedStruct]
public partial record struct Il2CppWindowsRuntimeFactoryTableEntry
{
public Pointer<Il2CppType> Type;
public Il2CppMethodPointer CreateFactoryFunction;
}

View File

@@ -0,0 +1,147 @@
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using NoisyCowStudios.Bin2Object;
using VersionedSerialization;
namespace Il2CppInspector.Next;
public class BinaryObjectStreamReader : BinaryObjectStream, IReader
{
public new StructVersion Version
{
get => _version;
set
{
_version = value;
base.Version = _version.AsDouble;
}
}
private StructVersion _version;
public virtual int Bits { get; set; }
public bool Is32Bit => Bits == 32;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TTo Cast<TFrom, TTo>(in TFrom from) => Unsafe.As<TFrom, TTo>(ref Unsafe.AsRef(in from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private T ReadInternal<T>() where T : unmanaged
{
var size = Unsafe.SizeOf<T>();
var value = MemoryMarshal.Read<T>(ReadBytes(size));
return value;
}
public T ReadPrimitive<T>() where T : unmanaged
{
if (typeof(T) == typeof(sbyte))
return Cast<byte, T>(ReadByte());
if (typeof(T) == typeof(short))
return Cast<short, T>(ReadInt16());
if (typeof(T) == typeof(int))
return Cast<int, T>(ReadInt32());
if (typeof(T) == typeof(long))
return Cast<long, T>(ReadInt64());
if (typeof(T) == typeof(byte))
return Cast<byte, T>(ReadByte());
if (typeof(T) == typeof(ushort))
return Cast<ushort, T>(ReadUInt16());
if (typeof(T) == typeof(uint))
return Cast<uint, T>(ReadUInt32());
if (typeof(T) == typeof(ulong))
return Cast<ulong, T>(ReadUInt64());
return ReadInternal<T>();
}
public ImmutableArray<T> ReadPrimitiveArray<T>(long count) where T : unmanaged
{
var array = ImmutableArray.CreateBuilder<T>(checked((int)count));
for (long i = 0; i < count; i++)
array.Add(ReadPrimitive<T>());
return array.MoveToImmutable();
}
public T ReadVersionedObject<T>() where T : IReadable, new() => ReadVersionedObject<T>(Version);
public T ReadVersionedObject<T>(in StructVersion version = default) where T : IReadable, new()
{
var obj = new T();
var a = this;
obj.Read(ref a, in version);
return obj;
}
public ImmutableArray<T> ReadVersionedObjectArray<T>(long count) where T : IReadable, new() => ReadVersionedObjectArray<T>(count, Version);
public ImmutableArray<T> ReadVersionedObjectArray<T>(long count, in StructVersion version = default) where T : IReadable, new()
{
var array = ImmutableArray.CreateBuilder<T>(checked((int)count));
for (long i = 0; i < count; i++)
array.Add(ReadVersionedObject<T>(in version));
return array.MoveToImmutable();
}
public long ReadNInt()
=> Is32Bit ? ReadPrimitive<int>() : ReadPrimitive<long>();
public ulong ReadNUInt()
=> Is32Bit ? ReadPrimitive<uint>() : ReadPrimitive<ulong>();
public string ReadString() => ReadNullTerminatedString();
public new ReadOnlySpan<byte> ReadBytes(int length)
{
return base.ReadBytes(length);
}
public void Align(int alignment = 0)
{
if (alignment == 0)
alignment = Is32Bit ? 4 : 8;
var rem = Position % alignment;
if (rem != 0)
Position += alignment - rem;
}
public TType ReadPrimitive<TType>(long addr) where TType : unmanaged
{
Position = addr;
return ReadPrimitive<TType>();
}
public ImmutableArray<TType> ReadPrimitiveArray<TType>(long addr, long count) where TType : unmanaged
{
Position = addr;
return ReadPrimitiveArray<TType>(count);
}
public TType ReadVersionedObject<TType>(long addr) where TType : IReadable, new()
{
Position = addr;
return ReadVersionedObject<TType>(Version);
}
public ImmutableArray<TType> ReadVersionedObjectArray<TType>(long addr, long count) where TType : IReadable, new()
{
Position = addr;
return ReadVersionedObjectArray<TType>(count, Version);
}
public void Skip(int count)
{
Position += count;
}
}

View File

@@ -0,0 +1,37 @@
using System.Runtime.InteropServices;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using ImageIndex = int;
[VersionedStruct]
[StructLayout(LayoutKind.Explicit)]
public partial record struct Il2CppAssemblyDefinition
{
[FieldOffset(20)]
[VersionCondition(LessThan = "15.0")]
public Il2CppAssemblyNameDefinition LegacyAname;
[FieldOffset(0)]
public ImageIndex ImageIndex;
[FieldOffset(4)]
[VersionCondition(GreaterThan = "24.1")]
public uint Token;
[FieldOffset(8)]
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex;
[FieldOffset(12)]
[VersionCondition(GreaterThan = "20.0")]
public int ReferencedAssemblyStart;
[FieldOffset(16)]
[VersionCondition(GreaterThan = "20.0")]
public int ReferencedAssemblyCount;
[FieldOffset(20)]
public Il2CppAssemblyNameDefinition Aname;
}

View File

@@ -0,0 +1,64 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
[InlineArray(PublicKeyLength)]
public struct PublicKeyToken
{
private const int PublicKeyLength = 8;
private byte _value;
}
[VersionedStruct]
[StructLayout(LayoutKind.Explicit)]
public partial record struct Il2CppAssemblyNameDefinition
{
[FieldOffset(0)]
public StringIndex NameIndex;
[FieldOffset(4)]
public StringIndex CultureIndex;
[FieldOffset(8)]
[VersionCondition(LessThan = "24.3")]
public int HashValueIndex;
[FieldOffset(12)]
public StringIndex PublicKeyIndex;
[FieldOffset(44)]
[VersionCondition(LessThan = "15.0")]
[CustomSerialization("reader.ReadPrimitive<PublicKeyToken>();", "8")]
private PublicKeyToken _legacyPublicKeyToken;
[FieldOffset(16)]
public AssemblyHashAlgorithm HashAlg;
[FieldOffset(20)]
public int HashLen;
[FieldOffset(24)]
public AssemblyNameFlags Flags;
[FieldOffset(28)]
public int Major;
[FieldOffset(32)]
public int Minor;
[FieldOffset(36)]
public int Build;
[FieldOffset(40)]
public int Revision;
[FieldOffset(44)]
[CustomSerialization("reader.ReadPrimitive<PublicKeyToken>();", "8")]
public PublicKeyToken PublicKeyToken;
}

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
[VersionedStruct]
public partial record struct Il2CppCustomAttributeDataRange
{
public uint Token { get; private set; }
public uint StartOffset { get; private set; }
}

View File

@@ -0,0 +1,13 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
[VersionedStruct]
public partial record struct Il2CppCustomAttributeTypeRange
{
[VersionCondition(GreaterThan = "24.1")]
public uint Token { get; private set; }
public int Start { get; private set; }
public int Count { get; private set; }
}

View File

@@ -0,0 +1,24 @@
namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
using TypeIndex = int;
using MethodIndex = int;
using VersionedSerialization.Attributes;
[VersionedStruct]
public partial record struct Il2CppEventDefinition
{
public StringIndex NameIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
public MethodIndex Add { get; private set; }
public MethodIndex Remove { get; private set; }
public MethodIndex Raise { get; private set; }
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public uint Token { get; private set; }
public readonly bool IsValid => NameIndex != 0;
}

View File

@@ -0,0 +1,15 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using FieldIndex = int;
using TypeIndex = int;
using DefaultValueDataIndex = int;
[VersionedStruct]
public partial record struct Il2CppFieldDefaultValue
{
public FieldIndex FieldIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
public DefaultValueDataIndex DataIndex { get; private set; }
}

View File

@@ -0,0 +1,20 @@
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
using StringIndex = int;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppFieldDefinition
{
public StringIndex NameIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public uint Token { get; private set; }
public readonly bool IsValid => NameIndex != 0;
}

View File

@@ -0,0 +1,13 @@
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
using FieldIndex = int;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppFieldMarshaledSize
{
public FieldIndex FieldIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
public int MarshaledSize { get; private set; }
}

View File

@@ -0,0 +1,13 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using FieldIndex = int;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppFieldRef
{
public TypeIndex TypeIndex { get; private set; }
public FieldIndex FieldIndex { get; private set; }
}

View File

@@ -0,0 +1,13 @@
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
using GenericParameterIndex = int;
[VersionedStruct]
public partial record struct Il2CppGenericContainer
{
public int OwnerIndex { get; private set; }
public int TypeArgc { get; private set; }
public int IsMethod { get; private set; }
public GenericParameterIndex GenericParameterStart { get; private set; }
}

View File

@@ -0,0 +1,21 @@
using System.Reflection;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using GenericContainerIndex = int;
using StringIndex = int;
using GenericParameterConstraintIndex = short;
[VersionedStruct]
public partial record struct Il2CppGenericParameter
{
public GenericContainerIndex OwnerIndex { get; private set; }
public StringIndex NameIndex { get; private set; }
public GenericParameterConstraintIndex ConstraintsStart { get; private set; }
public short ConstraintsCount { get; private set; }
public ushort Num { get; private set; }
public ushort Flags { get; private set; }
public readonly GenericParameterAttributes Attributes => (GenericParameterAttributes)Flags;
}

View File

@@ -0,0 +1,182 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
// Unity 4.6.1p5 - first release, no global-metadata.dat
// Unity 5.2.0f3 -> v15
// Unity 5.3.0f4 -> v16
// Unity 5.3.2f1 -> v19
// Unity 5.3.3f1 -> v20
// Unity 5.3.5f1 -> v21
// Unity 5.5.0f3 -> v22
// Unity 5.6.0f3 -> v23
// Unity 2017.1.0f3 -> v24
// Unity 2018.3.0f2 -> v24.1
// Unity 2019.1.0f2 -> v24.2
// Unity 2019.3.7f1 -> v24.3
// Unity 2019.4.15f1 -> v24.4
// Unity 2019.4.21f1 -> v24.5
// Unity 2020.1.0f1 -> v24.3
// Unity 2020.1.11f1 -> v24.4
// Unity 2020.2.0f1 -> v27
// Unity 2020.2.4f1 -> v27.1
// Unity 2021.1.0f1 -> v27.2
// https://unity3d.com/get-unity/download/archive
// Metadata version is written at the end of Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata or WriteMetadata (Unity.IL2CPP.dll)
[VersionedStruct]
public partial record struct Il2CppGlobalMetadataHeader
{
public int Sanity { get; private set; }
public int Version { get; private set; }
public int StringLiteralOffset { get; private set; }
public int StringLiteralSize { get; private set; }
public int StringLiteralDataOffset { get; private set; }
public int StringLiteralDataSize { get; private set; }
public int StringOffset { get; private set; }
public int StringSize { get; private set; }
public int EventsOffset { get; private set; }
public int EventsSize { get; private set; }
public int PropertiesOffset { get; private set; }
public int PropertiesSize { get; private set; }
public int MethodsOffset { get; private set; }
public int MethodsSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesSize { get; private set; }
public int FieldDefaultValuesOffset { get; private set; }
public int FieldDefaultValuesSize { get; private set; }
public int FieldAndParameterDefaultValueDataOffset { get; private set; }
public int FieldAndParameterDefaultValueDataSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public int FieldMarshaledSizesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public int FieldMarshaledSizesSize { get; private set; }
public int ParametersOffset { get; private set; }
public int ParametersSize { get; private set; }
public int FieldsOffset { get; private set; }
public int FieldsSize { get; private set; }
public int GenericParametersOffset { get; private set; }
public int GenericParametersSize { get; private set; }
public int GenericParameterConstraintsOffset { get; private set; }
public int GenericParameterConstraintsSize { get; private set; }
public int GenericContainersOffset { get; private set; }
public int GenericContainersSize { get; private set; }
public int NestedTypesOffset { get; private set; }
public int NestedTypesSize { get; private set; }
public int InterfacesOffset { get; private set; }
public int InterfacesSize { get; private set; }
public int VTableMethodsOffset { get; private set; }
public int VTableMethodsSize { get; private set; }
public int InterfaceOffsetsOffset { get; private set; }
public int InterfaceOffsetsSize { get; private set; }
public int TypeDefinitionsOffset { get; private set; }
public int TypeDefinitionsSize { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int RgctxEntriesOffset { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int RgctxEntriesCount { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public int ImagesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public int ImagesSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public int AssembliesOffset { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public int AssembliesSize { get; private set; }
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
public int MetadataUsageListsOffset { get; private set; }
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
public int MetadataUsageListsCount { get; private set; }
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
public int MetadataUsagePairsOffset { get; private set; }
[VersionCondition(GreaterThan = "19.0", LessThan = "24.5")]
public int MetadataUsagePairsCount { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public int FieldRefsOffset { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public int FieldRefsSize { get; private set; }
[VersionCondition(GreaterThan = "20.0")]
public int ReferencedAssembliesOffset { get; private set; }
[VersionCondition(GreaterThan = "20.0")]
public int ReferencedAssembliesSize { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
public int AttributesInfoOffset { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
public int AttributesInfoCount { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
public int AttributesTypesOffset { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "27.2")]
public int AttributesTypesCount { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
public int AttributeDataOffset { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
public int AttributeDataSize { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
public int AttributeDataRangeOffset { get; private set; }
[VersionCondition(GreaterThan = "29.0")]
public int AttributeDataRangeSize { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
public int UnresolvedIndirectCallParameterTypesOffset { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
public int UnresolvedIndirectCallParameterTypesSize { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
public int UnresolvedIndirectCallParameterRangesOffset { get; private set; }
[VersionCondition(GreaterThan = "22.0")]
public int UnresolvedIndirectCallParameterRangesSize { get; private set; }
[VersionCondition(GreaterThan = "23.0")]
public int WindowsRuntimeTypeNamesOffset { get; private set; }
[VersionCondition(GreaterThan = "23.0")]
public int WindowsRuntimeTypeNamesSize { get; private set; }
[VersionCondition(GreaterThan = "27.0")]
public int WindowsRuntimeStringsOffset { get; private set; }
[VersionCondition(GreaterThan = "27.0")]
public int WindowsRuntimeStringsSize { get; private set; }
[VersionCondition(GreaterThan = "24.0")]
public int ExportedTypeDefinitionsOffset { get; private set; }
[VersionCondition(GreaterThan = "24.0")]
public int ExportedTypeDefinitionsSize { get; private set; }
public const int ExpectedSanity = unchecked((int)0xFAB11BAF);
public readonly bool SanityValid => Sanity == ExpectedSanity;
}

View File

@@ -0,0 +1,37 @@
namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
using AssemblyIndex = int;
using TypeDefinitionIndex = int;
using MethodIndex = int;
using CustomAttributeIndex = int;
using VersionedSerialization.Attributes;
[VersionedStruct]
public partial record struct Il2CppImageDefinition
{
public StringIndex NameIndex { get; private set; }
public AssemblyIndex AssemblyIndex { get; private set; }
public TypeDefinitionIndex TypeStart { get; private set; }
public uint TypeCount { get; private set; }
[VersionCondition(GreaterThan = "24.0")]
public TypeDefinitionIndex ExportedTypeStart { get; private set; }
[VersionCondition(GreaterThan = "24.0")]
public uint ExportedTypeCount { get; private set; }
public MethodIndex EntryPointIndex { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public uint Token { get; private set; }
[VersionCondition(GreaterThan = "24.1")]
public CustomAttributeIndex CustomAttributeStart { get; private set; }
[VersionCondition(GreaterThan = "24.1")]
public uint CustomAttributeCount { get; private set; }
public readonly bool IsValid => NameIndex != 0;
}

View File

@@ -0,0 +1,12 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppInterfaceOffsetPair
{
public TypeIndex InterfaceTypeIndex { get; private set; }
public int Offset { get; private set; }
}

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
[VersionedStruct]
public partial record struct Il2CppMetadataRange
{
public int Start { get; private set; }
public int Length { get; private set; }
}

View File

@@ -0,0 +1,64 @@
using System.Diagnostics;
using VersionedSerialization;
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
using EncodedMethodIndex = uint;
[VersionedStruct]
public partial record struct Il2CppMetadataUsage
{
private const uint TypeMask = 0b111u << 29;
private const uint InflatedMask = 0b1;
private const uint IndexMask = ~(TypeMask | InflatedMask);
public readonly Il2CppMetadataUsageType Type => (Il2CppMetadataUsageType)((EncodedValue & TypeMask) >> 29);
public readonly uint Index => (EncodedValue & IndexMask) >> 1;
public readonly bool Inflated => (EncodedValue & InflatedMask) == 1;
public EncodedMethodIndex EncodedValue;
public static Il2CppMetadataUsage FromValue(in StructVersion version, uint encodedValue)
{
if (version >= MetadataVersions.V270)
{
return new Il2CppMetadataUsage
{
EncodedValue = encodedValue
};
}
if (version >= MetadataVersions.V190)
{
// Below v27 we need to fake the 'inflated' flag, so shift the value by one
var type = (encodedValue & TypeMask) >> 29;
var value = encodedValue & (IndexMask | 1);
Debug.Assert((value & 0x10000000) == 0);
return new Il2CppMetadataUsage
{
EncodedValue = (type << 29) | (value << 1)
};
}
/* These encoded indices appear only in vtables, and are decoded by IsGenericMethodIndex/GetDecodedMethodIndex */
var methodType = (encodedValue >> 31) != 0
? Il2CppMetadataUsageType.MethodRef
: Il2CppMetadataUsageType.MethodDef;
var index = encodedValue & 0x7FFFFFFF;
Debug.Assert((index & 0x60000000) == 0);
return new Il2CppMetadataUsage
{
EncodedValue = ((uint)methodType << 29) | (index << 1)
};
}
public readonly override string ToString()
{
return $"{Type} @ 0x{Index:X}";
}
}

View File

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

View File

@@ -0,0 +1,10 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
[VersionedStruct]
public partial record struct Il2CppMetadataUsagePair
{
public uint DestinationIndex { get; private set; }
public uint EncodedSourceIndex { get; private set; }
}

View File

@@ -0,0 +1,13 @@
namespace Il2CppInspector.Next.Metadata;
public enum Il2CppMetadataUsageType
{
Invalid = 0b000,
TypeInfo = 0b001,
Il2CppType = 0b010,
MethodDef = 0b011,
FieldInfo = 0b100,
StringLiteral = 0b101,
MethodRef = 0b110,
FieldRva = 0b111,
}

View File

@@ -0,0 +1,56 @@
using System.Reflection;
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
{
public StringIndex NameIndex { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
public TypeDefinitionIndex DeclaringType { get; private set; }
public TypeIndex ReturnType { get; private set; }
[VersionCondition(GreaterThan = "31.0")]
public uint ReturnParameterToken { get; private set; }
public ParameterIndex ParameterStart { get; private set; }
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex { get; private set; }
public GenericContainerIndex GenericContainerIndex { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int MethodIndex { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int InvokerIndex { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int ReversePInvokeWrapperIndex { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int RgctxStartIndex { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int RgctxCount { get; private set; }
public uint Token { get; private set; }
public ushort Flags { get; private set; }
public ushort ImplFlags { get; private set; }
public ushort Slot { get; private set; }
public ushort ParameterCount { get; private set; }
public readonly MethodAttributes Attributes => (MethodAttributes)Flags;
public readonly MethodImplAttributes ImplAttributes => (MethodImplAttributes)ImplFlags;
public readonly bool IsValid => NameIndex != 0;
}

View File

@@ -0,0 +1,14 @@
namespace Il2CppInspector.Next.Metadata;
using ParameterIndex = int;
using TypeIndex = int;
using DefaultValueDataIndex = int;
using VersionedSerialization.Attributes;
[VersionedStruct]
public partial record struct Il2CppParameterDefaultValue
{
public ParameterIndex ParameterIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
public DefaultValueDataIndex DataIndex { get; private set; }
}

View File

@@ -0,0 +1,19 @@
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
using StringIndex = int;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppParameterDefinition
{
public StringIndex NameIndex { get; private set; }
public uint Token { get; private set; }
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
public readonly bool IsValid => NameIndex != 0;
}

View File

@@ -0,0 +1,24 @@
using System.Reflection;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
using MethodIndex = int;
[VersionedStruct]
public partial record struct Il2CppPropertyDefinition
{
public StringIndex NameIndex { get; private set; }
public MethodIndex Get { get; private set; }
public MethodIndex Set { get; private set; }
public PropertyAttributes Attrs { get; private set; }
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public uint Token { get; private set; }
public readonly bool IsValid => NameIndex != 0;
}

View File

@@ -0,0 +1,12 @@
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
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

@@ -0,0 +1,91 @@
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;
using PropertyIndex = int;
using NestedTypeIndex = int;
using InterfacesIndex = int;
using VTableIndex = int;
[VersionedStruct]
public partial record struct Il2CppTypeDefinition
{
public const TypeIndex InvalidTypeIndex = -1;
public StringIndex NameIndex { get; private set; }
public StringIndex NamespaceIndex { get; private set; }
[VersionCondition(LessThan = "24.0")]
public int CustomAttributeIndex { get; private set; }
public TypeIndex ByValTypeIndex { get; private set; }
[VersionCondition(LessThan = "24.5")]
public TypeIndex ByRefTypeIndex { get; private set; }
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")]
public int RgctxStartIndex { get; private set; }
[VersionCondition(LessThan = "24.1")]
public int RgctxCount { get; private set; }
public GenericContainerIndex GenericContainerIndex { get; private set; }
[VersionCondition(LessThan = "22.0")]
public int ReversePInvokeWrapperIndex { get; private set; }
[VersionCondition(LessThan = "22.0")]
public int MarshalingFunctionsIndex { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "22.0")]
public int CcwFunctionIndex { get; private set; }
[VersionCondition(GreaterThan = "21.0", LessThan = "22.0")]
public int GuidIndex { get; private set; }
public TypeAttributes Flags { get; private set; }
public FieldIndex FieldIndex { get; private set; }
public MethodIndex MethodIndex { get; private set; }
public EventIndex EventIndex { get; private set; }
public PropertyIndex PropertyIndex { get; private set; }
public NestedTypeIndex NestedTypeIndex { get; private set; }
public InterfacesIndex InterfacesIndex { get; private set; }
public VTableIndex VTableIndex { get; private set; }
public InterfacesIndex InterfaceOffsetsStart { get; private set; }
public ushort MethodCount { get; private set; }
public ushort PropertyCount { get; private set; }
public ushort FieldCount { get; private set; }
public ushort EventCount { get; private set; }
public ushort NestedTypeCount { get; private set; }
public ushort VTableCount { get; private set; }
public ushort InterfacesCount { get; private set; }
public ushort InterfaceOffsetsCount { get; private set; }
public Il2CppTypeDefinitionBitfield Bitfield { get; private set; }
[VersionCondition(GreaterThan = "19.0")]
public uint Token { get; private set; }
public readonly bool IsValid => NameIndex != 0;
public int GetEnumElementTypeIndex(StructVersion version)
=> version >= MetadataVersions.V350
? ParentIndex
: ElementTypeIndex;
}

View File

@@ -0,0 +1,21 @@
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
[VersionedStruct]
public partial record struct Il2CppTypeDefinitionBitfield
{
private uint _value;
public bool ValueType => ((_value >> 0) & 1) == 1;
public bool EnumType => ((_value >> 1) & 1) == 1;
public bool HasFinalize => ((_value >> 2) & 1) == 1;
public bool HasCctor => ((_value >> 3) & 1) == 1;
public bool IsBlittable => ((_value >> 4) & 1) == 1;
public bool IsImportOrWindowsRuntime => ((_value >> 5) & 1) == 1;
public PackingSize PackingSize => (PackingSize)((_value >> 6) & 0b1111);
public bool DefaultPackingSize => ((_value >> 10) & 1) == 1;
public bool DefaultClassSize => ((_value >> 11) & 1) == 1;
public PackingSize ClassSize => (PackingSize)((_value >> 12) & 0b1111);
public bool IsByRefLike => ((_value >> 13) & 1) == 1;
}

View File

@@ -0,0 +1,12 @@
namespace Il2CppInspector.Next.Metadata;
using VersionedSerialization.Attributes;
using StringIndex = int;
using TypeIndex = int;
[VersionedStruct]
public partial record struct Il2CppWindowsRuntimeTypeNamePair
{
public StringIndex NameIndex { get; private set; }
public TypeIndex TypeIndex { get; private set; }
}

View File

@@ -0,0 +1,14 @@
namespace Il2CppInspector.Next.Metadata;
public enum PackingSize
{
Zero,
One,
Two,
Four,
Eight,
Sixteen,
ThirtyTwo,
SixtyFour,
OneHundredTwentyEight
}

View File

@@ -0,0 +1,34 @@
using VersionedSerialization;
namespace Il2CppInspector.Next;
public static class MetadataVersions
{
public static readonly StructVersion V160 = new(16);
public static readonly StructVersion V190 = new(19);
public static readonly StructVersion V210 = new(21);
public static readonly StructVersion V220 = new(22);
public static readonly StructVersion V240 = new(24);
public static readonly StructVersion V241 = new(24, 1);
public static readonly StructVersion V242 = new(24, 2);
public static readonly StructVersion V243 = new(24, 3);
public static readonly StructVersion V244 = new(24, 4);
public static readonly StructVersion V245 = new(24, 5);
public static readonly StructVersion V270 = new(27);
public static readonly StructVersion V271 = new(27, 1);
public static readonly StructVersion V272 = new(27, 2);
// These two versions have two variations:
public static readonly StructVersion V290 = new(29);
public static readonly StructVersion V310 = new(31);
// No tag - 29.0/31.0
public static readonly string Tag2022 = "2022"; // 29.1/31.1
// Unity 6000.3.0a2
public static readonly StructVersion V350 = new(35);
}

View File

@@ -0,0 +1,60 @@
using System.Collections.Immutable;
using VersionedSerialization;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next;
public struct Pointer<T>(ulong value = 0) : IReadable, IEquatable<Pointer<T>> where T : struct, IReadable
{
[NativeInteger]
private ulong _value = value;
public readonly ulong PointerValue => _value;
public readonly bool Null => _value == 0;
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
_value = reader.ReadNUInt();
}
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
return is32Bit ? 4 : 8;
}
public readonly T Read(ref SpanReader reader, in StructVersion version)
{
reader.Offset = (int)PointerValue;
return reader.ReadVersionedObject<T>(version);
}
public readonly ImmutableArray<T> ReadArray(ref SpanReader reader, long count, in StructVersion version)
{
reader.Offset = (int)PointerValue;
return reader.ReadVersionedObjectArray<T>(count, version);
}
public static implicit operator Pointer<T>(ulong value) => new(value);
public static implicit operator ulong(Pointer<T> ptr) => ptr.PointerValue;
#region Equality operators + ToString
public static bool operator ==(Pointer<T> left, Pointer<T> right)
=> left._value == right._value;
public static bool operator !=(Pointer<T> left, Pointer<T> right)
=> !(left == right);
public readonly override bool Equals(object? obj)
=> obj is Pointer<T> other && Equals(other);
public readonly bool Equals(Pointer<T> other)
=> this == other;
public readonly override int GetHashCode()
=> HashCode.Combine(_value);
public readonly override string ToString() => $"0x{_value:X} <{typeof(T).Name}>";
#endregion
}

View File

@@ -0,0 +1,60 @@
using System.Collections.Immutable;
using VersionedSerialization;
using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next;
public struct PrimitivePointer<T>(ulong value = 0) : IReadable, IEquatable<PrimitivePointer<T>> where T : unmanaged
{
[NativeInteger]
private ulong _value = value;
public readonly ulong PointerValue => _value;
public readonly bool Null => _value == 0;
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
_value = reader.ReadNUInt();
}
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
return is32Bit ? 4 : 8;
}
public readonly T Read(ref SpanReader reader)
{
reader.Offset = (int)PointerValue;
return reader.ReadPrimitive<T>();
}
public readonly ImmutableArray<T> ReadArray(ref SpanReader reader, long count)
{
reader.Offset = (int)PointerValue;
return reader.ReadPrimitiveArray<T>(count);
}
public static implicit operator PrimitivePointer<T>(ulong value) => new(value);
public static implicit operator ulong(PrimitivePointer<T> ptr) => ptr.PointerValue;
#region Equality operators + ToString
public static bool operator ==(PrimitivePointer<T> left, PrimitivePointer<T> right)
=> left._value == right._value;
public static bool operator !=(PrimitivePointer<T> left, PrimitivePointer<T> right)
=> !(left == right);
public readonly override bool Equals(object? obj)
=> obj is PrimitivePointer<T> other && Equals(other);
public readonly bool Equals(PrimitivePointer<T> other)
=> this == other;
public readonly override int GetHashCode()
=> HashCode.Combine(_value);
public readonly override string ToString() => $"0x{_value:X} <{typeof(T).Name}>";
#endregion
}

View File

@@ -12,6 +12,7 @@ using System.IO;
using System.Linq;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using Il2CppInspector.Next;
using Il2CppInspector.Reflection;
namespace Il2CppInspector.Outputs
@@ -186,7 +187,7 @@ namespace Il2CppInspector.Outputs
};
if (mType.IsExplicitLayout || mType.IsSequentialLayout)
mType.ClassLayout = new ClassLayoutUser(1, (uint)type.Sizes.nativeSize);
mType.ClassLayout = new ClassLayoutUser(1, (uint)type.Sizes.NativeSize);
// Add nested types
foreach (var nestedType in type.DeclaredNestedTypes)
@@ -241,7 +242,7 @@ namespace Il2CppInspector.Outputs
AddMethod(module, mType, method);
// Add token attribute
if (type.Definition != null)
if (type.Definition.IsValid)
mType.AddAttribute(module, tokenAttribute, ("Token", $"0x{type.MetadataToken:X8}"));
// Add custom attribute attributes
@@ -269,7 +270,7 @@ namespace Il2CppInspector.Outputs
if (field.HasFieldRVA) {
// Attempt to get field size
var fieldSize = field.FieldType.Sizes.nativeSize;
var fieldSize = field.FieldType.Sizes.NativeSize;
var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, fieldSize);
mField.InitialValue = preview;
@@ -310,7 +311,7 @@ namespace Il2CppInspector.Outputs
// Add token attribute
// Generic properties and constructed properties (from disperate get/set methods) have no definition
if (prop.Definition != null)
if (prop.Definition.IsValid)
mProp.AddAttribute(module, tokenAttribute, ("Token", $"0x{prop.MetadataToken:X8}"));
// Add custom attribute attributes
@@ -429,11 +430,11 @@ 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)
args.Add(("Slot", method.Definition.slot.ToString()));
if (method.Definition.Slot != ushort.MaxValue)
args.Add(("Slot", method.Definition.Slot.ToString()));
mMethod.AddAttribute(module, addressAttribute, args.ToArray());
}
@@ -469,7 +470,7 @@ namespace Il2CppInspector.Outputs
return def.AddAttribute(module, attributeAttribute,
("Name", ca.AttributeType.Name),
("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start)))
("Offset", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
);
}
@@ -591,7 +592,7 @@ namespace Il2CppInspector.Outputs
// Create folder for DLLs
Directory.CreateDirectory(outputPath);
if (model.Package.Version >= 29)
if (model.Package.Version >= MetadataVersions.V290)
{
// We can now apply all attributes directly.
directApplyAttributes = model.TypesByDefinitionIndex
@@ -648,7 +649,7 @@ namespace Il2CppInspector.Outputs
AddCustomAttribute(module, module.Assembly, ca);
// Add token attributes
module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.token:X8}"));
module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.Token:X8}"));
module.Assembly.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.MetadataToken:X8}"));
if (types.TryGetValue(module, out var shallowTypes))

View File

@@ -353,7 +353,7 @@ namespace Il2CppInspector.Outputs
foreach (var asm in assemblies) {
text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName}");
if (!SuppressMetadata)
text.Append($" - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}");
text.Append($" - Types {asm.ImageDefinition.TypeStart}-{asm.ImageDefinition.TypeStart + asm.ImageDefinition.TypeCount - 1}");
text.AppendLine();
// Assembly-level attributes
@@ -426,7 +426,7 @@ namespace Il2CppInspector.Outputs
sb.Append($" // Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}");
// For static array initializers, output metadata address and preview
if (field.HasFieldRVA && !SuppressMetadata) {
var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.nativeSize);
var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.NativeSize);
sb.Append($" // Static value (base64): {Convert.ToBase64String(preview)} - Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}");
}
sb.Append("\n");

View File

@@ -38,14 +38,20 @@ namespace Il2CppInspector.Outputs
using var fs = new FileStream(typeHeaderFile, FileMode.Create);
_writer = new StreamWriter(fs, Encoding.ASCII);
const string decompilerIfDef = "#if !defined(_GHIDRA_) && !defined(_IDA_) && !defined(_IDACLANG_)";
using (_writer)
{
writeHeader();
// Write primitive type definitions for when we're not including other headers
writeCode($"""
#if defined(_IDACLANG_) || defined(_BINARYNINJA_)
#define IS_LIBCLANG_DECOMPILER
#endif
#if defined(_GHIDRA_) || defined(_IDA_) || defined(IS_LIBCLANG_DECOMPILER)
#define IS_DECOMPILER
#endif
#if defined(_GHIDRA_) || defined(_IDA_)
typedef unsigned __int8 uint8_t;
typedef unsigned __int16 uint16_t;
@@ -57,7 +63,7 @@ namespace Il2CppInspector.Outputs
typedef __int64 int64_t;
#endif
#ifdef _IDACLANG_
#if defined(IS_LIBCLANG_DECOMPILER)
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
@@ -66,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_) || defined(_IDACLANG_)
#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
{decompilerIfDef}
#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));
@@ -93,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)
@@ -114,17 +123,20 @@ namespace Il2CppInspector.Outputs
}
// C does not support namespaces
writeCode($"{decompilerIfDef}");
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($"{decompilerIfDef}");
writeCode("#ifndef IS_DECOMPILER");
writeCode("}");
writeCode("#endif");
}
@@ -248,7 +260,7 @@ namespace Il2CppInspector.Outputs
using (_writer)
{
writeHeader();
writeCode($"#define __IL2CPP_METADATA_VERSION {_model.Package.Version * 10:F0}");
writeCode($"#define __IL2CPP_METADATA_VERSION {_model.Package.Version.Major * 10 + _model.Package.Version.Minor * 10:F0}");
}
// Write boilerplate code
@@ -305,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

@@ -8,6 +8,7 @@ using System.IO;
using System.Text.Json;
using Il2CppInspector.Reflection;
using Il2CppInspector.Model;
using Il2CppInspector.Next;
namespace Il2CppInspector.Outputs
{
@@ -182,8 +183,8 @@ namespace Il2CppInspector.Outputs
// TODO: In the future, add data ranges for the entire IL2CPP metadata tree
writeArray("arrayMetadata", () => {
if (model.Package.Version >= 24.2) {
writeObject(() => writeTypedArray(binary.CodeRegistration.pcodeGenModules, binary.Modules.Count, "struct Il2CppCodeGenModule *", "g_CodeGenModules"));
if (model.Package.Version >= MetadataVersions.V242) {
writeObject(() => writeTypedArray(binary.CodeRegistration.CodeGenModules, binary.Modules.Count, "struct Il2CppCodeGenModule *", "g_CodeGenModules"));
}
}, "IL2CPP Array Metadata");
}
@@ -216,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

@@ -22,7 +22,9 @@ namespace Il2CppInspector.Outputs
public static IEnumerable<string> GetAvailableTargets() {
var ns = typeof(PythonScript).Namespace + ".ScriptResources.Targets";
var res = ResourceHelper.GetNamesForNamespace(ns);
return res.Select(s => Path.GetFileNameWithoutExtension(s.Substring(ns.Length + 1))).OrderBy(s => s);
return res
.Select(s => Path.GetFileNameWithoutExtension(s[(ns.Length + 1)..]))
.OrderBy(s => s);
}
// Output script file
@@ -52,12 +54,11 @@ namespace Il2CppInspector.Outputs
var jsonMetadataRelativePath = getRelativePath(outputFile, jsonMetadataFile);
var ns = typeof(PythonScript).Namespace + ".ScriptResources";
var preamble = ResourceHelper.GetText(ns + ".shared-preamble.py");
var main = ResourceHelper.GetText(ns + ".shared-main.py");
var api = ResourceHelper.GetText($"{ns}.Targets.{target}.py");
var ns = $"{typeof(PythonScript).Namespace}.ScriptResources";
var baseScipt = ResourceHelper.GetText($"{ns}.shared_base.py");
var impl = ResourceHelper.GetText($"{ns}.Targets.{target}.py");
var script = string.Join("\n", new [] { preamble, api, main })
var script = string.Join("\n", baseScipt, impl)
.Replace("%SCRIPTFILENAME%", Path.GetFileName(outputFile))
.Replace("%TYPE_HEADER_RELATIVE_PATH%", typeHeaderRelativePath.ToEscapedString())
.Replace("%JSON_METADATA_RELATIVE_PATH%", jsonMetadataRelativePath.ToEscapedString())

View File

@@ -0,0 +1,330 @@
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
# from typing import Literal
# bv: BinaryView = None # type: ignore
# except:
# pass
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment: bool = True
_status: BaseStatusHandler
_view: BinaryView
_undo_id: str
_components: dict[str, Component]
_type_cache: dict[str, Type]
_function_type_cache: dict[str, Type]
_address_size: int
_endianness: Literal["little", "big"]
TYPE_PARSER_OPTIONS = ["--target=x86_64-pc-linux", "-x", "c++", "-D_BINARYNINJA_=1"]
def __init__(self, status: BaseStatusHandler):
self._status = status
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 ") :]
if type in self._type_cache:
return self._type_cache[type]
if type.endswith("*"):
base_type = self._get_or_create_type(type[:-1].strip())
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._type_cache[type] = parsed
return parsed
def _parse_type_source(self, types: str, filename: str | None = None):
parsed_types, errors = TypeParser.default.parse_types_from_source(
types,
filename if filename else "types.hpp",
self._view.platform
if self._view.platform is not None
else Platform["windows-x86_64"],
self._view,
self.TYPE_PARSER_OPTIONS,
)
if parsed_types is None:
log_error("Failed to import types.")
log_error(errors)
return None
return parsed_types
def get_script_directory(self) -> str:
return CURRENT_PATH
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 = {}
self._address_size = self._view.address_size
self._endianness = (
"little" if self._view.endianness == Endianness.LittleEndian else "big"
)
self._status.update_step("Parsing header")
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f:
parsed_types = self._parse_type_source(f.read(), "il2cpp.hpp")
if parsed_types is None:
return
self._status.update_step("Importing header types", len(parsed_types.types))
def import_progress_func(progress: int, total: int):
self._status.update_progress(1)
return True
self._view.define_user_types(
[(x.name, x.type) for x in parsed_types.types], import_progress_func
)
def on_finish(self):
self._view.commit_undo_actions(self._undo_id)
self._view.set_analysis_hold(False)
self._view.update_analysis()
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)
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
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 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 set_data_comment(self, address: int, cmt: str):
self._view.set_comment_at(address, cmt)
def set_function_comment(self, address: int, cmt: str):
function = self._view.get_function_at(address)
if function is None:
return
function.comment = cmt
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
def set_function_name(self, address: int, name: str):
function = self._view.get_function_at(address)
if function is None:
return
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 update(self):
if self.was_cancelled():
raise RuntimeError("Cancelled script.")
current_time = datetime.now()
if 0.5 > (current_time - self.last_updated_time).total_seconds():
return
self.last_updated_time = current_time
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_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 update_progress(self, new_progress=1):
self.current_items += new_progress
self.update()
def was_cancelled(self):
return False
def close(self):
pass
# Entry point
class Il2CppTask(BackgroundTaskThread):
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()
Il2CppTask().start()

View File

@@ -1,106 +1,128 @@
# Ghidra-specific implementation
from ghidra.app.cmd.function import ApplyFunctionSignatureCmd
from ghidra.app.script import GhidraScriptUtil
from ghidra.app.util.cparser.C import CParserUtils
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
xrefs = currentProgram.getReferenceManager()
#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
def set_name(addr, name):
if not name.startswith("_ZN"):
createLabel(toAddr(addr), name, True)
return
cmd = DemanglerCmd(currentAddress.getAddress(hex(addr)), name)
if not cmd.applyTo(currentProgram, monitor):
print("Failed to apply demangled name to %s at %s due %s, falling back to mangled" % (name, hex(addr), cmd.getStatusMsg()))
createLabel(toAddr(addr), name, True)
class GhidraDisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment = False
def make_function(start, end = None):
addr = toAddr(start)
# Don't override existing functions
fn = getFunctionAt(addr)
if fn is None:
# Create new function if none exists
createFunction(addr, None)
def _to_address(self, value):
return toAddr(Long(value))
def make_array(addr, numItems, cppType):
if cppType.startswith('struct '):
cppType = cppType[7:]
def get_script_directory(self) -> str:
return getSourceFile().getParentFile().toString()
t = getDataTypes(cppType)[0]
a = ArrayDataType(t, numItems, t.getLength())
addr = toAddr(addr)
removeDataAt(addr)
createData(addr, a)
def on_start(self):
self.xrefs = currentProgram.getReferenceManager()
def define_code(code):
# Code declarations are not supported in Ghidra
# This only affects string literals for metadata version < 19
# TODO: Replace with creating a DataType for enums
# Check that the user has parsed the C headers first
if len(getDataTypes('Il2CppObject')) == 0:
print('STOP! You must import the generated C header file (%TYPE_HEADER_RELATIVE_PATH%) before running this script.')
print('See https://github.com/djkaty/Il2CppInspector/blob/master/README.md#adding-metadata-to-your-ghidra-workflow for instructions.')
sys.exit()
# Ghidra sets the image base for ELF to 0x100000 for some reason
# https://github.com/NationalSecurityAgency/ghidra/issues/1020
# Make sure that the base address is 0
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
if currentProgram.getExecutableFormat().endswith('(ELF)'):
currentProgram.setImageBase(self._to_address(0), True)
# Don't trigger decompiler
setAnalysisOption(currentProgram, "Call Convention ID", "false")
def on_finish(self):
pass
def define_function(self, address: int, end: int | None = None):
address = self._to_address(address)
# Don't override existing functions
fn = getFunctionAt(address)
if fn is None:
# Create new function if none exists
createFunction(address, None)
def define_data_array(self, address: int, type: str, count: int):
if type.startswith('struct '):
type = type[7:]
t = getDataTypes(type)[0]
a = ArrayDataType(t, count, t.getLength())
address = self._to_address(address)
removeDataAt(address)
createData(address, a)
def set_data_type(self, address: int, type: str):
if type.startswith('struct '):
type = type[7:]
try:
t = getDataTypes(type)[0]
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):
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(self._to_address(address), cmt)
def set_function_comment(self, address: int, cmt: str):
setPlateComment(self._to_address(address), cmt)
def set_data_name(self, address: int, name: str):
address = self._to_address(address)
if len(name) > 2000:
print("Name length exceeds 2000 characters, skipping (%s)" % name)
return
if not name.startswith("_ZN"):
createLabel(address, name, True)
return
cmd = DemanglerCmd(address, name)
if not cmd.applyTo(currentProgram, monitor):
print(f"Failed to apply demangled name to {name} at {address} due {cmd.getStatusMsg()}, falling back to mangled")
createLabel(address, name, True)
def set_function_name(self, address: int, name: str):
return self.set_data_name(address, name)
def add_cross_reference(self, from_address: int, to_address: int):
self.xrefs.addMemoryReference(self._to_address(from_address), self._to_address(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
def import_c_typedef(self, type_def: str):
# Code declarations are not supported in Ghidra
# This only affects string literals for metadata version < 19
# TODO: Replace with creating a DataType for enums
pass
class GhidraStatusHandler(BaseStatusHandler):
pass
def set_function_type(addr, sig):
make_function(addr)
typeSig = CParserUtils.parseSignature(None, currentProgram, sig)
ApplyFunctionSignatureCmd(toAddr(addr), typeSig, SourceType.USER_DEFINED, False, True).applyTo(currentProgram)
def set_type(addr, cppType):
if cppType.startswith('struct '):
cppType = cppType[7:]
try:
t = getDataTypes(cppType)[0]
addr = toAddr(addr)
removeDataAt(addr)
createData(addr, t)
except:
print("Failed to set type: %s" % cppType)
def set_comment(addr, text):
setEOLComment(toAddr(addr), text)
def set_header_comment(addr, text):
setPlateComment(toAddr(addr), text)
def script_prologue(status):
# Check that the user has parsed the C headers first
if len(getDataTypes('Il2CppObject')) == 0:
print('STOP! You must import the generated C header file (%TYPE_HEADER_RELATIVE_PATH%) before running this script.')
print('See https://github.com/djkaty/Il2CppInspector/blob/master/README.md#adding-metadata-to-your-ghidra-workflow for instructions.')
sys.exit()
# Ghidra sets the image base for ELF to 0x100000 for some reason
# https://github.com/NationalSecurityAgency/ghidra/issues/1020
# Make sure that the base address is 0
# 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)
# Don't trigger decompiler
setAnalysisOption(currentProgram, "Call Convention ID", "false")
def get_script_directory(): return getSourceFile().getParentFile().toString()
def script_epilogue(status): pass
def add_function_to_group(addr, group): pass
def add_xref(addr, to):
xrefs.addMemoryReference(currentAddress.getAddress(hex(addr)), currentAddress.getAddress(hex(to)), RefType.DATA, SourceType.USER_DEFINED, 0)
def process_string_literals(status, data):
for d in jsonData['stringLiterals']:
define_string(d)
# I don't know how to make inline strings in Ghidra
# Just revert back original impl
addr = parse_address(d)
set_name(addr, d['name'])
set_type(addr, r'struct String *')
set_comment(addr, d['string'])
status.update_progress()
class StatusHandler(BaseStatusHandler): pass
status = GhidraStatusHandler()
backend = GhidraDisassemblerInterface()
context = ScriptContext(backend, status)
context.process()

View File

@@ -8,6 +8,8 @@ import ida_nalt
import ida_ida
import ida_ua
import ida_segment
import ida_funcs
import ida_xref
try: # 7.7+
import ida_srclang
@@ -23,206 +25,206 @@ try:
except ImportError:
FOLDERS_AVAILABLE = False
cached_genflags = 0
skip_make_function = False
func_dirtree = None
is_32_bit = False
fake_segments_base = None
def script_prologue(status):
global cached_genflags, skip_make_function, func_dirtree, is_32_bit, fake_segments_base
# Disable autoanalysis
cached_genflags = ida_ida.inf_get_genflags()
ida_ida.inf_set_genflags(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"]
for lib in PROBLEMATIC_TYPELIBS:
for platform in PLATFORMS:
ida_typeinf.del_til(f"{lib}_{platform}")
# Set name mangling to GCC 3.x and display demangled as default
ida_ida.inf_set_demnames(ida_ida.DEMNAM_GCC3 | ida_ida.DEMNAM_NAME)
status.update_step('Processing Types')
if IDACLANG_AVAILABLE:
header_path = os.path.join(get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%")
ida_srclang.set_parser_argv("clang", "-target x86_64-pc-linux -x c++ -D_IDACLANG_=1") # -target required for 8.3+
ida_srclang.parse_decls_with_parser("clang", None, header_path, True)
else:
original_macros = ida_typeinf.get_c_macros()
ida_typeinf.set_c_macros(original_macros + ";_IDA_=1")
ida_typeinf.idc_parse_types(os.path.join(get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%"), ida_typeinf.PT_FILE)
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:
print(".pdata section found, skipping function boundaries")
if FOLDERS_AVAILABLE:
func_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
is_32_bit = ida_ida.inf_is_32bit_exactly()
def script_epilogue(status):
# Reenable auto-analysis
global cached_genflags
ida_ida.inf_set_genflags(cached_genflags)
# Utility methods
def set_name(addr, name):
ida_name.set_name(addr, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK | ida_name.SN_FORCE)
def make_function(start, end = None):
global skip_make_function
if skip_make_function:
return
ida_bytes.del_items(start, ida_bytes.DELIT_SIMPLE, 12) # Undefine x bytes which should hopefully be enough for the first instruction
ida_ua.create_insn(start) # Create instruction at start
if not ida_funcs.add_func(start, end if end is not None else ida_idaapi.BADADDR): # This fails if the function doesn't start with an instruction
print(f"failed to mark function {hex(start)}-{hex(end) if end is not None else '???'} as function")
TYPE_CACHE = {}
def get_type(typeName):
if typeName not in TYPE_CACHE:
info = ida_typeinf.idc_parse_decl(None, typeName, ida_typeinf.PT_RAWARGS)
if info is None:
print(f"Failed to create type {typeName}.")
return None
TYPE_CACHE[typeName] = info[1:]
return TYPE_CACHE[typeName]
#try:
# from typing import TYPE_CHECKING
# if TYPE_CHECKING:
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
# import json
# import os
# from datetime import datetime
#except:
# pass
TINFO_DEFINITE = 0x0001 # These only exist in idc for some reason, so we redefine it here
DEFAULT_TIL: "til_t" = None # type: ignore
def set_type(addr, cppType):
cppType += ';'
class IDADisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment = True
info = get_type(cppType)
if info is None:
return
_status: BaseStatusHandler
if ida_typeinf.apply_type(None, info[0], info[1], addr, TINFO_DEFINITE) is None:
print(f"set_type({hex(addr)}, {cppType}); failed!")
_type_cache: dict
_folders: list
def set_function_type(addr, sig):
set_type(addr, sig)
_function_dirtree: "ida_dirtree.dirtree_t"
_cached_genflags: int
_skip_function_creation: bool
_is_32_bit: bool
_fake_segments_base: int
def make_array(addr, numItems, cppType):
set_type(addr, cppType)
def __init__(self, status: BaseStatusHandler):
self._status = status
flags = ida_bytes.get_flags(addr)
if ida_bytes.is_struct(flags):
opinfo = ida_nalt.opinfo_t()
ida_bytes.get_opcode(opinfo, addr, 0, flags)
entrySize = ida_bytes.get_data_elsize(addr, flags, opinfo)
tid = opinfo.tid
else:
entrySize = ida_bytes.get_item_size(addr)
tid = ida_idaapi.BADADDR
self._type_cache = {}
self._folders = []
ida_bytes.create_data(addr, flags, numItems * entrySize, tid)
self._cached_genflags = 0
self._skip_function_creation = False
self._is_32_bit = False
self._fake_segments_base = 0
def define_code(code):
ida_typeinf.idc_parse_types(code)
def _get_type(self, type: str):
if type not in self._type_cache:
info = ida_typeinf.idc_parse_decl(DEFAULT_TIL, type, ida_typeinf.PT_RAWARGS)
if info is None:
print(f"Failed to create type {type}.")
return None
def set_comment(addr, comment, repeatable = True):
ida_bytes.set_cmt(addr, comment, repeatable)
self._type_cache[type] = info[1:]
def set_header_comment(addr, comment):
func = ida_funcs.get_func(addr)
if func is None:
return
return self._type_cache[type]
ida_funcs.set_func_cmt(func, comment, True)
def get_script_directory(self) -> str:
return os.path.dirname(os.path.realpath(__file__))
def get_script_directory():
return os.path.dirname(os.path.realpath(__file__))
def on_start(self):
# Disable autoanalysis
self._cached_genflags = ida_ida.inf_get_genflags()
ida_ida.inf_set_genflags(self._cached_genflags & ~ida_ida.INFFL_AUTO)
folders = []
def add_function_to_group(addr, group):
global func_dirtree, folders
return
# Unload type libraries we know to cause issues - like the c++ linux one
PLATFORMS = ["x86", "x64", "arm", "arm64"]
PROBLEMATIC_TYPELIBS = ["gnulnx"]
if not FOLDERS_AVAILABLE:
return
for lib in PROBLEMATIC_TYPELIBS:
for platform in PLATFORMS:
ida_typeinf.del_til(f"{lib}_{platform}")
if group not in folders:
folders.append(group)
func_dirtree.mkdir(group)
# Set name mangling to GCC 3.x and display demangled as default
ida_ida.inf_set_demnames(ida_ida.DEMNAM_GCC3 | ida_ida.DEMNAM_NAME)
name = ida_funcs.get_func_name(addr)
func_dirtree.rename(name, f"{group}/{name}")
self._status.update_step('Processing Types')
def add_xref(addr, to):
ida_xref.add_dref(addr, to, ida_xref.XREF_USER | ida_xref.dr_I)
if IDACLANG_AVAILABLE:
header_path = os.path.join(self.get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%")
ida_srclang.set_parser_argv("clang", "-target x86_64-pc-linux -x c++ -D_IDACLANG_=1") # -target required for 8.3+
ida_srclang.parse_decls_with_parser("clang", None, header_path, True)
else:
original_macros = ida_typeinf.get_c_macros()
ida_typeinf.set_c_macros(original_macros + ";_IDA_=1")
ida_typeinf.idc_parse_types(os.path.join(self.get_script_directory(), "%TYPE_HEADER_RELATIVE_PATH%"), ida_typeinf.PT_FILE)
ida_typeinf.set_c_macros(original_macros)
def write_string(addr, string):
encoded_string = string.encode() + b'\x00'
string_length = len(encoded_string)
ida_bytes.put_bytes(addr, encoded_string)
ida_bytes.create_strlit(addr, string_length, ida_nalt.STRTYPE_C)
# Skip make_function on Windows GameAssembly.dll files due to them predefining all functions through pdata which makes the method very slow
self._skip_function_creation = ida_segment.get_segm_by_name(".pdata") is not None
if self._skip_function_creation:
print(".pdata section found, skipping function boundaries")
def write_address(addr, value):
global is_32_bit
if FOLDERS_AVAILABLE:
self._function_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
if is_32_bit:
ida_bytes.put_dword(addr, value)
else:
ida_bytes.put_qword(addr, value)
self._is_32_bit = ida_ida.inf_is_32bit_exactly()
def create_fake_segment(name, size):
global is_32_bit
def on_finish(self):
ida_ida.inf_set_genflags(self._cached_genflags)
start = ida_ida.inf_get_max_ea()
end = start + size
def define_function(self, address: int, end: int | None = None):
if self._skip_function_creation:
return
ida_segment.add_segm(0, start, end, name, "DATA")
segment = ida_segment.get_segm_by_name(name)
segment.bitness = 1 if is_32_bit else 2
segment.perm = ida_segment.SEGPERM_READ
segment.update()
ida_bytes.del_items(address, ida_bytes.DELIT_SIMPLE, 12) # Undefine x bytes which should hopefully be enough for the first instruction
ida_ua.create_insn(address) # Create instruction at start
if not ida_funcs.add_func(address, end if end is not None else ida_idaapi.BADADDR): # This fails if the function doesn't start with an instruction
print(f"failed to mark function {hex(address)}-{hex(end) if end is not None else '???'} as function")
return start
def define_data_array(self, address: int, type: str, count: int):
self.set_data_type(address, type)
def process_string_literals(status, data):
total_string_length = 0
for d in data['stringLiterals']:
total_string_length += len(d["string"]) + 1
flags = ida_bytes.get_flags(address)
if ida_bytes.is_struct(flags):
opinfo = ida_nalt.opinfo_t()
ida_bytes.get_opinfo(opinfo, address, 0, flags)
entrySize = ida_bytes.get_data_elsize(address, flags, opinfo)
tid = opinfo.tid
else:
entrySize = ida_bytes.get_item_size(address)
tid = ida_idaapi.BADADDR
aligned_length = total_string_length + (4096 - (total_string_length % 4096))
segment_base = create_fake_segment(".fake_strings", aligned_length)
ida_bytes.create_data(address, flags, count * entrySize, tid)
current_string_address = segment_base
for d in data['stringLiterals']:
define_string(d)
def set_data_type(self, address: int, type: str):
type += ';'
ref_addr = parse_address(d)
write_string(current_string_address, d["string"])
write_address(ref_addr, current_string_address)
set_type(ref_addr, r'const char* const')
info = self._get_type(type)
if info is None:
return
current_string_address += len(d["string"]) + 1
status.update_progress()
if ida_typeinf.apply_type(DEFAULT_TIL, info[0], info[1], address, TINFO_DEFINITE) is None:
print(f"set_type({hex(address)}, {type}); failed!")
def set_function_type(self, address: int, type: str):
self.set_data_type(address, type)
def set_data_comment(self, address: int, cmt: str):
ida_bytes.set_cmt(address, cmt, False)
def set_function_comment(self, address: int, cmt: str):
func = ida_funcs.get_func(address)
if func is None:
return
ida_funcs.set_func_cmt(func, cmt, True)
def set_data_name(self, address: int, name: str):
ida_name.set_name(address, name, ida_name.SN_NOWARN | ida_name.SN_NOCHECK | ida_name.SN_FORCE)
def set_function_name(self, address: int, name: str):
self.set_data_name(address, name)
def add_cross_reference(self, from_address: int, to_address: int):
ida_xref.add_dref(from_address, to_address, ida_xref.XREF_USER | ida_xref.dr_I)
def import_c_typedef(self, type_def: str):
ida_typeinf.idc_parse_types(type_def, 0)
# optional
def add_function_to_group(self, address: int, group: str):
if not FOLDERS_AVAILABLE or True: # enable at your own risk - this is slow
return
if group not in self._folders:
self._folders.append(group)
self._function_dirtree.mkdir(group)
name = ida_funcs.get_func_name(address)
self._function_dirtree.rename(name, f"{group}/{name}")
# only required if supports_fake_string_segment == True
def create_fake_segment(self, name: str, size: int) -> int:
start = ida_ida.inf_get_max_ea()
end = start + size
ida_segment.add_segm(0, start, end, name, "DATA")
segment = ida_segment.get_segm_by_name(name)
segment.bitness = 1 if self._is_32_bit else 2
segment.perm = ida_segment.SEGPERM_READ
segment.update()
return start
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:
ida_bytes.put_dword(address, value)
else:
ida_bytes.put_qword(address, value)
# Status handler
class StatusHandler(BaseStatusHandler):
class IDAStatusHandler(BaseStatusHandler):
def __init__(self):
self.step = "Initializing"
self.max_items = 0
self.current_items = 0
self.start_time = datetime.datetime.now()
self.start_time = datetime.now()
self.step_start_time = self.start_time
self.last_updated_time = datetime.datetime.min
self.last_updated_time = datetime.min
def initialize(self):
ida_kernwin.show_wait_box("Processing")
@@ -231,7 +233,7 @@ class StatusHandler(BaseStatusHandler):
if self.was_cancelled():
raise RuntimeError("Cancelled script.")
current_time = datetime.datetime.now()
current_time = datetime.now()
if 0.5 > (current_time - self.last_updated_time).total_seconds():
return
@@ -254,8 +256,8 @@ Elapsed: {step_time} ({total_time})
self.step = step
self.max_items = max_items
self.current_items = 0
self.step_start_time = datetime.datetime.now()
self.last_updated_time = datetime.datetime.min
self.step_start_time = datetime.now()
self.last_updated_time = datetime.min
self.update()
def update_progress(self, new_progress = 1):
@@ -265,5 +267,10 @@ Elapsed: {step_time} ({total_time})
def was_cancelled(self):
return ida_kernwin.user_cancelled()
def close(self):
def shutdown(self):
ida_kernwin.hide_wait_box()
status = IDAStatusHandler()
backend = IDADisassemblerInterface(status)
context = ScriptContext(backend, status)
context.process()

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