53 Commits

Author SHA1 Message Date
LukeFZ
dbdf9e2123 set version, name, public key and hash alg from assembly name definition for dummy dlls 2026-01-18 02:46:13 +01:00
LukeFZ
0a66441bf0 fix enum base type on metadata versions v35+ 2026-01-18 02:28:31 +01:00
LukeFZ
0cfb90a0b7 add support for metadata version v105 (6000.5.0a5) 2026-01-15 14:16:42 +01:00
LukeFZ
f757d2c4d6 bump version to 2026.1 2026-01-14 18:16:37 +01:00
LukeFZ
41d9a52ad6 add support for slightly different name translation versions 2026-01-14 18:10:40 +01:00
LukeFZ
4d396847fa add InspectorSettings to the FrontendCoreJsonSerializerContext 2026-01-14 17:44:04 +01:00
LukeFZ
8c2f7a9960 implement (redux cli only) support for specifying a name translation map 2026-01-14 17:40:51 +01:00
LukeFZ
9fe77fdb1e allow specifying imagebase in redux CLI 2026-01-14 17:11:17 +01:00
LukeFZ
12887c99df ignore unity and il2cpp api headers in language statistics 2026-01-09 18:07:46 +01:00
LukeFZ
25b9ff03e6 ignore Il2CppTests/TestExpectedResults in language statistics 2026-01-09 18:05:20 +01:00
LukeFZ
d314004185 Merge branch 'master' of https://github.com/LukeFZ/Il2CppInspectorRedux 2026-01-07 20:39:24 +01:00
LukeFZ
776c507a36 mini typing fix in IDA script impl and bump Il2CppTests to .net 10 2026-01-07 20:39:10 +01:00
LukeFZ
14821d023c add implicit reference to enum class if a struct has a enum pointer as an instance field
this is only needed due to the forward definition code only being capable of generating struct types.
closes #38
2025-12-30 15:11:55 +01:00
LukeFZ
4b5a860381 remove try catch from GenerateRemainingTypeDefinitions() 2025-12-30 15:10:40 +01:00
LukeFZ
cfccc6982c reformat disassembler scripts 2025-12-26 05:48:20 +01:00
LukeFZ
e1b64dfd65 enable folder creation by default on IDA 9.3+
testing showed that this is now fast enough to not cause meaningful slowdowns
2025-12-25 16:42:57 +01:00
LukeFZ
c72bd2174e add additional heuristics for metadata registration
closes #35
2025-12-23 00:51:00 +01:00
LukeFZ
73b1594a0e implement new C++ type dependency graph resolver
this attempts to more properly fix some longstanding type emitting issues.

closes #36
2025-12-22 23:05:41 +01:00
LukeFZ
67f3fbe35c preemptively check if MethodPointers/InvokerIndices are mapped to prevent exceptions 2025-12-22 21:55:10 +01:00
LukeFZ
64a2bb3db7 fix unity version parsing breaking with three letter metadata versions 2025-12-19 23:19:44 +01:00
LukeFZ
8c0ef09a77 add unity header for v104 2025-12-19 13:04:09 +01:00
LukeFZ
b0a82f1dd0 fix incorrect bit indices for IsByRefLike and HasInlineArray in Il2CppTypeDefinitionBitfield 2025-12-19 13:01:23 +01:00
LukeFZ
77b4b60014 add untested support for metadata version v104 2025-12-19 12:58:47 +01:00
Luke
9d6b0c0d82 Update README.md 2025-12-16 10:52:32 +01:00
LukeFZ
51af520405 fix workflow (again) 2025-12-15 09:40:53 +01:00
LukeFZ
dd50c5d89e make build explicitly upload zips 2025-12-15 09:06:14 +01:00
LukeFZ
c9197a22f7 add rust caching to redux gui build workflow, fix release workflow 2025-12-15 08:23:38 +01:00
LukeFZ
332e3d4b27 bump version to 2025.1 2025-12-15 08:13:19 +01:00
LukeFZ
d872ac8014 fix redux gui workflow 2025-12-15 05:39:03 +01:00
LukeFZ
750a0c88d5 fix build workflow 2025-12-15 05:36:10 +01:00
LukeFZ
c3cc327feb explicitly specify required directories for each build job 2025-12-15 05:28:48 +01:00
LukeFZ
f8436517dc apply sparse checkout improvements to all build jobs 2025-12-15 05:21:35 +01:00
LukeFZ
d3d5ea4834 try to improve workflow clone times by excluding tests folder, add copyright to build props 2025-12-15 05:18:06 +01:00
LukeFZ
e25ea95ec6 update workflow to .net 10 and simplify logic a bit 2025-12-15 05:07:40 +01:00
LukeFZ
746abe53e3 update bin2object 2025-12-15 05:02:49 +01:00
LukeFZ
982396505d fix remaining compile time warnings 2025-12-15 05:00:02 +01:00
LukeFZ
9718c3025b unify some common project properties into Directory.Build.props, bump to .net 10 2025-12-15 04:55:56 +01:00
LukeFZ
5253bfb34a fix build.yml errors and add release workflow 2025-12-15 04:13:24 +01:00
LukeFZ
9bd32cee84 update sln 2025-12-15 04:12:41 +01:00
LukeFZ
16b1cffc0c rename "old" artifacts to "legacy" in workflow 2025-12-15 03:48:48 +01:00
LukeFZ
0da6aa67c5 fix compilation warnings 2025-12-14 08:33:04 +01:00
LukeFZ
e09776b4c8 fix reading interface and generic parameter constraint typeindices on v39 2025-12-14 08:32:32 +01:00
LukeFZ
4befde8ab4 increase CodeRegistration validation heuristics thresholds due to some games reaching them 2025-11-26 19:54:05 +01:00
LukeFZ
6d674ecc8c use same metareg scanner for < v27 and > v27, implement TryMapVATR in PEReader for performance 2025-11-11 04:14:31 +01:00
LukeFZ
8b93dda191 Merge branch 'master' of https://github.com/LukeFZ/Il2CppInspectorRedux 2025-10-13 09:17:05 +02:00
LukeFZ
bba8a2913a also unload mssdk64 tils to prevent type name conflicts 2025-10-13 09:16:45 +02:00
Luke
193395db29 Change Bin2Object URL to point to forked repo instead 2025-10-12 17:28:16 +02:00
LukeFZ
481d05668d (hopefully) lower python requirement to 3.8 2025-10-12 17:13:20 +02:00
LukeFZ
ca6c958f9a add pyghidra runtime annotation to fix support 2025-10-12 17:08:19 +02:00
LukeFZ
7a621b40c6 fix support for properties without methods 2025-09-26 00:30:59 +02:00
LukeFZ
1a418280fb update bin2object (again) 2025-09-26 00:28:51 +02:00
LukeFZ
f1a69cafe3 fix support for 35+, add support for 39 2025-09-18 23:36:57 +02:00
LukeFZ
e5f2fa703d update bin2object 2025-09-18 23:35:48 +02:00
81 changed files with 14262 additions and 1040 deletions

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
Il2CppTests/TestExpectedResults/* linguist-vendored
Il2CppInspector.Common/Cpp/Il2CppAPIHeaders/* linguist-vendored
Il2CppInspector.Common/Cpp/UnityHeaders/* linguist-vendored

View File

@@ -1,20 +1,30 @@
name: Il2CppInspectorRedux Build
on: [push, workflow_dispatch]
on: [push, workflow_dispatch, workflow_call]
jobs:
build-redux-gui: # this already includes stuff only relevant for linux/macos for when the gui is released on those platforms
runs-on: windows-latest
strategy:
matrix:
platform: ['windows-latest']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: true
sparse-checkout: |
VersionedSerialization
VersionedSerialization.Generator
Il2CppInspector.Common
Il2CppInspector.Redux.FrontendCore
Il2CppInspector.Redux.GUI
Il2CppInspector.Redux.GUI.UI
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: '10.0.x'
- name: Setup pnpm
uses: pnpm/action-setup@v4
@@ -28,20 +38,25 @@ jobs:
cache: "pnpm"
cache-dependency-path: Il2CppInspector.Redux.GUI.UI/pnpm-lock.yaml
- name: Install frontend dependencies
run: pnpm install
working-directory: ./Il2CppInspector.Redux.GUI.UI
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Setup Rust cache
uses: Swatinem/rust-cache@v2
with:
workspaces: "./Il2CppInspector.Redux.GUI.UI/src-tauri"
- name: Setup Tauri dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install frontend dependencies
run: pnpm install
working-directory: ./Il2CppInspector.Redux.GUI.UI
- name: Cache NuGet packages
uses: actions/cache@v4
@@ -57,68 +72,74 @@ jobs:
# 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/
run: dotnet publish -c Release --no-self-contained --no-restore -o ./win-x64 -r win-x64 ./Il2CppInspector.Redux.GUI/Il2CppInspector.Redux.GUI.csproj
- name: Upload GUI Artifact
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.GUI
path: build_output
path: ./win-x64
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']
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [ '10.0.x' ]
rid: ['win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64']
steps:
- uses: actions/checkout@v4
with:
submodules: true
steps:
- uses: actions/checkout@v5
with:
submodules: true
sparse-checkout: |
VersionedSerialization
VersionedSerialization.Generator
Il2CppInspector.Common
Il2CppInspector.Redux.FrontendCore
Il2CppInspector.Redux.CLI
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Cache NuGet packages
uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI
- name: 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: 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 }}
- 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
- uses: actions/checkout@v5
with:
submodules: true
sparse-checkout: |
VersionedSerialization
VersionedSerialization.Generator
Il2CppInspector.Common
Il2CppInspector.GUI
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
dotnet-version: '10.0.x'
- uses: actions/cache@v4
with:
@@ -131,32 +152,37 @@ jobs:
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
run: dotnet publish -c Release --no-self-contained --no-restore -o ./win-x64 -r win-x64 ./Il2CppInspector.GUI/Il2CppInspector.GUI.csproj
- name: Upload GUI Artifact
- 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
name: Il2CppInspectorRedux.Legacy.GUI
path: ./win-x64
build-old-cli:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [ '9.0.x' ]
dotnet-version: [ '10.0.x' ]
rid: ['win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64']
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
submodules: true
sparse-checkout: |
VersionedSerialization
VersionedSerialization.Generator
Il2CppInspector.Common
Il2CppInspector.CLI
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ matrix.dotnet-version }}
- uses: actions/cache@v3
- uses: actions/cache@v5
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
@@ -172,5 +198,5 @@ jobs:
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.Old.CLI-${{ matrix.rid }}
name: Il2CppInspectorRedux.Legacy.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }}

33
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Il2CppInspectorRedux Release
on:
push:
tags:
- '*'
jobs:
build_artifacts:
name: Build artifacts
uses: ./.github/workflows/build.yml
make_release:
name: Create release
needs: build_artifacts
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v7
with:
path: ./artifacts/
- name: Create artifact ZIPs
shell: pwsh
working-directory: ./artifacts
run: 'Get-ChildItem -Path . -Directory | ForEach-Object { Compress-Archive -Path $_.Name -DestinationPath "$($_.Name).zip" }'
- name: Delete unzipped artifact folders
shell: pwsh
working-directory: ./artifacts
run: 'Get-ChildItem -Path . -Directory | ForEach-Object { Remove-Item $_.Name -Recurse }'
- name: Make release
uses: softprops/action-gh-release@v2.4.2
with:
files: ./artifacts/**/*
name: Il2CppInspectorRedux ${{ github.ref_name }}
generate_release_notes: false

2
.gitmodules vendored
View File

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

View File

@@ -1,5 +1,13 @@
<Project>
<PropertyGroup>
<EnableSourceControlManagerQueries>true</EnableSourceControlManagerQueries>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
<EnableSourceControlManagerQueries>true</EnableSourceControlManagerQueries>
<Version>2026.1</Version>
<AssemblyVersion>$(Version).0.0</AssemblyVersion>
<FileVersion>$(AssemblyVersion)</FileVersion>
<Authors>Katy Coe, LukeFZ</Authors>
<Company>Noisy Cow Studios, LukeFZ</Company>
<Copyright>(c) 2023-2026 LukeFZ - https://github.com/LukeFZ, original (c) 2017-2021 Katy Coe - www.djkaty.com - www.github.com/djkaty</Copyright>
</PropertyGroup>
</Project>

View File

@@ -2,19 +2,13 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<!-- Plugins may require bass class library assemblies we're not using so disable trimming -->
<PublishTrimmed>false</PublishTrimmed>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<Version>2023.1</Version>
<Company>LukeFZ, Noisy Cow Studios</Company>
<Product>Il2CppInspectorRedux Command-Line Edition</Product>
<Copyright>(c) 2023-2024 LukeFZ - https://github.com/LukeFZ, original (c) 2017-2021 Katy Coe - www.djkaty.com - www.github.com/djkaty</Copyright>
<PackageId>Il2CppInspectorRedux.CLI</PackageId>
<Authors>LukeFZ, Katy Coe</Authors>
<AssemblyName>Il2CppInspector</AssemblyName>
</PropertyGroup>

View File

@@ -0,0 +1,47 @@
// Copyright (c) 2017-2021 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Il2CppInspector.CLI;
public static class PathUtils
{
public static string FindPath(string pathWithWildcards) {
var absolutePath = Path.GetFullPath(pathWithWildcards);
if (!absolutePath.Contains('*', StringComparison.Ordinal))
return absolutePath;
// Backslash is a special character when evaluating regexes so Windows path separator must be escaped... with a backslash
var sections = new Regex(string.Format(@"((?:[^*]*){0})((?:.*?)\*.*?)(?:$|{0})",
Path.DirectorySeparatorChar == '\\' ? @"\\" : Path.DirectorySeparatorChar.ToString()));
var matches = sections.Matches(absolutePath);
var pathLength = 0;
var path = "";
foreach (Match match in matches)
{
path += match.Groups[1].Value;
var search = match.Groups[2].Value;
if (!Directory.Exists(path))
return null;
var dir = Directory.GetDirectories(path, search, SearchOption.TopDirectoryOnly)
.OrderByDescending(x => x)
.FirstOrDefault();
path = dir + Path.DirectorySeparatorChar;
pathLength += match.Groups[1].Value.Length + match.Groups[2].Value.Length + 1;
}
if (pathLength < absolutePath.Length)
path += absolutePath[pathLength..];
return path;
}
}

View File

@@ -172,11 +172,10 @@ namespace Il2CppInspector.CLI
}
private static int Run(Options options) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Banner
var location = Assembly.GetEntryAssembly().Location;
if (location == "") // Single file executables don't have an assembly location
location = Path.Join(AppContext.BaseDirectory, "Il2CppInspector.exe");
var location = Path.Join(AppContext.BaseDirectory, "Il2CppInspector.exe");
var asmInfo = FileVersionInfo.GetVersionInfo(location);
Console.WriteLine(asmInfo.ProductName);
@@ -189,7 +188,7 @@ namespace Il2CppInspector.CLI
try {
PluginManager.EnsureInit();
}
catch (Exception ex) when (ex is InvalidOperationException || ex is DirectoryNotFoundException) {
catch (Exception ex) when (ex is InvalidOperationException or DirectoryNotFoundException) {
Console.Error.WriteLine(ex.Message);
return 1;
}
@@ -260,8 +259,8 @@ namespace Il2CppInspector.CLI
var unityAssembliesPath = string.Empty;
if (options.CreateSolution) {
unityPath = Utils.FindPath(options.UnityPath);
unityAssembliesPath = Utils.FindPath(options.UnityAssembliesPath);
unityPath = PathUtils.FindPath(options.UnityPath);
unityAssembliesPath = PathUtils.FindPath(options.UnityAssembliesPath);
if (!Directory.Exists(unityPath)) {
Console.Error.WriteLine($"Unity path {unityPath} does not exist");

View File

@@ -1,47 +0,0 @@
// Copyright (c) 2017-2021 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Il2CppInspector
{
public class Utils
{
public static string FindPath(string pathWithWildcards) {
var absolutePath = Path.GetFullPath(pathWithWildcards);
if (absolutePath.IndexOf("*", StringComparison.Ordinal) == -1)
return absolutePath;
// Backslash is a special character when evaluating regexes so Windows path separator must be escaped... with a backslash
Regex sections = new Regex(string.Format(@"((?:[^*]*){0})((?:.*?)\*.*?)(?:$|{0})",
Path.DirectorySeparatorChar == '\\' ? @"\\" : Path.DirectorySeparatorChar.ToString()));
var matches = sections.Matches(absolutePath);
var pathLength = 0;
var path = "";
foreach (Match match in matches) {
path += match.Groups[1].Value;
var search = match.Groups[2].Value;
if (!Directory.Exists(path))
return null;
var dir = Directory.GetDirectories(path, search, SearchOption.TopDirectoryOnly)
.OrderByDescending(x => x)
.FirstOrDefault();
path = dir + Path.DirectorySeparatorChar;
pathLength += match.Groups[1].Value.Length + match.Groups[2].Value.Length + 1;
}
if (pathLength < absolutePath.Length)
path += absolutePath.Substring(pathLength);
return path;
}
}
}

View File

@@ -85,7 +85,7 @@ public class CppDeclarationGenerator
// Resets the cache of visited types and pending types to output, but preserve any names we have already generated
public void Reset() {
_visitedFieldStructs.Clear();
_cppTypeDependencyGraph.Reset();
_visitedTypes.Clear();
_todoFieldStructs.Clear();
_todoTypeStructs.Clear();
@@ -98,124 +98,17 @@ public class CppDeclarationGenerator
* (For example: structures for value types must precede any usage of those value types for layout reasons).
*/
// A cache of field structures that have already been generated, to eliminate duplicate definitions
private readonly HashSet<TypeInfo> _visitedFieldStructs = [];
// A queue of field structures that need to be generated.
private readonly List<TypeInfo> _todoFieldStructs = [];
private readonly HashSet<TypeInfo> _requiredForwardDefinitionsForFields = [];
private readonly HashSet<TypeInfo> _currentVisitedFieldStructs = [];
private readonly HashSet<TypeInfo> _currentTodoFieldStructs = [];
private readonly HashSet<TypeInfo> _currentRequiredForwardDefinitions = [];
private readonly HashSet<TypeInfo> _currentlyVisitingFieldStructs = [];
private class CircularReferenceException(TypeInfo circularType, TypeInfo parentType) : Exception("Circular reference detected")
{
public TypeInfo CircularReferencedType { get; } = circularType;
public TypeInfo ParentType { get; } = parentType;
}
// Walk over dependencies of the given type, to figure out what field structures it depends on
private void VisitFieldStructsInner(TypeInfo ti)
{
if (_visitedFieldStructs.Contains(ti) || _currentVisitedFieldStructs.Contains(ti))
return;
if (ti.IsByRef || ti.ContainsGenericParameters)
return;
_currentVisitedFieldStructs.Add(ti);
_currentlyVisitingFieldStructs.Add(ti);
if (ti.BaseType != null)
VisitFieldStructsInner(ti.BaseType);
if (ti.IsArray)
VisitFieldStructsInner(ti.ElementType);
if (ti.IsEnum)
VisitFieldStructsInner(ti.GetEnumUnderlyingType());
foreach (var fi in ti.DeclaredFields.Where(fi => !fi.IsStatic && !fi.IsLiteral))
ProcessTypeField(fi);
_currentTodoFieldStructs.Add(ti);
_currentlyVisitingFieldStructs.Remove(ti);
return;
void ProcessTypeField(FieldInfo fi)
{
if (fi.FieldType.IsEnum || fi.FieldType.IsValueType)
{
VisitFieldStructsInner(fi.FieldType);
}
else if (fi.FieldType.HasElementType)
{
var elementType = fi.FieldType.ElementType;
if (!fi.FieldType.IsPointer || !_currentRequiredForwardDefinitions.Contains(elementType))
{
VisitFieldStructsInner(elementType);
if (elementType.IsValueType
&& elementType != ti
&& _currentlyVisitingFieldStructs.Contains(elementType)
&& !_currentRequiredForwardDefinitions.Contains(elementType))
{
// this is now an issue: there is a loop, and we need to resolve it
// if the field type is a pointer, we can make a forward declaration and be done with it
// otherwise, we cannot generate these types
if (!fi.FieldType.IsPointer)
Debugger.Break();
throw new CircularReferenceException(elementType, ti);
}
}
}
}
}
private void ClearCurrentFieldStructVisitState()
{
_currentTodoFieldStructs.Clear();
_currentVisitedFieldStructs.Clear();
_currentlyVisitingFieldStructs.Clear();
}
private readonly CppTypeDependencyGraph _cppTypeDependencyGraph = new();
private void VisitFieldStructs(TypeInfo ti)
{
ClearCurrentFieldStructVisitState();
if (ti.ContainsGenericParameters)
return;
var requiredTypesToVisit = new Stack<TypeInfo>([ti]);
while (true)
{
try
{
foreach (var typeToVisit in requiredTypesToVisit)
VisitFieldStructsInner(typeToVisit);
}
catch (CircularReferenceException ex)
{
ClearCurrentFieldStructVisitState();
_currentRequiredForwardDefinitions.Add(ex.CircularReferencedType);
requiredTypesToVisit.Push(ex.ParentType);
continue;
}
break;
}
_todoFieldStructs.AddRange(_currentTodoFieldStructs);
foreach (var visitedType in _currentVisitedFieldStructs)
_visitedFieldStructs.Add(visitedType);
foreach (var requiredType in _currentRequiredForwardDefinitions)
_requiredForwardDefinitionsForFields.Add(requiredType);
_todoFieldStructs.AddRange(_cppTypeDependencyGraph.DeriveDependencyOrderedTypes(ti));
}
// Generate the fields for the base class of all objects (Il2CppObject)
@@ -542,37 +435,34 @@ public class CppDeclarationGenerator
/// <returns>A string containing C type declarations</returns>
public List<(TypeInfo ilType, CppComplexType valueType, CppComplexType referenceType, CppComplexType fieldsType,
CppComplexType vtableType, CppComplexType staticsType)> GenerateRemainingTypeDeclarations() {
try
{
var decl = GenerateVisitedFieldStructs().Select(s =>
(s.ilType, s.valueType, s.referenceType, s.fieldsType, (CppComplexType)null,
(CppComplexType)null))
.ToList();
foreach (var ti in _todoTypeStructs)
{
var (cls, statics, vtable) = GenerateTypeStruct(ti);
decl.Add((ti, null, cls, null, vtable, statics));
}
var decl = GenerateVisitedFieldStructs().Select(s =>
(s.ilType, s.valueType, s.referenceType, s.fieldsType, (CppComplexType)null,
(CppComplexType)null))
.ToList();
return decl;
}
catch (Exception)
foreach (var ti in _todoTypeStructs)
{
return null;
}
finally
{
_todoTypeStructs.Clear();
_todoFieldStructs.Clear();
var (cls, statics, vtable) = GenerateTypeStruct(ti);
decl.Add((ti, null, cls, null, vtable, statics));
}
_todoTypeStructs.Clear();
_todoFieldStructs.Clear();
return decl;
}
public List<CppType> GenerateRequiredForwardDefinitions()
// L-TODO: With the move to the new dependency graph
// we might not need this anymore. maybe remove it in the future?
=> [];
/*
=> _requiredForwardDefinitionsForFields
.Select(x => new CppForwardDefinitionType(TypeNamer.GetName(x)))
.Cast<CppType>()
.ToList();
*/
#endregion

View File

@@ -497,6 +497,7 @@ namespace Il2CppInspector.Cpp
// Allow auto-generation of forward declarations
// This will break type generation unless the ultimate wanted type is a pointer
// Note this can still be the case with indirectionCount == 0 if .AsPointer() is called afterwards
if (!Types.ContainsKey(baseName))
Struct(baseName);

View File

@@ -0,0 +1,178 @@
#nullable enable
using System.Collections;
using System.Diagnostics;
using Il2CppInspector.Reflection;
namespace Il2CppInspector.Cpp;
public class CppTypeDependencyGraph
{
private sealed class CppTypeNode(TypeInfo typeInfo)
{
public TypeInfo Type { get; } = typeInfo;
public HashSet<CppTypeNode> IncomingValueReferences { get; } = [];
public HashSet<CppTypeNode> OutgoingValueReferences { get; } = [];
public HashSet<CppTypeNode> IncomingReferenceReferences { get; } = [];
public HashSet<CppTypeNode> OutgoingReferenceReferences { get; } = [];
public override string ToString()
=> Type.ToString();
}
private readonly Dictionary<TypeInfo, CppTypeNode> _typeNodes = [];
private readonly HashSet<CppTypeNode> _alreadyProcessedNodes = [];
private readonly Queue<CppTypeNode> _currentlyProcessingNodes = [];
private void AddReference(CppTypeNode from, CppTypeNode to, bool isReference)
{
// We do not count references to itself, even though they could occur
// as they cannot be value references
if (from.Type == to.Type)
return;
// if the target node is already processed, skip adding the reference
// as it is as assumed to already be present in any output.
if (_alreadyProcessedNodes.Contains(to))
return;
if (isReference)
{
from.OutgoingReferenceReferences.Add(to);
to.IncomingReferenceReferences.Add(from);
}
else
{
from.OutgoingValueReferences.Add(to);
to.IncomingValueReferences.Add(from);
}
}
private CppTypeNode GetNode(TypeInfo typeInfo)
{
if (_typeNodes.TryGetValue(typeInfo, out var typeNode))
return typeNode;
_typeNodes[typeInfo] = typeNode = new CppTypeNode(typeInfo);
CollectReferences(typeNode);
_currentlyProcessingNodes.Enqueue(typeNode);
return typeNode;
}
private void CollectReferences(CppTypeNode typeNode)
{
var typeInfo = typeNode.Type;
// if we have a base type, reference it.
// this is always a value reference
if (typeInfo.BaseType != null)
AddReference(typeNode, GetNode(typeInfo.BaseType), false);
// if this is an array, we also reference the element type.
// the reference type depends on the type of the element
if (typeInfo.IsArray)
AddReference(typeNode, GetNode(typeInfo.ElementType), typeInfo.ElementType.IsPassedByReference);
// if we are an enum, reference the underlying type.
// this is always a value reference
if (typeInfo.IsEnum)
AddReference(typeNode, GetNode(typeInfo.GetEnumUnderlyingType()), false);
// finally, process all instance fields.
// the reference type depends on the type of the field
foreach (var field in typeInfo.DeclaredFields.Where(f => f.IsInstanceField))
{
// if the field type is a pointer and the element type an enum, we have to add a reference to the enum.
// this is a workaround for type emitting only being able to generate forward definitions for *struct* types, and not enum types
if (field.FieldType.IsPointer)
{
// support multiple pointer levels
var type = field.FieldType;
while (type.IsPointer)
type = type.ElementType;
if (type.IsEnum)
AddReference(typeNode, GetNode(type), false);
}
else
{
AddReference(typeNode, GetNode(field.FieldType), field.FieldType.IsPassedByReference);
}
}
}
public List<TypeInfo> DeriveDependencyOrderedTypes(TypeInfo typeInfo)
{
_currentlyProcessingNodes.Clear();
// We assume that all nodes that are *not* in _currentlyProcessingNodes have already been
// processed in a previous call to DeriveDependencyOrderedTypes.
// initialize all dependencies for the given type info
_ = GetNode(typeInfo);
var dependencyOrderedTypes = new List<TypeInfo>(_currentlyProcessingNodes.Count);
while (_currentlyProcessingNodes.Count > 0)
{
var remainingCount = _currentlyProcessingNodes.Count;
for (int i = 0; i < remainingCount; i++)
{
var node = _currentlyProcessingNodes.Dequeue();
// If this node still has outgoing value references (=> non-ref references to unprocessed types), we cannot emit it yet.
if (node.OutgoingValueReferences.Count > 0)
{
_currentlyProcessingNodes.Enqueue(node);
continue;
}
// otherwise, we can safely emit it now.
dependencyOrderedTypes.Add(node.Type);
// now that we have emitted this node, we can remove it from the references of all other unprocessed nodes.
foreach (var referencingNode in node.IncomingValueReferences)
referencingNode.OutgoingValueReferences.Remove(node);
foreach (var referencingNode in node.IncomingReferenceReferences)
referencingNode.OutgoingReferenceReferences.Remove(node);
// ...clear all references from this node as well
node.IncomingValueReferences.Clear();
node.IncomingReferenceReferences.Clear();
// and mark it as processed
_alreadyProcessedNodes.Add(node);
}
// if we have not processed any node in this iteration, we have a circular dependency of some value types.
if (_currentlyProcessingNodes.Count == remainingCount)
throw new InvalidOperationException("Failed to resolve circular dependency during C++ type ordering.");
}
return dependencyOrderedTypes;
}
public void Reset()
{
_typeNodes.Clear();
_currentlyProcessingNodes.Clear();
}
}
// L-TODO: Do we want to expose these helper extensions on the types themselves?
file static class Extensions
{
extension(TypeInfo typeInfo)
{
public bool IsPassedByReference =>
typeInfo.IsByRef || typeInfo.IsPointer || !typeInfo.IsValueType;
}
extension(FieldInfo fieldInfo)
{
public bool IsInstanceField => fieldInfo is { IsLiteral: false, IsStatic: false, IsThreadStatic: false };
}
}

View File

@@ -1,4 +1,5 @@
using System.Diagnostics;
#nullable enable
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using Il2CppInspector.Reflection;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -176,9 +176,9 @@ namespace Il2CppInspector.Cpp.UnityHeaders
var bits = headerFilename.Split("-");
// Metadata version supplied
// Note: This relies on the metadata version being either 2 or 4 characters,
// Note: This relies on the metadata version being 2/3/4 characters,
// and that the smallest Unity version must be 5 characters or more
if (headerFilename[2] == '-' || headerFilename[4] == '-')
if (headerFilename.Substring(2, 3).Contains('-'))
bits = bits.Skip(1).ToArray();
var Min = new UnityVersion(bits[0]);

View File

@@ -197,7 +197,27 @@ namespace Il2CppInspector
return exports.Values;
}
public override bool TryMapVATR(ulong uiAddr, out uint fileOffset)
{
if (uiAddr == 0)
{
fileOffset = 0;
return true;
}
var section = sections.FirstOrDefault(x => uiAddr - pe.ImageBase >= x.VirtualAddress &&
uiAddr - pe.ImageBase < x.VirtualAddress + x.SizeOfRawData);
if (section == null)
{
fileOffset = 0;
return false;
}
fileOffset = (uint)(uiAddr - section.VirtualAddress - pe.ImageBase + section.PointerToRawData);
return true;
}
public override uint MapVATR(ulong uiAddr) {
if (uiAddr == 0)
return 0;

View File

@@ -106,7 +106,7 @@ namespace Il2CppInspector
// Can't use Stream.CopyTo as it doesn't support length parameter
var buffer = new byte[length];
source.Position = fileStart;
source.Read(buffer, 0, (int) length);
source.ReadExactly(buffer);
il2cpp.Write(buffer);
memoryNext += length;

View File

@@ -52,7 +52,9 @@ namespace Il2CppInspector
ctors[i] = new CustomAttributeCtor();
var ctorIndex = _data.ReadUInt32();
ctors[i].Ctor = _assembly.Model.MethodsByDefinitionIndex[ctorIndex];
ctors[i].Ctor = _assembly.Model.Package.Version >= MetadataVersions.V1040
? _assembly.Model.GetMetadataUsageMethod(MetadataUsage.FromEncodedIndex(_assembly.Model.Package, ctorIndex))
: _assembly.Model.MethodsByDefinitionIndex[ctorIndex];
}
_data.Position = _dataBufferStart;
@@ -158,7 +160,9 @@ namespace Il2CppInspector
memberIndex = -(memberIndex + 1);
var typeDefIndex = _data.ReadCompressedUInt32();
var typeInfo = _assembly.Model.TypesByDefinitionIndex[typeDefIndex];
var typeInfo = _assembly.Model.Package.Version >= MetadataVersions.V1040
? _assembly.Model.TypesByReferenceIndex[typeDefIndex]
: _assembly.Model.TypesByDefinitionIndex[typeDefIndex];
return (typeInfo, memberIndex);
}

View File

@@ -296,8 +296,11 @@ namespace Il2CppInspector
*/
if ((Metadata != null && Metadata.Types.Length != MetadataRegistration.TypeDefinitionsSizesCount)
|| CodeRegistration.ReversePInvokeWrapperCount > 0x10000
|| CodeRegistration.UnresolvedVirtualCallCount > 0x4000 // >= 22
|| CodeRegistration.InteropDataCount > 0x1000 // >= 23
// L-NOTE: These below boundaries have been updated already as some games
// have reached these limits during normal use. Maybe we should just remove them
// at this point?
|| CodeRegistration.UnresolvedVirtualCallCount > 0x8000 // >= 22
|| CodeRegistration.InteropDataCount > 0x2000 // >= 23
|| (Image.Version <= MetadataVersions.V241 && CodeRegistration.InvokerPointersCount > CodeRegistration.MethodPointersCount))
throw new NotSupportedException("The detected Il2CppCodeRegistration / Il2CppMetadataRegistration structs do not pass validation. This may mean that their fields have been re-ordered as a form of obfuscation and Il2CppInspector has not been able to restore the original order automatically. Consider re-ordering the fields in Il2CppBinaryClasses.cs and try again.");
@@ -331,21 +334,38 @@ namespace Il2CppInspector
// If a module contains only interfaces, abstract methods and/or non-concrete generic methods,
// 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.ReadMappedUWordArray(module.MethodPointers, (int) module.MethodPointerCount));
} catch (InvalidOperationException) {
if (Image.TryMapVATR(module.MethodPointers, out _))
{
try
{
ModuleMethodPointers.Add(module, Image.ReadMappedUWordArray(module.MethodPointers, (int)module.MethodPointerCount));
}
catch (InvalidOperationException)
{
ModuleMethodPointers.Add(module, new ulong[module.MethodPointerCount]);
}
}
else
{
ModuleMethodPointers.Add(module, new ulong[module.MethodPointerCount]);
}
// Read method invoker pointer indices - one per method
try
if (Image.TryMapVATR(module.InvokerIndices, out _))
{
MethodInvokerIndices.Add(module,
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
try
{
MethodInvokerIndices.Add(module,
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
}
catch (InvalidOperationException)
{
MethodInvokerIndices.Add(module, [.. new int[(int)module.MethodPointerCount]]);
}
}
catch (InvalidOperationException)
else
{
MethodInvokerIndices.Add(module, [..new int[(int)module.MethodPointerCount]]);
MethodInvokerIndices.Add(module, [.. new int[(int)module.MethodPointerCount]]);
}
}
}

View File

@@ -53,15 +53,16 @@ namespace Il2CppInspector
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<TypeIndex> 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<TypeIndex> 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, byte[]> AssemblyPublicKeys => Metadata.AssemblyPublicKeys;
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; }
@@ -75,6 +76,7 @@ namespace Il2CppInspector
public Dictionary<Il2CppMethodSpec, ulong> GenericMethodPointers { get; }
public Dictionary<Il2CppMethodSpec, int> GenericMethodInvokerIndices => Binary.GenericMethodInvokerIndices;
public ImmutableArray<Il2CppTypeDefinitionSizes> TypeDefinitionSizes => Binary.TypeDefinitionSizes;
public Dictionary<TypeIndex, int> TypeInlineArrays { get; } = new();
// TODO: Finish all file access in the constructor and eliminate the need for this
public IFileFormatStream BinaryImage => Binary.Image;
@@ -287,6 +289,11 @@ namespace Il2CppInspector
}
}
if (Version >= MetadataVersions.V1040)
{
TypeInlineArrays = Metadata.TypeInlineArrays.ToDictionary(x => x.TypeIndex, x => x.Length);
}
// Merge all metadata usage references into a single distinct list
MetadataUsages = buildMetadataUsages();

View File

@@ -277,33 +277,30 @@ namespace Il2CppInspector
vas = FindAllMappedWords(imageBytes, typesLength).Select(a => a - mrSize + ptrSize * 4);
// >= 19 && < 27
if (Image.Version < MetadataVersions.V270)
foreach (var va in vas)
{
var mr = Image.ReadMappedVersionedObject<Il2CppMetadataRegistration>(va);
if (mr.MetadataUsagesCount == (ulong) metadata.MetadataUsageLists.Length)
metadataRegistration = va;
}
// >= 19
// Luke: Previously, a check comparing MetadataUsagesCount was used here,
// but I know of at least one binary where this will break detection.
// Testing showed that we can just use the same heuristic used for v27+
// on older versions as well, so we'll just use it for all cases.
// plagiarism. noun - https://www.lexico.com/en/definition/plagiarism
// the practice of taking someone else's work or ideas and passing them off as one's own.
// Synonyms: copying, piracy, theft, strealing, infringement of copyright
// >= 27
else
// On some 32-bit (arm) binaries, just checking fields with the type count is not sufficient
// for uniquely identifying the metareg. We assume that all binaries have at least one generic class + inst
// and use this for an additional heuristic, though this is kinda jank and might not be enough for all binaries
if (Image.Version >= MetadataVersions.V190)
{
foreach (var va in vas)
{
var mr = Image.ReadMappedVersionedObject<Il2CppMetadataRegistration>(va);
if (mr.TypeDefinitionsSizesCount == metadata.Types.Length
&& mr.FieldOffsetsCount == metadata.Types.Length)
&& mr.FieldOffsetsCount == metadata.Types.Length
&& mr is { GenericInstsCount: > 0, GenericClassesCount: > 0 })
{
metadataRegistration = va;
break;
}
}
}
if (metadataRegistration == 0)
return (0, 0);

View File

@@ -36,13 +36,15 @@ namespace Il2CppInspector
public ImmutableArray<Il2CppMetadataUsagePair> MetadataUsagePairs { get; set; }
public ImmutableArray<Il2CppFieldRef> FieldRefs { get; set; }
public ImmutableArray<int> InterfaceUsageIndices { get; set; }
public ImmutableArray<TypeIndex> InterfaceUsageIndices { get; set; }
public ImmutableArray<int> NestedTypeIndices { get; set; }
public ImmutableArray<int> AttributeTypeIndices { get; set; }
public ImmutableArray<int> GenericConstraintIndices { get; set; }
public ImmutableArray<TypeIndex> GenericConstraintIndices { get; set; }
public ImmutableArray<uint> VTableMethodIndices { get; set; }
public string[] StringLiterals { get; set; }
public ImmutableArray<Il2CppInlineArrayLength> TypeInlineArrays { get; set; }
public int FieldAndParameterDefaultValueDataOffset => Version >= MetadataVersions.V380
? Header.FieldAndParameterDefaultValueData.Offset
: Header.FieldAndParameterDefaultValueDataOffset;
@@ -52,6 +54,7 @@ namespace Il2CppInspector
: Header.AttributeDataOffset;
public Dictionary<int, string> Strings { get; private set; } = [];
public Dictionary<int, byte[]> AssemblyPublicKeys { get; private set; } = [];
// Set if something in the metadata has been modified / decrypted
public bool IsModified { get; private set; } = false;
@@ -95,7 +98,7 @@ namespace Il2CppInspector
// Set object versioning for Bin2Object from metadata version
Version = new StructVersion(Header.Version);
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V380) {
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V1050) {
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version}).");
}
@@ -142,6 +145,31 @@ namespace Il2CppInspector
throw new InvalidOperationException("Could not determine TypeIndex size based on the metadata header");
var fullTag = $"{tag}_{TypeIndex.TagPrefix}{typeIndexSize}";
if (Version >= MetadataVersions.V390)
{
var parameterIndexSize = GetIndexSize(Header.Parameters.Count);
fullTag += $"_{ParameterIndex.TagPrefix}{parameterIndexSize}";
}
if (Version >= MetadataVersions.V1040)
{
var eventIndexSize = GetIndexSize(Header.Events.Count);
var interfacesIndexSize = GetIndexSize(Header.InterfaceOffsets.Count);
var nestedTypeIndexSize = GetIndexSize(Header.NestedTypes.Count);
var propertyIndexSize = GetIndexSize(Header.Properties.Count);
fullTag += $"_{EventIndex.TagPrefix}{eventIndexSize}"
+ $"_{InterfacesIndex.TagPrefix}{interfacesIndexSize}"
+ $"_{NestedTypeIndex.TagPrefix}{nestedTypeIndexSize}"
+ $"_{PropertyIndex.TagPrefix}{propertyIndexSize}";
}
if (Version >= MetadataVersions.V1050)
{
var methodIndexSize = GetIndexSize(Header.Methods.Count);
fullTag += $"_{MethodIndex.TagPrefix}{methodIndexSize}";
}
Version = new StructVersion(Version.Major, Version.Minor, fullTag);
}
@@ -154,8 +182,11 @@ namespace Il2CppInspector
// in the header after the sanity and version fields, and since it will always point directly to the first byte after the end of the header,
// we can use this value to determine the actual header length and therefore narrow down the metadata version to 24.0/24.1 or 24.2.
if (!pluginResult.SkipValidation) {
var realHeaderLength = Header.StringLiteralOffset;
if (!pluginResult.SkipValidation)
{
var realHeaderLength = Version >= MetadataVersions.V380
? Header.StringLiterals.Offset
: Header.StringLiteralOffset;
if (realHeaderLength != Sizeof<Il2CppGlobalMetadataHeader>()) {
if (Version == MetadataVersions.V240) {
@@ -195,11 +226,11 @@ namespace Il2CppInspector
FieldDefaultValues = ReadMetadataArray<Il2CppFieldDefaultValue>(Header.FieldDefaultValuesOffset, Header.FieldDefaultValuesSize, Header.FieldDefaultValues);
Properties = ReadMetadataArray<Il2CppPropertyDefinition>(Header.PropertiesOffset, Header.PropertiesSize, Header.Properties);
Events = ReadMetadataArray<Il2CppEventDefinition>(Header.EventsOffset, Header.EventsSize, Header.Events);
InterfaceUsageIndices = ReadMetadataPrimitiveArray<int>(Header.InterfacesOffset, Header.InterfacesSize, Header.Interfaces);
InterfaceUsageIndices = ReadMetadataArray<TypeIndex>(Header.InterfacesOffset, Header.InterfacesSize, Header.Interfaces);
NestedTypeIndices = ReadMetadataPrimitiveArray<int>(Header.NestedTypesOffset, Header.NestedTypesSize, Header.NestedTypes);
GenericContainers = ReadMetadataArray<Il2CppGenericContainer>(Header.GenericContainersOffset, Header.GenericContainersSize, Header.GenericContainers);
GenericParameters = ReadMetadataArray<Il2CppGenericParameter>(Header.GenericParametersOffset, Header.GenericParametersSize, Header.GenericParameters);
GenericConstraintIndices = ReadMetadataPrimitiveArray<int>(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize, Header.GenericParameterConstraints);
GenericConstraintIndices = ReadMetadataArray<TypeIndex>(Header.GenericParameterConstraintsOffset, Header.GenericParameterConstraintsSize, Header.GenericParameterConstraints);
InterfaceOffsets = ReadMetadataArray<Il2CppInterfaceOffsetPair>(Header.InterfaceOffsetsOffset, Header.InterfaceOffsetsSize, Header.InterfaceOffsets);
VTableMethodIndices = ReadMetadataPrimitiveArray<uint>(Header.VTableMethodsOffset, Header.VTableMethodsSize, Header.VtableMethods);
@@ -249,16 +280,28 @@ namespace Il2CppInspector
Header.AttributeDataRangeSize, Header.AttributeDataRanges);
}
if (Version >= MetadataVersions.V1040)
{
TypeInlineArrays = ReadMetadataArray<Il2CppInlineArrayLength>(0, 0, Header.TypeInlineArrays);
}
// Get all metadata strings
var pluginGetStringsResult = PluginHooks.GetStrings(this);
if (pluginGetStringsResult.IsDataModified && !pluginGetStringsResult.IsInvalid)
Strings = pluginGetStringsResult.Strings;
else {
Position = Header.StringOffset;
var stringOffset = Version >= MetadataVersions.V380
? Header.Strings.Offset
: Header.StringOffset;
var stringLength = Version >= MetadataVersions.V380
? Header.Strings.SectionSize
: Header.StringSize;
while (Position < Header.StringOffset + Header.StringSize)
Strings.Add((int) Position - Header.StringOffset, ReadNullTerminatedString());
Position = stringOffset;
while (Position < stringOffset + stringLength)
Strings.Add((int)Position - stringOffset, ReadNullTerminatedString());
}
// Get all string literals
@@ -277,15 +320,17 @@ namespace Il2CppInspector
if (Version >= MetadataVersions.V350)
{
StringLiterals = new string[stringLiteralList.Length - 1];
for (var i = 0; i < stringLiteralList.Length; i++)
var literals = new string[stringLiteralList.Length - 1];
for (var i = 0; i < literals.Length; i++)
{
var currentStringDataIndex = stringLiteralList[i].DataIndex;
var nextStringDataIndex = stringLiteralList[i + 1].DataIndex;
var stringLength = nextStringDataIndex - currentStringDataIndex;
StringLiterals[i] = ReadFixedLengthString(dataOffset + currentStringDataIndex, stringLength);
literals[i] = ReadFixedLengthString(dataOffset + currentStringDataIndex, stringLength);
}
StringLiterals = literals;
}
else
{
@@ -297,6 +342,22 @@ namespace Il2CppInspector
}
}
// These are stored in a special way, so we read them in advance.
if (Version == MetadataVersions.V242 || Version >= MetadataVersions.V244)
{
var stringOffset = Version >= MetadataVersions.V380
? Header.Strings.Offset
: Header.StringOffset;
foreach (var assembly in Assemblies)
{
Position = stringOffset + assembly.Aname.PublicKeyIndex;
var length = ReadCompressedUInt32();
AssemblyPublicKeys[assembly.Aname.PublicKeyIndex] = ReadBytes((int)length).ToArray();
}
}
// Post-processing hook
IsModified |= PluginHooks.PostProcessMetadata(this).IsStreamModified;
return;

View File

@@ -1,13 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<LangVersion>preview</LangVersion>
<AssemblyName>Il2CppInspector.Common</AssemblyName>
<Authors>Katy Coe</Authors>
<Version>2023.1</Version>
<Company>Noisy Cow Studios</Company>
<Product>Il2CppInspector Shared Library</Product>
<TargetsForTfmSpecificBuildOutput>$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage</TargetsForTfmSpecificBuildOutput>
<PackageId>NoisyCowStudios.Il2CppInspector</PackageId>
@@ -15,9 +9,9 @@
</Description>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageProjectUrl>https://github.com/djkaty/Il2CppInspector</PackageProjectUrl>
<PackageProjectUrl>https://github.com/LukeFZ/Il2CppInspectorRedux</PackageProjectUrl>
<PackageLicenseExpression>AGPL-3.0-only</PackageLicenseExpression>
<RepositoryUrl>https://github.com/djkaty/Il2CppInspector</RepositoryUrl>
<RepositoryUrl>https://github.com/LukeFZ/Il2CppInspectorRedux</RepositoryUrl>
<PackageTags>IL2CPP;Unity;Reverse Engineering;Metadata</PackageTags>
</PropertyGroup>
@@ -66,6 +60,10 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Next\NameTranslation\" />
</ItemGroup>
<Target DependsOnTargets="ResolveReferences" Name="CopyProjectReferencesToPackage">
<ItemGroup>
<BuildOutputInPackage Include="@(ReferenceCopyLocalPaths-&gt;WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />

View File

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

View File

@@ -2,58 +2,27 @@
namespace Il2CppInspector.Next.Metadata;
public struct GenericContainerIndex(int value) : IReadable, IEquatable<GenericContainerIndex>
public struct GenericContainerIndex(int value) : IIndexType<GenericContainerIndex>, IReadable, IEquatable<GenericContainerIndex>
{
public const string TagPrefix = nameof(GenericContainerIndex);
public const string TagPrefix = nameof(GenericContainerIndex);
static string IIndexType<GenericContainerIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<GenericContainerIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static implicit operator int(GenericContainerIndex idx) => idx._value;
public static implicit operator GenericContainerIndex(int idx) => new(idx);
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
if (version >= MetadataVersions.V380
&& version.Tag != null
&& version.Tag.Contains(TagPrefix)
&& !version.Tag.Contains($"{TagPrefix}4"))
{
if (version.Tag.Contains($"{TagPrefix}2"))
return sizeof(ushort);
if (version.Tag.Contains($"{TagPrefix}1"))
return sizeof(byte);
}
return sizeof(int);
}
=> IIndexType<GenericContainerIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
if (version >= MetadataVersions.V380
&& version.Tag != null
&& version.Tag.Contains(TagPrefix)
&& !version.Tag.Contains($"{TagPrefix}4"))
{
if (version.Tag.Contains($"{TagPrefix}2"))
{
_value = reader.ReadPrimitive<short>();
_value = _value == ushort.MaxValue ? -1 : _value;
return;
}
if (version.Tag.Contains($"{TagPrefix}1"))
{
_value = reader.ReadPrimitive<byte>();
_value = _value == byte.MaxValue ? -1 : _value;
return;
}
}
_value = reader.ReadPrimitive<int>();
_value = IIndexType<GenericContainerIndex>.ReadIndex(ref reader, in version);
}
#region Equality operators + ToString
#region Operators + ToString
public static implicit operator int(GenericContainerIndex idx) => idx._value;
public static implicit operator GenericContainerIndex(int idx) => new(idx);
public static bool operator ==(GenericContainerIndex left, GenericContainerIndex right)
=> left._value == right._value;
@@ -61,7 +30,7 @@ public struct GenericContainerIndex(int value) : IReadable, IEquatable<GenericCo
public static bool operator !=(GenericContainerIndex left, GenericContainerIndex right)
=> !(left == right);
public readonly override bool Equals(object? obj)
public readonly override bool Equals(object obj)
=> obj is GenericContainerIndex other && Equals(other);
public readonly bool Equals(GenericContainerIndex other)

View File

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

View File

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

View File

@@ -66,7 +66,7 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(LessThan = "35.0")]
public int MethodsSize { get; private set; }
[VersionCondition(GreaterThan = "16.0")]
[VersionCondition(GreaterThan = "16.0", LessThan = "35.0")]
[VersionCondition(EqualTo = "16.0")]
public int ParameterDefaultValuesOffset { get; private set; }
@@ -310,6 +310,9 @@ public partial record struct Il2CppGlobalMetadataHeader
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata TypeDefinitions { get; private set; }
[VersionCondition(GreaterThan = "104.0")]
public Il2CppSectionMetadata TypeInlineArrays { get; private set; }
[VersionCondition(GreaterThan = "38.0")]
public Il2CppSectionMetadata Images { get; private set; }

View File

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

View File

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

View File

@@ -4,7 +4,6 @@ using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
using ParameterIndex = int;
[VersionedStruct]
public partial record struct Il2CppMethodDefinition

View File

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

View File

@@ -4,7 +4,6 @@ using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
using MethodIndex = int;
[VersionedStruct]
public partial record struct Il2CppPropertyDefinition

View File

@@ -6,11 +6,6 @@ namespace Il2CppInspector.Next.Metadata;
using StringIndex = int;
using FieldIndex = int;
using MethodIndex = int;
using EventIndex = int;
using PropertyIndex = int;
using NestedTypeIndex = int;
using InterfacesIndex = int;
using VTableIndex = int;
[VersionedStruct]

View File

@@ -17,5 +17,6 @@ public partial record struct Il2CppTypeDefinitionBitfield
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;
public bool IsByRefLike => ((_value >> 16) & 1) == 1;
public bool HasInlineArray => ((_value >> 17) & 1) == 1;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,57 +2,27 @@
namespace Il2CppInspector.Next.Metadata;
public struct TypeDefinitionIndex(int value) : IReadable, IEquatable<TypeDefinitionIndex>
public struct TypeDefinitionIndex(int value) : IIndexType<TypeDefinitionIndex>, IReadable, IEquatable<TypeDefinitionIndex>
{
public const string TagPrefix = nameof(TypeDefinitionIndex);
public const string TagPrefix = nameof(TypeDefinitionIndex);
static string IIndexType<TypeDefinitionIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<TypeDefinitionIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static implicit operator int(TypeDefinitionIndex idx) => idx._value;
public static implicit operator TypeDefinitionIndex(int idx) => new(idx);
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
if (version >= MetadataVersions.V380
&& version.Tag != null
&& version.Tag.Contains(TagPrefix)
&& !version.Tag.Contains($"{TagPrefix}4"))
{
if (version.Tag.Contains($"{TagPrefix}2"))
return sizeof(ushort);
if (version.Tag.Contains($"{TagPrefix}1"))
return sizeof(byte);
}
return sizeof(int);
}
=> IIndexType<TypeDefinitionIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
if (version >= MetadataVersions.V380
&& version.Tag != null
&& version.Tag.Contains(TagPrefix)
&& !version.Tag.Contains($"{TagPrefix}4"))
{
if (version.Tag.Contains($"{TagPrefix}2"))
{
_value = reader.ReadPrimitive<ushort>();
return;
}
if (version.Tag.Contains($"{TagPrefix}1"))
{
_value = reader.ReadPrimitive<byte>();
_value = _value == byte.MaxValue ? -1 : _value;
return;
}
}
_value = reader.ReadPrimitive<int>();
_value = IIndexType<TypeDefinitionIndex>.ReadIndex(ref reader, in version);
}
#region Equality operators + ToString
#region Operators + ToString
public static implicit operator int(TypeDefinitionIndex idx) => idx._value;
public static implicit operator TypeDefinitionIndex(int idx) => new(idx);
public static bool operator ==(TypeDefinitionIndex left, TypeDefinitionIndex right)
=> left._value == right._value;
@@ -60,7 +30,7 @@ public struct TypeDefinitionIndex(int value) : IReadable, IEquatable<TypeDefinit
public static bool operator !=(TypeDefinitionIndex left, TypeDefinitionIndex right)
=> !(left == right);
public readonly override bool Equals(object? obj)
public readonly override bool Equals(object obj)
=> obj is TypeDefinitionIndex other && Equals(other);
public readonly bool Equals(TypeDefinitionIndex other)

View File

@@ -2,58 +2,27 @@
namespace Il2CppInspector.Next.Metadata;
public struct TypeIndex(int value) : IReadable, IEquatable<TypeIndex>
public struct TypeIndex(int value) : IIndexType<TypeIndex>, IReadable, IEquatable<TypeIndex>
{
public const string TagPrefix = nameof(TypeIndex);
public const string TagPrefix = nameof(TypeIndex);
static string IIndexType<TypeIndex>.TagPrefix => TagPrefix;
static StructVersion IIndexType<TypeIndex>.AddedVersion => MetadataVersions.V390;
private int _value = value;
public static implicit operator int(TypeIndex idx) => idx._value;
public static implicit operator TypeIndex(int idx) => new(idx);
public static int Size(in StructVersion version = default, bool is32Bit = false)
{
if (version >= MetadataVersions.V380
&& version.Tag != null
&& version.Tag.Contains(TagPrefix)
&& !version.Tag.Contains($"{TagPrefix}4"))
{
if (version.Tag.Contains($"{TagPrefix}2"))
return sizeof(ushort);
if (version.Tag.Contains($"{TagPrefix}1"))
return sizeof(byte);
}
return sizeof(int);
}
=> IIndexType<TypeIndex>.IndexSize(version, is32Bit);
public void Read<TReader>(ref TReader reader, in StructVersion version = default) where TReader : IReader, allows ref struct
{
if (version >= MetadataVersions.V380
&& version.Tag != null
&& version.Tag.Contains(TagPrefix)
&& !version.Tag.Contains($"{TagPrefix}4"))
{
if (version.Tag.Contains($"{TagPrefix}2"))
{
_value = reader.ReadPrimitive<ushort>();
_value = _value == ushort.MaxValue ? -1 : _value;
return;
}
if (version.Tag.Contains($"{TagPrefix}1"))
{
_value = reader.ReadPrimitive<byte>();
_value = _value == byte.MaxValue ? -1 : _value;
return;
}
}
_value = reader.ReadPrimitive<int>();
_value = IIndexType<TypeIndex>.ReadIndex(ref reader, in version);
}
#region Equality operators + ToString
#region Operators + ToString
public static implicit operator int(TypeIndex idx) => idx._value;
public static implicit operator TypeIndex(int idx) => new(idx);
public static bool operator ==(TypeIndex left, TypeIndex right)
=> left._value == right._value;
@@ -61,7 +30,7 @@ public struct TypeIndex(int value) : IReadable, IEquatable<TypeIndex>
public static bool operator !=(TypeIndex left, TypeIndex right)
=> !(left == right);
public readonly override bool Equals(object? obj)
public readonly override bool Equals(object obj)
=> obj is TypeIndex other && Equals(other);
public readonly bool Equals(TypeIndex other)

View File

@@ -27,7 +27,7 @@ public static class MetadataVersions
public static readonly StructVersion V310 = new(31);
// No tag - 29.0/31.0
public static readonly string Tag2022 = "2022"; // 29.1/31.1
public const string Tag2022 = "2022"; // 29.1/31.1
// Unity 6000.3.0a2
public static readonly StructVersion V350 = new(35);
@@ -35,4 +35,17 @@ public static class MetadataVersions
// Unity 6000.3.0a5
public static readonly StructVersion V380 = new(38);
// NOTE: This version uses tags to specify the size of TypeIndex, TypeDefinitionIndex, and GenericContainerIndex.
// Unity 6000.3.0b1
public static readonly StructVersion V390 = new(39);
// NOTE: This version additionally uses a tag to specify the size of ParameterIndex.
// Unity 6000.5.0a3
public static readonly StructVersion V1040 = new(104);
// NOTE: This version additionally uses tags to specify the size of InterfaceIndex, EventIndex, PropertyIndex, NestedTypeIndex,
// alongside a new metadata section for Il2CppInlineArrayLength and a bitfield flag indicating an inline array.
// Unity 6000.5.0a5
public static readonly StructVersion V1050 = new(105);
// NOTE: This version additionally uses a tag to specify the size of MethodIndex.
}

View File

@@ -0,0 +1,97 @@
using dnlib.DotNet;
using Il2CppInspector.Reflection;
using System.Diagnostics.CodeAnalysis;
namespace Il2CppInspector.Next.NameTranslation;
public readonly struct NameTranslationApplierContext
{
private readonly NameTranslationInfo _nameTranslationInfo;
private NameTranslationApplierContext(NameTranslationInfo nameTranslationInfo)
{
_nameTranslationInfo = nameTranslationInfo;
}
public static void Process(Assembly assemblyDefinition, NameTranslationInfo nameTranslationInfo)
{
var ctx = new NameTranslationApplierContext(nameTranslationInfo);
ctx.Process(assemblyDefinition);
}
private string TranslateName(string name) =>
_nameTranslationInfo.NameTranslation.GetValueOrDefault(name, name);
private bool TryTranslateName(string name, out string translatedName)
{
if (_nameTranslationInfo.NameTranslation.TryGetValue(name, out var translated))
{
translatedName = translated;
return true;
}
translatedName = null;
return false;
}
private string TranslateNamespace(string className, string currentNamespace) =>
_nameTranslationInfo.ClassNamespaces.GetValueOrDefault(className, currentNamespace);
private void Process(Assembly assemblyDefinition)
{
foreach (var module in assemblyDefinition.DefinedTypes)
Process(module);
}
private void Process(TypeInfo typeDefinition)
{
if (TryTranslateName(typeDefinition.Name, out var translatedName))
{
typeDefinition.Namespace = TranslateNamespace(typeDefinition.Name, typeDefinition.Namespace);
typeDefinition.Name = translatedName;
}
foreach (var method in typeDefinition.DeclaredMethods)
Process(method);
foreach (var field in typeDefinition.DeclaredFields)
Process(field);
foreach (var property in typeDefinition.DeclaredProperties)
Process(property);
foreach (var evt in typeDefinition.DeclaredEvents)
Process(evt);
foreach (var nestedType in typeDefinition.DeclaredNestedTypes)
Process(nestedType);
}
private void Process(MethodInfo methodDefinition)
{
methodDefinition.Name = TranslateName(methodDefinition.Name);
foreach (var parameter in methodDefinition.DeclaredParameters)
Process(parameter);
}
private void Process(FieldInfo fieldDefinition)
{
fieldDefinition.Name = TranslateName(fieldDefinition.Name);
}
private void Process(PropertyInfo propertyDefinition)
{
propertyDefinition.Name = TranslateName(propertyDefinition.Name);
}
private void Process(EventInfo eventDefinition)
{
eventDefinition.Name = TranslateName(eventDefinition.Name);
}
private void Process(ParameterInfo parameterDefinition)
{
parameterDefinition.Name = TranslateName(parameterDefinition.Name);
}
}

View File

@@ -0,0 +1,8 @@
using System.Collections.Frozen;
namespace Il2CppInspector.Next.NameTranslation;
public sealed record NameTranslationInfo(
FrozenDictionary<string, string> NameTranslation,
FrozenDictionary<string, string> ClassNamespaces
);

View File

@@ -0,0 +1,188 @@
using dnlib.DotNet;
using System.Collections.Frozen;
using System.Diagnostics;
namespace Il2CppInspector.Next.NameTranslation;
public ref struct NameTranslationParserContext
{
private enum CurrentSection
{
None,
Classes,
Methods,
Fields,
Properties,
Events,
Parameters,
Hashes,
}
private CurrentSection _currentSection;
private bool _reverseOrder;
private readonly ReadOnlySpan<string> _nameTranslationLines;
private readonly char _seperator;
private NameTranslationParserContext(ReadOnlySpan<string> nameTranslationLines, char seperator)
{
_currentSection = CurrentSection.None;
_nameTranslationLines = nameTranslationLines;
_seperator = seperator;
}
public static NameTranslationInfo Parse(ReadOnlySpan<string> nameTranslationLines, char seperator = '⇨')
{
var ctx = new NameTranslationParserContext(nameTranslationLines, seperator);
return ctx.Parse();
}
private void HandleMetadataLine(string line)
{
switch (line)
{
case "#ReverseOrder":
_reverseOrder = true;
break;
case "#Classes":
_currentSection = CurrentSection.Classes;
break;
case "#Methods":
_currentSection = CurrentSection.Methods;
break;
case "#Fields":
_currentSection = CurrentSection.Fields;
break;
case "#Properties":
_currentSection = CurrentSection.Properties;
break;
case "#Events":
_currentSection = CurrentSection.Events;
break;
case "#Parameters":
_currentSection = CurrentSection.Parameters;
break;
// Used in some variations of the standard name translation map.
// This doesn't seperate data by sections, but is easier to parse overall
case not null when line.StartsWith("#Hashes"):
_currentSection = CurrentSection.Hashes;
break;
default:
Debug.Assert(false);
break;
}
}
private NameTranslationInfo Parse()
{
var nameTranslationMap = new Dictionary<string, string>(_nameTranslationLines.Length);
var namespaceTranslationMap = new Dictionary<string, string>();
foreach (var line in _nameTranslationLines)
{
if (line.StartsWith('#'))
{
HandleMetadataLine(line);
continue;
}
var split = line.Split(_seperator, 2);
var obfuscatedName = split[0];
var deobfuscatedFullName = split[1].TrimEnd();
if (!_reverseOrder)
{
(obfuscatedName, deobfuscatedFullName) = (deobfuscatedFullName, obfuscatedName);
}
string deobfuscatedName;
string? deobfuscatedNamespace;
if (_currentSection != CurrentSection.Classes && _currentSection != CurrentSection.Hashes)
{
deobfuscatedNamespace = null;
deobfuscatedName = ParseFullName(deobfuscatedFullName);
}
else
{
(deobfuscatedName, deobfuscatedNamespace) = ParseFullClassName(deobfuscatedFullName);
}
// This sometimes happens, just ignore it if so
if (obfuscatedName == deobfuscatedName)
continue;
if (nameTranslationMap.TryGetValue(obfuscatedName, out var alreadySeenName))
{
if (alreadySeenName != deobfuscatedName)
throw new InvalidDataException(
$"Name translation collision detected for name {obfuscatedName}: {alreadySeenName} vs. {deobfuscatedName}");
}
else
{
nameTranslationMap[obfuscatedName] = deobfuscatedName;
if (deobfuscatedNamespace != null)
namespaceTranslationMap[obfuscatedName] = deobfuscatedNamespace;
}
}
return new NameTranslationInfo(
nameTranslationMap
.ToFrozenDictionary(),
namespaceTranslationMap
.ToFrozenDictionary());
}
private string ParseFullName(string fullName)
=> _currentSection switch
{
CurrentSection.Classes => ParseFullClassName(fullName).Item1,
CurrentSection.Methods or CurrentSection.Properties => ParseFullMemberWithArgumentsName(fullName),
CurrentSection.Fields or CurrentSection.Events => ParseFullMemberName(fullName),
CurrentSection.Parameters => ParseFullParameterName(fullName),
_ => throw new UnreachableException()
};
private static (string, string?) ParseFullClassName(string fullName)
{
// For the namespace, return everything up until the last part of the fully qualified class name
var lastNamespaceIdx = fullName.LastIndexOf('.');
var ns = lastNamespaceIdx == -1
? null
: fullName[..lastNamespaceIdx];
// If we have a nested class, return that name
var nestedClassIdx = fullName.LastIndexOf('/') + 1;
if (nestedClassIdx != 0)
return (fullName[nestedClassIdx..], ns);
// Otherwise, if we have a namespace, return the last part of the fully qualified name
if (lastNamespaceIdx != -1)
return (fullName[(lastNamespaceIdx + 1)..], ns);
// Otherwise the class has no namespace, return the full name
return (fullName, ns);
}
// Used for both fields and events
private static string ParseFullMemberName(string fullName)
{
var memberNameStartIdx = fullName.LastIndexOf("::", StringComparison.Ordinal) + 2;
return fullName[memberNameStartIdx..];
}
private static string ParseFullMemberWithArgumentsName(string fullName)
{
var methodNameWithParameters = ParseFullMemberName(fullName);
var parametersStartIdx = methodNameWithParameters.IndexOf('(');
return methodNameWithParameters[..parametersStartIdx];
}
private static string ParseFullParameterName(string fullName)
{
var parameterNameStartIdx = fullName.LastIndexOf(' ') + 1;
return fullName[parameterNameStartIdx..];
}
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable;
#nullable enable
using System.Collections.Immutable;
using VersionedSerialization;
using VersionedSerialization.Attributes;

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable;
#nullable enable
using System.Collections.Immutable;
using VersionedSerialization;
using VersionedSerialization.Attributes;

View File

@@ -5,15 +5,14 @@
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using dnlib.DotNet;
using dnlib.DotNet.Emit;
using Il2CppInspector.Next;
using Il2CppInspector.Next.Metadata;
using Il2CppInspector.Reflection;
using AssemblyNameFlags = System.Reflection.AssemblyNameFlags;
namespace Il2CppInspector.Outputs
{
@@ -88,6 +87,9 @@ namespace Il2CppInspector.Outputs
private TypeDef metadataPreviewAttribute;
private TypeDef tokenAttribute;
// Runtime attributes we need to add
private TypeInfo inlineArrayAttribute;
// The namespace for our custom types
private const string rootNamespace = "Il2CppInspector.DLL";
@@ -162,6 +164,14 @@ namespace Il2CppInspector.Outputs
return module;
}
private void GetBuiltinAttributes()
{
if (model.Package.Version >= MetadataVersions.V1040)
{
inlineArrayAttribute = model.TypesByFullName["System.Runtime.CompilerServices.InlineArrayAttribute"];
}
}
// Create a new DLL assembly definition
private ModuleDefUser CreateAssembly(string name) {
// Create module
@@ -176,6 +186,34 @@ namespace Il2CppInspector.Outputs
return module;
}
private AssemblyDefUser CreateAssembly(Il2CppAssemblyNameDefinition nameDefinition)
{
var name = model.Package.Strings[nameDefinition.NameIndex];
var version = new Version(nameDefinition.Major, nameDefinition.Minor, nameDefinition.Build,
nameDefinition.Revision);
return new AssemblyDefUser(name, version)
{
PublicKey = new PublicKey(model.Package.AssemblyPublicKeys[nameDefinition.PublicKeyIndex]),
Culture = model.Package.Strings[nameDefinition.CultureIndex],
HashAlgorithm = (AssemblyHashAlgorithm)nameDefinition.HashAlg,
HasPublicKey = nameDefinition.Flags.HasFlag(AssemblyNameFlags.PublicKey)
};
}
private ModuleDefUser CreateAssembly(Assembly assembly)
{
var module = new ModuleDefUser(assembly.ShortName)
{
Kind = ModuleKind.Dll
};
var asm = CreateAssembly(assembly.AssemblyDefinition.Aname);
asm.Modules.Add(module);
return module;
}
// Create a shallow type definition that only populates the type itself and its nested types.
// Used for custom attributes.
private TypeDefUser CreateTypeShallow(ModuleDef module, TypeInfo type)
@@ -245,6 +283,20 @@ namespace Il2CppInspector.Outputs
if (type.Definition.IsValid)
mType.AddAttribute(module, tokenAttribute, ("Token", $"0x{type.MetadataToken:X8}"));
if (model.Package.Version >= MetadataVersions.V1040)
{
// L-TODO: Verify if we actually need to add the attribute ourselves; it might just be preserved
if (type.HasInlineArray)
{
var typeRef = GetTypeRef(module, inlineArrayAttribute);
var ctorRef = new MemberRefUser(typeRef.Module, ".ctor",
MethodSig.CreateInstance(module.CorLibTypes.Void, module.CorLibTypes.Int32), typeRef);
mType.CustomAttributes.Add(new CustomAttribute(ctorRef, new CAArgument[] {
new(module.CorLibTypes.Int32, type.InlineArrayLength)
}));
}
}
// Add custom attribute attributes
foreach (var ca in type.CustomAttributes)
AddCustomAttribute(module, mType, ca);
@@ -298,6 +350,10 @@ namespace Il2CppInspector.Outputs
private PropertyDef AddProperty(ModuleDef module, TypeDef mType, PropertyInfo prop) {
PropertySig s;
// Example: ZstdSharp MEM_32Bit which gets inlined using weaving and all accessors removed
if (prop.GetMethod == null && prop.SetMethod == null)
return null;
// Static or instance
if (prop.GetMethod?.IsStatic ?? prop.SetMethod.IsStatic)
s = PropertySig.CreateStatic(GetTypeSig(module, prop.PropertyType));
@@ -619,7 +675,7 @@ namespace Il2CppInspector.Outputs
foreach (var asm in model.Assemblies) {
// Create assembly and add primary module to list
var module = CreateAssembly(asm.ShortName);
var module = CreateAssembly(asm);
module.Context = ctx;
modules.Add(asm, module);
}
@@ -632,6 +688,10 @@ namespace Il2CppInspector.Outputs
baseDll.Write(Path.Combine(outputPath, baseDll.Name));
}
// Initialize required builtin attributes we might need to add
// ourselves
GetBuiltinAttributes();
// Add all types
foreach (var asm in model.Assemblies) {
statusCallback?.Invoke(this, "Preparing " + asm.ShortName);

View File

@@ -15,18 +15,22 @@ from binaryninja import (
)
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
try:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..shared_base import (
BaseStatusHandler,
BaseDisassemblerInterface,
ScriptContext,
)
import os
from datetime import datetime
from typing import Literal, Union
bv: BinaryView = None # type: ignore
except ImportError:
pass
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
@@ -71,7 +75,7 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
self._type_cache[type] = parsed
return parsed
def _parse_type_source(self, types: str, filename: str | None = None):
def _parse_type_source(self, types: str, filename: Union[str, None] = None):
parsed_types, errors = TypeParser.default.parse_types_from_source(
types,
filename if filename else "types.hpp",
@@ -127,7 +131,7 @@ class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
self._view.set_analysis_hold(False)
self._view.update_analysis()
def define_function(self, address: int, end: int | None = None):
def define_function(self, address: int, end: Union[int, None] = None):
if self._view.get_function_at(address) is not None:
return

View File

@@ -8,121 +8,145 @@ from ghidra.app.cmd.label import DemanglerCmd
from ghidra.app.services import DataTypeManagerService
from java.lang import Long
#try:
# from typing import TYPE_CHECKING
# if TYPE_CHECKING:
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
# import json
# import os
# import sys
# from datetime import datetime
#except:
# pass
try:
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from ..shared_base import (
BaseStatusHandler,
BaseDisassemblerInterface,
ScriptContext,
)
import json
import os
import sys
from datetime import datetime
except ImportError:
pass
class GhidraDisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment = False
supports_fake_string_segment = False
def _to_address(self, value):
return toAddr(Long(value))
def _to_address(self, value):
return toAddr(Long(value))
def get_script_directory(self) -> str:
return getSourceFile().getParentFile().toString()
def get_script_directory(self) -> str:
return getSourceFile().getParentFile().toString()
def on_start(self):
self.xrefs = currentProgram.getReferenceManager()
def on_start(self):
self.xrefs = currentProgram.getReferenceManager()
# 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()
# 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")
# 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)
def on_finish(self):
pass
# Don't trigger decompiler
setAnalysisOption(currentProgram, "Call Convention ID", "false")
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 on_finish(self):
pass
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 define_function(self, address: int, end: Union[int, None] = None):
address = self._to_address(address)
# Don't override existing functions
fn = getFunctionAt(address)
if fn is None:
# Create new function if none exists
createFunction(address, None)
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 define_data_array(self, address: int, type: str, count: int):
if type.startswith("struct "):
type = type[7:]
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)
t = getDataTypes(type)[0]
a = ArrayDataType(t, count, t.getLength())
address = self._to_address(address)
removeDataAt(address)
createData(address, a)
def set_data_comment(self, address: int, cmt: str):
setEOLComment(self._to_address(address), cmt)
def set_data_type(self, address: int, type: str):
if type.startswith("struct "):
type = type[7:]
def set_function_comment(self, address: int, cmt: str):
setPlateComment(self._to_address(address), cmt)
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_data_name(self, address: int, name: str):
address = self._to_address(address)
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)
if len(name) > 2000:
print("Name length exceeds 2000 characters, skipping (%s)" % name)
return
def set_data_comment(self, address: int, cmt: str):
setEOLComment(self._to_address(address), cmt)
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_comment(self, address: int, cmt: str):
setPlateComment(self._to_address(address), cmt)
def set_function_name(self, address: int, name: str):
return self.set_data_name(address, name)
def set_data_name(self, address: int, name: str):
address = self._to_address(address)
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)
if len(name) > 2000:
print("Name length exceeds 2000 characters, skipping (%s)" % name)
return
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
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
class GhidraStatusHandler(BaseStatusHandler):
pass
status = GhidraStatusHandler()
backend = GhidraDisassemblerInterface()
context = ScriptContext(backend, status)
context.process()
context.process()

View File

@@ -10,267 +10,303 @@ import ida_ua
import ida_segment
import ida_funcs
import ida_xref
import ida_pro
try: # 7.7+
import ida_srclang
IDACLANG_AVAILABLE = True
print("IDACLANG available")
except ImportError:
IDACLANG_AVAILABLE = False
if ida_pro.IDA_SDK_VERSION > 770:
import ida_srclang
import ida_dirtree
IDACLANG_AVAILABLE = True
FOLDERS_AVAILABLE = True
print("IDACLANG available")
else:
IDACLANG_AVAILABLE = False
FOLDERS_AVAILABLE = False
try:
import ida_dirtree
FOLDERS_AVAILABLE = True
print("folders available")
from typing import TYPE_CHECKING, Union
if TYPE_CHECKING:
from ..shared_base import (
BaseStatusHandler,
BaseDisassemblerInterface,
ScriptContext,
)
import os
from datetime import datetime
except ImportError:
FOLDERS_AVAILABLE = False
pass
#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: "ida_typeinf.til_t" = None # type: ignore
TINFO_DEFINITE = 0x0001 # These only exist in idc for some reason, so we redefine it here
DEFAULT_TIL: "til_t" = None # type: ignore
class IDADisassemblerInterface(BaseDisassemblerInterface):
supports_fake_string_segment = True
supports_fake_string_segment = True
_status: BaseStatusHandler
_status: BaseStatusHandler
_type_cache: dict
_folders: list
_function_dirtree: "ida_dirtree.dirtree_t"
_cached_genflags: int
_skip_function_creation: bool
_is_32_bit: bool
_fake_segments_base: int
_type_cache: dict
_folders: list[str]
def __init__(self, status: BaseStatusHandler):
self._status = status
self._type_cache = {}
self._folders = []
_function_dirtree: "ida_dirtree.dirtree_t"
_cached_genflags: int
_skip_function_creation: bool
_is_32_bit: bool
_fake_segments_base: int
self._cached_genflags = 0
self._skip_function_creation = False
self._is_32_bit = False
self._fake_segments_base = 0
def __init__(self, status: BaseStatusHandler):
self._status = status
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
self._type_cache = {}
self._folders = []
self._type_cache[type] = info[1:]
self._cached_genflags = 0
self._skip_function_creation = False
self._is_32_bit = False
self._fake_segments_base = 0
return self._type_cache[type]
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 get_script_directory(self) -> str:
return os.path.dirname(os.path.realpath(__file__))
self._type_cache[type] = info[1:]
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)
return self._type_cache[type]
# Unload type libraries we know to cause issues - like the c++ linux one
PLATFORMS = ["x86", "x64", "arm", "arm64"]
PROBLEMATIC_TYPELIBS = ["gnulnx"]
def get_script_directory(self) -> str:
return os.path.dirname(os.path.realpath(__file__))
for lib in PROBLEMATIC_TYPELIBS:
for platform in PLATFORMS:
ida_typeinf.del_til(f"{lib}_{platform}")
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)
# 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)
# Unload type libraries we know to cause issues - like the c++ linux one
PLATFORMS = ["x86", "x64", "arm", "arm64", "win7"]
PROBLEMATIC_TYPELIBS = ["gnulnx", "mssdk64"]
self._status.update_step('Processing Types')
for lib in PROBLEMATIC_TYPELIBS:
for platform in PLATFORMS:
ida_typeinf.del_til(f"{lib}_{platform}")
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)
# 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)
# 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")
self._status.update_step("Processing Types")
if FOLDERS_AVAILABLE:
self._function_dirtree = ida_dirtree.get_std_dirtree(ida_dirtree.DIRTREE_FUNCS)
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)
self._is_32_bit = ida_ida.inf_is_32bit_exactly()
# 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 on_finish(self):
ida_ida.inf_set_genflags(self._cached_genflags)
if FOLDERS_AVAILABLE:
self._function_dirtree = ida_dirtree.get_std_dirtree(
ida_dirtree.DIRTREE_FUNCS
)
def define_function(self, address: int, end: int | None = None):
if self._skip_function_creation:
return
self._is_32_bit = ida_ida.inf_is_32bit_exactly()
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")
def on_finish(self):
ida_ida.inf_set_genflags(self._cached_genflags)
def define_data_array(self, address: int, type: str, count: int):
self.set_data_type(address, type)
def define_function(self, address: int, end: Union[int, None] = None):
if self._skip_function_creation:
return
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
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"
)
ida_bytes.create_data(address, flags, count * entrySize, tid)
def define_data_array(self, address: int, type: str, count: int):
self.set_data_type(address, type)
def set_data_type(self, address: int, type: str):
type += ';'
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
info = self._get_type(type)
if info is None:
return
ida_bytes.create_data(address, flags, count * entrySize, tid)
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_data_type(self, address: int, type: str):
type += ";"
def set_function_type(self, address: int, type: str):
self.set_data_type(address, type)
info = self._get_type(type)
if info is None:
return
def set_data_comment(self, address: int, cmt: str):
ida_bytes.set_cmt(address, cmt, False)
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_comment(self, address: int, cmt: str):
func = ida_funcs.get_func(address)
if func is None:
return
def set_function_type(self, address: int, type: str):
self.set_data_type(address, type)
ida_funcs.set_func_cmt(func, cmt, True)
def set_data_comment(self, address: int, cmt: str):
ida_bytes.set_cmt(address, cmt, False)
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_comment(self, address: int, cmt: str):
func = ida_funcs.get_func(address)
if func is None:
return
def set_function_name(self, address: int, name: str):
self.set_data_name(address, name)
ida_funcs.set_func_cmt(func, cmt, True)
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 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 import_c_typedef(self, type_def: str):
ida_typeinf.idc_parse_types(type_def, 0)
def set_function_name(self, address: int, name: str):
self.set_data_name(address, name)
# 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
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)
if group not in self._folders:
self._folders.append(group)
self._function_dirtree.mkdir(group)
def import_c_typedef(self, type_def: str):
ida_typeinf.idc_parse_types(type_def, 0)
name = ida_funcs.get_func_name(address)
self._function_dirtree.rename(name, f"{group}/{name}")
# optional
def add_function_to_group(self, address: int, group: str):
if (
not FOLDERS_AVAILABLE or ida_pro.IDA_SDK_VERSION < 930
): # enable at your own risk on pre 9.3 - this is slow
return
# 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
if group not in self._folders:
self._folders.append(group)
self._function_dirtree.mkdir(group)
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()
name = ida_funcs.get_func_name(address)
self._function_dirtree.rename(name, f"{group}/{name}")
return start
# 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
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
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)
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 IDAStatusHandler(BaseStatusHandler):
def __init__(self):
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
def initialize(self):
ida_kernwin.show_wait_box("Processing")
def __init__(self):
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
def update(self):
if self.was_cancelled():
raise RuntimeError("Cancelled script.")
def initialize(self):
ida_kernwin.show_wait_box("Processing")
current_time = datetime.now()
if 0.5 > (current_time - self.last_updated_time).total_seconds():
return
def update(self):
if self.was_cancelled():
raise RuntimeError("Cancelled script.")
self.last_updated_time = current_time
current_time = datetime.now()
if 0.5 > (current_time - self.last_updated_time).total_seconds():
return
step_time = current_time - self.step_start_time
total_time = current_time - self.start_time
message = f"""
self.last_updated_time = current_time
step_time = current_time - self.step_start_time
total_time = current_time - self.start_time
message = f"""
Running IL2CPP script.
Current Step: {self.step}
Progress: {self.current_items}/{self.max_items}
Elapsed: {step_time} ({total_time})
"""
ida_kernwin.replace_wait_box(message)
ida_kernwin.replace_wait_box(message)
def update_step(self, step, max_items = 0):
print(step)
def update_step(self, step, max_items=0):
print(step)
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()
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 update_progress(self, new_progress=1):
self.current_items += new_progress
self.update()
def was_cancelled(self):
return ida_kernwin.user_cancelled()
def was_cancelled(self):
return ida_kernwin.user_cancelled()
def shutdown(self):
ida_kernwin.hide_wait_box()
def shutdown(self):
ida_kernwin.hide_wait_box()
status = IDAStatusHandler()
backend = IDADisassemblerInterface(status)
context = ScriptContext(backend, status)
context.process()
context.process()

View File

@@ -1,293 +1,387 @@
# @runtime PyGhidra
# ^-- required for ghidra, ignored by all others
# Generated script file by Il2CppInspectorRedux - https://github.com/LukeFZ (Original Il2CppInspector by http://www.djkaty.com - https://github.com/djkaty)
# Target Unity version: %TARGET_UNITY_VERSION%
import json
import os
from datetime import datetime
from typing import Union
import abc
class BaseStatusHandler(abc.ABC):
def initialize(self): pass
def shutdown(self): pass
def initialize(self):
pass
def update_step(self, name: str, max_items: int = 0): print(name)
def update_progress(self, progress: int = 1): pass
def shutdown(self):
pass
def update_step(self, name: str, max_items: int = 0):
print(name)
def update_progress(self, progress: int = 1):
pass
def was_cancelled(self):
return False
def was_cancelled(self): return False
class BaseDisassemblerInterface(abc.ABC):
supports_fake_string_segment: bool = False
supports_fake_string_segment: bool = False
@abc.abstractmethod
def get_script_directory(self) -> str: return ""
@abc.abstractmethod
def get_script_directory(self) -> str:
return ""
@abc.abstractmethod
def on_start(self): pass
@abc.abstractmethod
def on_start(self):
pass
@abc.abstractmethod
def on_finish(self): pass
@abc.abstractmethod
def on_finish(self):
pass
@abc.abstractmethod
def define_function(self, address: int, end: int | None = None): pass
@abc.abstractmethod
def define_function(self, address: int, end: Union[int, None] = None):
pass
@abc.abstractmethod
def define_data_array(self, address: int, type: str, count: int): pass
@abc.abstractmethod
def define_data_array(self, address: int, type: str, count: int):
pass
@abc.abstractmethod
def set_data_type(self, address: int, type: str): pass
@abc.abstractmethod
def set_data_type(self, address: int, type: str):
pass
@abc.abstractmethod
def set_function_type(self, address: int, type: str): pass
@abc.abstractmethod
def set_function_type(self, address: int, type: str):
pass
@abc.abstractmethod
def set_data_comment(self, address: int, cmt: str): pass
@abc.abstractmethod
def set_data_comment(self, address: int, cmt: str):
pass
@abc.abstractmethod
def set_function_comment(self, address: int, cmt: str): pass
@abc.abstractmethod
def set_function_comment(self, address: int, cmt: str):
pass
@abc.abstractmethod
def set_data_name(self, address: int, name: str): pass
@abc.abstractmethod
def set_data_name(self, address: int, name: str):
pass
@abc.abstractmethod
def set_function_name(self, address: int, name: str): pass
@abc.abstractmethod
def set_function_name(self, address: int, name: str):
pass
@abc.abstractmethod
def add_cross_reference(self, from_address: int, to_address: int): pass
@abc.abstractmethod
def add_cross_reference(self, from_address: int, to_address: int):
pass
@abc.abstractmethod
def import_c_typedef(self, type_def: str): pass
@abc.abstractmethod
def import_c_typedef(self, type_def: str):
pass
# optional
def add_function_to_group(self, address: int, group: str): pass
def cache_function_types(self, function_types: list[str]): pass
# optional
def add_function_to_group(self, address: int, group: str):
pass
# only required if supports_fake_string_segment == True
def create_fake_segment(self, name: str, size: int) -> int: return 0
def cache_function_types(self, function_types: list[str]):
pass
# only required if supports_fake_string_segment == True
def create_fake_segment(self, name: str, size: int) -> int:
return 0
def write_string(self, address: int, value: str) -> int:
return 0
def write_address(self, address: int, value: int):
pass
def write_string(self, address: int, value: str) -> int: pass
def write_address(self, address: int, value: int): pass
class ScriptContext:
_backend: BaseDisassemblerInterface
_status: BaseStatusHandler
_backend: BaseDisassemblerInterface
_status: BaseStatusHandler
def __init__(self, backend: BaseDisassemblerInterface, status: BaseStatusHandler) -> None:
self._backend = backend
self._status = status
def __init__(
self, backend: BaseDisassemblerInterface, status: BaseStatusHandler
) -> None:
self._backend = backend
self._status = status
def from_hex(self, addr: str):
return int(addr, 0)
def from_hex(self, addr: str):
return int(addr, 0)
def parse_address(self, d: dict):
return self.from_hex(d['virtualAddress'])
def parse_address(self, d: dict):
return self.from_hex(d["virtualAddress"])
def define_il_method(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_function_name(addr, definition['name'])
self._backend.set_function_type(addr, definition['signature'])
self._backend.set_function_comment(addr, definition['dotNetSignature'])
self._backend.add_function_to_group(addr, definition['group'])
def define_il_method(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_function_name(addr, definition["name"])
self._backend.set_function_type(addr, definition["signature"])
self._backend.set_function_comment(addr, definition["dotNetSignature"])
self._backend.add_function_to_group(addr, definition["group"])
def define_il_method_info(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_data_type(addr, r'struct MethodInfo *')
self._backend.set_data_name(addr, definition['name'])
self._backend.set_data_comment(addr, definition['dotNetSignature'])
if 'methodAddress' in definition:
method_addr = self.from_hex(definition["methodAddress"])
self._backend.add_cross_reference(method_addr, addr)
def define_cpp_function(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_function_name(addr, definition['name'])
self._backend.set_function_type(addr, definition['signature'])
def define_il_method_info(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_data_type(addr, r"struct MethodInfo *")
self._backend.set_data_name(addr, definition["name"])
self._backend.set_data_comment(addr, definition["dotNetSignature"])
if "methodAddress" in definition:
method_addr = self.from_hex(definition["methodAddress"])
self._backend.add_cross_reference(method_addr, addr)
def define_string(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_data_type(addr, r'struct String *')
self._backend.set_data_name(addr, definition['name'])
self._backend.set_data_comment(addr, definition['string'])
def define_cpp_function(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_function_name(addr, definition["name"])
self._backend.set_function_type(addr, definition["signature"])
def define_field(self, addr: str, name: str, type: str, il_type: str | None = None):
address = self.from_hex(addr)
self._backend.set_data_type(address, type)
self._backend.set_data_name(address, name)
if il_type is not None:
self._backend.set_data_comment(address, il_type)
def define_string(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_data_type(addr, r"struct String *")
self._backend.set_data_name(addr, definition["name"])
self._backend.set_data_comment(addr, definition["string"])
def define_field_from_json(self, definition: dict):
self.define_field(definition['virtualAddress'], definition['name'], definition['type'], definition['dotNetType'])
def define_field(
self, addr: str, name: str, type: str, il_type: Union[str, None] = None
):
address = self.from_hex(addr)
self._backend.set_data_type(address, type)
self._backend.set_data_name(address, name)
if il_type is not None:
self._backend.set_data_comment(address, il_type)
def define_array(self, definition: dict):
addr = self.parse_address(definition)
self._backend.define_data_array(addr, definition['type'], int(definition['count']))
self._backend.set_data_name(addr, definition['name'])
def define_field_from_json(self, definition: dict):
self.define_field(
definition["virtualAddress"],
definition["name"],
definition["type"],
definition["dotNetType"],
)
def define_field_with_value(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_data_name(addr, definition['name'])
self._backend.set_data_comment(addr, definition['value'])
def define_array(self, definition: dict):
addr = self.parse_address(definition)
self._backend.define_data_array(
addr, definition["type"], int(definition["count"])
)
self._backend.set_data_name(addr, definition["name"])
def process_metadata(self, metadata: dict):
# Function boundaries
function_addresses = metadata['functionAddresses']
function_addresses.sort()
count = len(function_addresses)
def define_field_with_value(self, definition: dict):
addr = self.parse_address(definition)
self._backend.set_data_name(addr, definition["name"])
self._backend.set_data_comment(addr, definition["value"])
self._status.update_step('Processing function boundaries', count)
for i in range(count):
start = self.from_hex(function_addresses[i])
if start == 0:
self._status.update_progress()
continue
def process_metadata(self, metadata: dict):
# Function boundaries
function_addresses = metadata["functionAddresses"]
function_addresses.sort()
count = len(function_addresses)
end = self.from_hex(function_addresses[i + 1]) if i + 1 != count else None
self._status.update_step("Processing function boundaries", count)
for i in range(count):
start = self.from_hex(function_addresses[i])
if start == 0:
self._status.update_progress()
continue
self._backend.define_function(start, end)
self._status.update_progress()
end = self.from_hex(function_addresses[i + 1]) if i + 1 != count else None
# Method definitions
self._status.update_step('Processing method definitions', len(metadata['methodDefinitions']))
self._backend.cache_function_types([x["signature"] for x in metadata['methodDefinitions']])
for d in metadata['methodDefinitions']:
self.define_il_method(d)
self._status.update_progress()
# Constructed generic methods
self._status.update_step('Processing constructed generic methods', len(metadata['constructedGenericMethods']))
self._backend.cache_function_types([x["signature"] for x in metadata['constructedGenericMethods']])
for d in metadata['constructedGenericMethods']:
self.define_il_method(d)
self._status.update_progress()
self._backend.define_function(start, end)
self._status.update_progress()
# Custom attributes generators
self._status.update_step('Processing custom attributes generators', len(metadata['customAttributesGenerators']))
self._backend.cache_function_types([x["signature"] for x in metadata['customAttributesGenerators']])
for d in metadata['customAttributesGenerators']:
self.define_cpp_function(d)
self._status.update_progress()
# Method.Invoke thunks
self._status.update_step('Processing Method.Invoke thunks', len(metadata['methodInvokers']))
self._backend.cache_function_types([x["signature"] for x in metadata['methodInvokers']])
for d in metadata['methodInvokers']:
self.define_cpp_function(d)
self._status.update_progress()
# Method definitions
self._status.update_step(
"Processing method definitions", len(metadata["methodDefinitions"])
)
self._backend.cache_function_types(
[x["signature"] for x in metadata["methodDefinitions"]]
)
for d in metadata["methodDefinitions"]:
self.define_il_method(d)
self._status.update_progress()
# String literals for version >= 19
if 'virtualAddress' in metadata['stringLiterals'][0]:
self._status.update_step('Processing string literals (V19+)', len(metadata['stringLiterals']))
# Constructed generic methods
self._status.update_step(
"Processing constructed generic methods",
len(metadata["constructedGenericMethods"]),
)
self._backend.cache_function_types(
[x["signature"] for x in metadata["constructedGenericMethods"]]
)
for d in metadata["constructedGenericMethods"]:
self.define_il_method(d)
self._status.update_progress()
if self._backend.supports_fake_string_segment:
total_string_length = 0
for d in metadata['stringLiterals']:
total_string_length += len(d["string"]) + 1
aligned_length = total_string_length + (4096 - (total_string_length % 4096))
segment_base = self._backend.create_fake_segment(".fake_strings", aligned_length)
# Custom attributes generators
self._status.update_step(
"Processing custom attributes generators",
len(metadata["customAttributesGenerators"]),
)
self._backend.cache_function_types(
[x["signature"] for x in metadata["customAttributesGenerators"]]
)
for d in metadata["customAttributesGenerators"]:
self.define_cpp_function(d)
self._status.update_progress()
current_string_address = segment_base
for d in metadata['stringLiterals']:
self.define_string(d)
# Method.Invoke thunks
self._status.update_step(
"Processing Method.Invoke thunks", len(metadata["methodInvokers"])
)
self._backend.cache_function_types(
[x["signature"] for x in metadata["methodInvokers"]]
)
for d in metadata["methodInvokers"]:
self.define_cpp_function(d)
self._status.update_progress()
ref_addr = self.parse_address(d)
written_string_length = self._backend.write_string(current_string_address, d["string"])
self._backend.set_data_type(ref_addr, r'const char* const')
self._backend.write_address(ref_addr, current_string_address)
# String literals for version >= 19
if "virtualAddress" in metadata["stringLiterals"][0]:
self._status.update_step(
"Processing string literals (V19+)", len(metadata["stringLiterals"])
)
current_string_address += written_string_length
self._status.update_progress()
else:
for d in metadata['stringLiterals']:
self.define_string(d)
self._status.update_progress()
if self._backend.supports_fake_string_segment:
total_string_length = 0
for d in metadata["stringLiterals"]:
total_string_length += len(d["string"]) + 1
# String literals for version < 19
else:
self._status.update_step('Processing string literals (pre-V19)')
litDecl = 'enum StringLiteralIndex {\n'
for d in metadata['stringLiterals']:
litDecl += " " + d['name'] + ",\n"
litDecl += '};\n'
aligned_length = total_string_length + (
4096 - (total_string_length % 4096)
)
segment_base = self._backend.create_fake_segment(
".fake_strings", aligned_length
)
self._backend.import_c_typedef(litDecl)
# Il2CppClass (TypeInfo) pointers
self._status.update_step('Processing Il2CppClass (TypeInfo) pointers', len(metadata['typeInfoPointers']))
for d in metadata['typeInfoPointers']:
self.define_field_from_json(d)
self._status.update_progress()
# Il2CppType (TypeRef) pointers
self._status.update_step('Processing Il2CppType (TypeRef) pointers', len(metadata['typeRefPointers']))
for d in metadata['typeRefPointers']:
self.define_field(d['virtualAddress'], d['name'], r'struct Il2CppType *', d['dotNetType'])
self._status.update_progress()
# MethodInfo pointers
self._status.update_step('Processing MethodInfo pointers', len(metadata['methodInfoPointers']))
for d in metadata['methodInfoPointers']:
self.define_il_method_info(d)
self._status.update_progress()
current_string_address = segment_base
for d in metadata["stringLiterals"]:
self.define_string(d)
# FieldInfo pointers, add the contents as a comment
self._status.update_step('Processing FieldInfo pointers', len(metadata['fields']))
for d in metadata['fields']:
self.define_field_with_value(d)
self._status.update_progress()
ref_addr = self.parse_address(d)
written_string_length = self._backend.write_string(
current_string_address, d["string"]
)
self._backend.set_data_type(ref_addr, r"const char* const")
self._backend.write_address(ref_addr, current_string_address)
# FieldRva pointers, add the contents as a comment
self._status.update_step('Processing FieldRva pointers', len(metadata['fieldRvas']))
for d in metadata['fieldRvas']:
self.define_field_with_value(d)
self._status.update_progress()
current_string_address += written_string_length
self._status.update_progress()
else:
for d in metadata["stringLiterals"]:
self.define_string(d)
self._status.update_progress()
# IL2CPP type metadata
self._status.update_step('Processing IL2CPP type metadata', len(metadata['typeMetadata']))
for d in metadata['typeMetadata']:
self.define_field(d['virtualAddress'], d['name'], d['type'])
# IL2CPP function metadata
self._status.update_step('Processing IL2CPP function metadata', len(metadata['functionMetadata']))
for d in metadata['functionMetadata']:
self.define_cpp_function(d)
# String literals for version < 19
else:
self._status.update_step("Processing string literals (pre-V19)")
litDecl = "enum StringLiteralIndex {\n"
for d in metadata["stringLiterals"]:
litDecl += " " + d["name"] + ",\n"
litDecl += "};\n"
# IL2CPP array metadata
self._status.update_step('Processing IL2CPP array metadata', len(metadata['arrayMetadata']))
for d in metadata['arrayMetadata']:
self.define_array(d)
self._backend.import_c_typedef(litDecl)
# IL2CPP API functions
self._status.update_step('Processing IL2CPP API functions', len(metadata['apis']))
self._backend.cache_function_types([x["signature"] for x in metadata['apis']])
for d in metadata['apis']:
self.define_cpp_function(d)
# Il2CppClass (TypeInfo) pointers
self._status.update_step(
"Processing Il2CppClass (TypeInfo) pointers",
len(metadata["typeInfoPointers"]),
)
for d in metadata["typeInfoPointers"]:
self.define_field_from_json(d)
self._status.update_progress()
def process(self):
self._status.initialize()
# Il2CppType (TypeRef) pointers
self._status.update_step(
"Processing Il2CppType (TypeRef) pointers", len(metadata["typeRefPointers"])
)
for d in metadata["typeRefPointers"]:
self.define_field(
d["virtualAddress"], d["name"], r"struct Il2CppType *", d["dotNetType"]
)
self._status.update_progress()
try:
start_time = datetime.now()
# MethodInfo pointers
self._status.update_step(
"Processing MethodInfo pointers", len(metadata["methodInfoPointers"])
)
for d in metadata["methodInfoPointers"]:
self.define_il_method_info(d)
self._status.update_progress()
self._status.update_step("Running script prologue")
self._backend.on_start()
# FieldInfo pointers, add the contents as a comment
self._status.update_step(
"Processing FieldInfo pointers", len(metadata["fields"])
)
for d in metadata["fields"]:
self.define_field_with_value(d)
self._status.update_progress()
metadata_path = os.path.join(self._backend.get_script_directory(), "%JSON_METADATA_RELATIVE_PATH%")
with open(metadata_path, "r") as f:
self._status.update_step("Loading JSON metadata")
metadata = json.load(f)['addressMap']
self.process_metadata(metadata)
# FieldRva pointers, add the contents as a comment
self._status.update_step(
"Processing FieldRva pointers", len(metadata["fieldRvas"])
)
for d in metadata["fieldRvas"]:
self.define_field_with_value(d)
self._status.update_progress()
self._status.update_step("Running script epilogue")
self._backend.on_finish()
# IL2CPP type metadata
self._status.update_step(
"Processing IL2CPP type metadata", len(metadata["typeMetadata"])
)
for d in metadata["typeMetadata"]:
self.define_field(d["virtualAddress"], d["name"], d["type"])
self._status.update_step('Script execution complete.')
# IL2CPP function metadata
self._status.update_step(
"Processing IL2CPP function metadata", len(metadata["functionMetadata"])
)
for d in metadata["functionMetadata"]:
self.define_cpp_function(d)
end_time = datetime.now()
print(f"Took: {end_time - start_time}")
# IL2CPP array metadata
self._status.update_step(
"Processing IL2CPP array metadata", len(metadata["arrayMetadata"])
)
for d in metadata["arrayMetadata"]:
self.define_array(d)
except RuntimeError:
pass
finally:
self._status.shutdown()
# IL2CPP API functions
self._status.update_step(
"Processing IL2CPP API functions", len(metadata["apis"])
)
self._backend.cache_function_types([x["signature"] for x in metadata["apis"]])
for d in metadata["apis"]:
self.define_cpp_function(d)
def process(self):
self._status.initialize()
try:
start_time = datetime.now()
self._status.update_step("Running script prologue")
self._backend.on_start()
metadata_path = os.path.join(
self._backend.get_script_directory(), "%JSON_METADATA_RELATIVE_PATH%"
)
with open(metadata_path, "r") as f:
self._status.update_step("Loading JSON metadata")
metadata = json.load(f)["addressMap"]
self.process_metadata(metadata)
self._status.update_step("Running script epilogue")
self._backend.on_finish()
self._status.update_step("Script execution complete.")
end_time = datetime.now()
print(f"Took: {end_time - start_time}")
except RuntimeError:
pass
finally:
self._status.shutdown()

View File

@@ -48,7 +48,7 @@ namespace Il2CppInspector.Reflection
public int MetadataToken { get; }
// Name of parameter
public string Name { get; }
public string Name { get; set; }
public string CSharpName => Constants.Keywords.Contains(Name) ? "@" + Name : Name.ToCIdentifier();
// Type of this parameter

View File

@@ -56,7 +56,14 @@ namespace Il2CppInspector.Reflection
return null;
if (IsArray)
return Assembly.Model.TypesByFullName["System.Array"];
if (IsEnum)
return Assembly.Model.TypesByFullName["System.Enum"];
if (Definition.IsValid) {
// L-NOTE: On metadata v35 and above, enums store their element type in the parent index field
// This means that we either have to implement a version check here, or handle enums in general.
// I chose to do the latter, which is why there is an if (IsEnum) check above.
if (Definition.ParentIndex >= 0)
return Assembly.Model.TypesByReferenceIndex[Definition.ParentIndex];
}
@@ -726,6 +733,9 @@ namespace Il2CppInspector.Reflection
set => @namespace = value;
}
public bool HasInlineArray => Definition.Bitfield.HasInlineArray;
public int InlineArrayLength => Assembly.Model.Package.TypeInlineArrays[Index];
// Number of dimensions of an array
private readonly int arrayRank;
public int GetArrayRank() => arrayRank;

View File

@@ -11,6 +11,7 @@ using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.NameTranslation;
namespace Il2CppInspector.Reflection
{
@@ -187,6 +188,19 @@ namespace Il2CppInspector.Reflection
PluginHooks.PostProcessTypeModel(this);
}
public void ApplyNameTranslationFromFile(string nameTranslationMapPath)
{
var lines = File.ReadAllLines(nameTranslationMapPath);
ApplyNameTranslation(lines);
}
public void ApplyNameTranslation(ReadOnlySpan<string> nameTranslationLines)
{
var info = NameTranslationParserContext.Parse(nameTranslationLines);
foreach (var assembly in Assemblies)
NameTranslationApplierContext.Process(assembly, info);
}
// Get generic arguments from either a type or method instanceIndex from a MethodSpec
public TypeInfo[] ResolveGenericArguments(Il2CppGenericInst inst) {

View File

@@ -2,21 +2,16 @@
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<TargetFramework>net10.0-windows</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<!-- Plugins may require bass class library assemblies we're not using so disable trimming -->
<PublishTrimmed>false</PublishTrimmed>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<UseWPF>true</UseWPF>
<AssemblyName>Il2CppInspector</AssemblyName>
<Version>2023.1</Version>
<Authors>Katy Coe, LukeFZ</Authors>
<Company>Noisy Cow Studios</Company>
<Product>Il2CppInspectorRedux Windows Edition</Product>
<ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Il2CppInspector.ico</ApplicationIcon>
<AssemblyVersion>2023.1.0.0</AssemblyVersion>
<FileVersion>2023.1.0.0</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@@ -28,13 +23,8 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Il2CppInspector.CLI\Utils.cs" Link="Utils.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ookii.Dialogs.Wpf" Version="5.0.1" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageReference Include="XamlAnimatedGif" Version="2.3.0">
<NoWarn>NU1701</NoWarn>
</PackageReference>

View File

@@ -58,8 +58,8 @@ namespace Il2CppInspectorGUI
// Find Unity paths
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
txtUnityPath.Text = Utils.FindPath($@"{programFiles}\Unity\Hub\Editor\*") ?? "<not set>";
txtUnityScriptPath.Text = Utils.FindPath($@"{programFiles}\Unity\Hub\Editor\*\Editor\Data\Resources\PackageManager\ProjectTemplates\libcache\com.unity.template.3d-*\ScriptAssemblies") ?? "<not set>";
txtUnityPath.Text = PathUtils.FindPath($@"{programFiles}\Unity\Hub\Editor\*") ?? "<not set>";
txtUnityScriptPath.Text = PathUtils.FindPath($@"{programFiles}\Unity\Hub\Editor\*\Editor\Data\Resources\PackageManager\ProjectTemplates\libcache\com.unity.template.3d-*\ScriptAssemblies") ?? "<not set>";
// Populate script target combo box and select IDA by default
cboPyTarget.ItemsSource = PythonScript.GetAvailableTargets();

View File

@@ -0,0 +1,48 @@
// Copyright (c) 2017-2021 Katy Coe - https://www.djkaty.com - https://github.com/djkaty
// All rights reserved
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace Il2CppInspector.GUI;
public static class PathUtils
{
public static string FindPath(string pathWithWildcards)
{
var absolutePath = Path.GetFullPath(pathWithWildcards);
if (!absolutePath.Contains('*', StringComparison.Ordinal))
return absolutePath;
// Backslash is a special character when evaluating regexes so Windows path separator must be escaped... with a backslash
var sections = new Regex(string.Format(@"((?:[^*]*){0})((?:.*?)\*.*?)(?:$|{0})",
Path.DirectorySeparatorChar == '\\' ? @"\\" : Path.DirectorySeparatorChar.ToString()));
var matches = sections.Matches(absolutePath);
var pathLength = 0;
var path = "";
foreach (Match match in matches)
{
path += match.Groups[1].Value;
var search = match.Groups[2].Value;
if (!Directory.Exists(path))
return null;
var dir = Directory.GetDirectories(path, search, SearchOption.TopDirectoryOnly)
.OrderByDescending(x => x)
.FirstOrDefault();
path = dir + Path.DirectorySeparatorChar;
pathLength += match.Groups[1].Value.Length + match.Groups[2].Value.Length + 1;
}
if (pathLength < absolutePath.Length)
path += absolutePath[pathLength..];
return path;
}
}

View File

@@ -61,6 +61,11 @@ public class CliClient : IDisposable
public async ValueTask<string> GetInspectorVersion(CancellationToken cancellationToken = default)
=> await _connection.InvokeAsync<string>(nameof(Il2CppHub.GetInspectorVersion), cancellationToken);
public async ValueTask SetSettings(InspectorSettings settings, CancellationToken cancellationToken = default)
{
await _connection.InvokeAsync(nameof(Il2CppHub.SetSettings), settings, cancellationToken);
}
public async ValueTask WaitForLoadingToFinishAsync(CancellationToken cancellationToken = default)
{
var currentLoadingCount = _finishedLoadingCount;

View File

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

View File

@@ -3,7 +3,7 @@ using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal abstract class ManualCommand<T>(PortProvider portProvider) : BaseCommand<T>(portProvider) where T : ManualCommandOptions
internal abstract class ManualCommand<T>(PortProvider portProvider) : BaseCommand<T>(portProvider) where T : ManualCommandSettings
{
public override ValidationResult Validate(CommandContext context, T settings)
{

View File

@@ -3,7 +3,7 @@ using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal class ManualCommandOptions : CommandSettings
internal class ManualCommandSettings : CommandSettings
{
[CommandArgument(0, "<InputPath>")]
[Description("Paths to the input files. Will be subsequently loaded until binary and metadata were found.")]

View File

@@ -1,16 +1,18 @@
using Il2CppInspector.Cpp;
using System.Globalization;
using Il2CppInspector.Cpp;
using Il2CppInspector.Redux.FrontendCore;
using Il2CppInspector.Redux.FrontendCore.Outputs;
using Spectre.Console;
using Spectre.Console.Cli;
namespace Il2CppInspector.Redux.CLI.Commands;
internal class ProcessCommand(PortProvider portProvider) : ManualCommand<ProcessCommand.Option>(portProvider)
internal sealed class ProcessCommand(PortProvider portProvider) : ManualCommand<ProcessCommand.Settings>(portProvider)
{
// NOTE: There might be a better option than replicating all available flags here (and in the TS UI).
// Investigate this in the future.
public class Option : ManualCommandOptions
public sealed class Settings : ManualCommandSettings
{
// C++ Scaffolding
[CommandOption("--output-cpp-scaffolding")]
@@ -67,13 +69,32 @@ internal class ProcessCommand(PortProvider portProvider) : ManualCommand<Process
[CommandOption("--extract-il2cpp-files")]
public string? ExtractIl2CppFilesPath { get; init; }
[CommandOption("--image-base")]
public string? ImageBase { get; init; }
[CommandOption("--name-translation-map")]
public string? NameTranslationMap { get; init; }
}
protected override async Task<int> ExecuteAsync(CliClient client, Option settings)
protected override async Task<int> ExecuteAsync(CliClient client, Settings settings)
{
var inspectorVersion = await client.GetInspectorVersion();
AnsiConsole.MarkupLineInterpolated($"Using inspector [gray]{inspectorVersion}[/]");
var imageBase = 0uL;
if (settings.ImageBase != null)
{
imageBase = ulong.Parse(settings.ImageBase,
settings.ImageBase.StartsWith("0x")
? NumberStyles.HexNumber
: NumberStyles.Integer);
AnsiConsole.MarkupLineInterpolated($"Setting image base to [white]0x{imageBase:x}[/]");
}
await client.SetSettings(new InspectorSettings(imageBase, settings.NameTranslationMap));
await client.SubmitInputFiles(settings.InputPaths.ToList());
await client.WaitForLoadingToFinishAsync();
if (!client.ImportCompleted)
@@ -148,7 +169,7 @@ internal class ProcessCommand(PortProvider portProvider) : ManualCommand<Process
return 0;
}
public override ValidationResult Validate(CommandContext context, Option settings)
public override ValidationResult Validate(CommandContext context, Settings settings)
{
if (settings.UnityPath != null && !Path.Exists(settings.UnityPath))
return ValidationResult.Error($"Provided Unity path {settings.UnityPath} does not exist.");
@@ -160,6 +181,9 @@ internal class ProcessCommand(PortProvider portProvider) : ManualCommand<Process
return ValidationResult.Error(
$"Provided extracted IL2CPP files path {settings.ExtractIl2CppFilesPath} already exists as a file.");
if (settings.NameTranslationMap != null && !File.Exists(settings.NameTranslationMap))
return ValidationResult.Error($"Provided name translation map {settings.NameTranslationMap} does not exist.");
if (settings is
{
CppScaffolding: false, CSharpStubs: false, DisassemblerMetadata: false, DummyDlls: false,

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>

View File

@@ -5,4 +5,5 @@ namespace Il2CppInspector.Redux.FrontendCore;
[JsonSerializable(typeof(string))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(InspectorSettings))]
public partial class FrontendCoreJsonSerializerContext : JsonSerializerContext;

View File

@@ -24,35 +24,40 @@ public class Il2CppHub : Hub
public async Task OnUiLaunched()
{
await State.Initialize(Client);
await State.InitializeAsync(Client);
}
public async Task SubmitInputFiles(List<string> inputFiles)
{
await State.LoadInputFiles(Client, inputFiles);
await State.LoadInputFilesAsync(Client, inputFiles);
}
public async Task QueueExport(string exportTypeId, string outputDirectory, Dictionary<string, string> settings)
{
await State.QueueExport(Client, exportTypeId, outputDirectory, settings);
await State.QueueExportAsync(Client, exportTypeId, outputDirectory, settings);
}
public async Task StartExport()
{
await State.StartExport(Client);
await State.StartExportAsync(Client);
}
public async Task<IEnumerable<string>> GetPotentialUnityVersions()
{
return await State.GetPotentialUnityVersions();
return await State.GetPotentialUnityVersionsAsync();
}
public async Task ExportIl2CppFiles(string outputDirectory)
{
await State.ExportIl2CppFiles(Client, outputDirectory);
await State.ExportIl2CppFilesAsync(Client, outputDirectory);
}
public async Task<string> GetInspectorVersion()
{
return await UiContext.GetInspectorVersion();
return await UiContext.GetInspectorVersionAsync();
}
public async Task SetSettings(InspectorSettings settings)
{
await State.SetSettingsAsync(Client, settings);
}
}

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>

View File

@@ -0,0 +1,3 @@
namespace Il2CppInspector.Redux.FrontendCore;
public sealed record InspectorSettings(ulong ImageBase = 0, string? NameTranslationMapPath = null);

View File

@@ -23,10 +23,11 @@ public class UiContext
private readonly List<UnityHeaders> _potentialUnityVersions = [];
private readonly LoadOptions _loadOptions = new();
private InspectorSettings _settings = new();
private readonly List<(string FormatId, string OutputDirectory, Dictionary<string, string> Settings)> _queuedExports = [];
private async Task<bool> TryLoadMetadataFromStream(UiClient client, MemoryStream stream)
private async Task<bool> TryLoadMetadataFromStreamAsync(UiClient client, MemoryStream stream)
{
try
{
@@ -41,7 +42,7 @@ public class UiContext
return false;
}
private async Task<bool> TryLoadBinaryFromStream(UiClient client, MemoryStream stream)
private async Task<bool> TryLoadBinaryFromStreamAsync(UiClient client, MemoryStream stream)
{
await client.ShowLogMessage("Processing binary");
@@ -64,7 +65,7 @@ public class UiContext
return false;
}
private async Task<bool> TryInitializeInspector(UiClient client)
private async Task<bool> TryInitializeInspectorAsync(UiClient client)
{
Debug.Assert(_binary != null);
Debug.Assert(_metadata != null);
@@ -96,6 +97,12 @@ public class UiContext
{
var typeModel = new TypeModel(inspector);
if (_settings.NameTranslationMapPath != null)
{
await client.ShowLogMessage("Applying name translation map");
typeModel.ApplyNameTranslationFromFile(_settings.NameTranslationMapPath);
}
// Just create the app model, do not initialize it - this is done lazily depending on the exports
_appModels.Add(new AppModel(typeModel, makeDefaultBuild: false));
}
@@ -117,25 +124,27 @@ public class UiContext
return true;
}
public async Task Initialize(UiClient client, CancellationToken cancellationToken = default)
public async Task InitializeAsync(UiClient client, CancellationToken cancellationToken = default)
{
await client.ShowSuccessToast("SignalR initialized!", cancellationToken);
}
public async Task LoadInputFiles(UiClient client, List<string> inputFiles,
public async Task LoadInputFilesAsync(UiClient client, List<string> inputFiles,
CancellationToken cancellationToken = default)
{
await using (await LoadingSession.Start(client))
{
_loadOptions.ImageBase = _settings.ImageBase;
var streams = Inspector.GetStreamsFromPackage(inputFiles);
if (streams != null)
{
// The input files contained a package that provides the metadata and binary.
// Use these instead of parsing all files individually.
if (!await TryLoadMetadataFromStream(client, streams.Value.Metadata))
if (!await TryLoadMetadataFromStreamAsync(client, streams.Value.Metadata))
return;
if (!await TryLoadBinaryFromStream(client, streams.Value.Binary))
if (!await TryLoadBinaryFromStreamAsync(client, streams.Value.Binary))
return;
}
else
@@ -152,7 +161,7 @@ public class UiContext
if ( _metadata == null && PathHeuristics.IsMetadataPath(inputFile))
{
if (await TryLoadMetadataFromStream(client, stream))
if (await TryLoadMetadataFromStreamAsync(client, stream))
{
await client.ShowSuccessToast($"Loaded metadata (v{_metadata!.Version}) from {inputFile}", cancellationToken);
}
@@ -160,9 +169,10 @@ public class UiContext
else if (_binary == null && PathHeuristics.IsBinaryPath(inputFile))
{
stream.Position = 0;
_loadOptions.BinaryFilePath = inputFile;
if (await TryLoadBinaryFromStream(client, stream))
if (await TryLoadBinaryFromStreamAsync(client, stream))
{
await client.ShowSuccessToast($"Loaded binary from {inputFile}", cancellationToken);
}
@@ -172,7 +182,7 @@ public class UiContext
if (_metadata != null && _binary != null)
{
if (await TryInitializeInspector(client))
if (await TryInitializeInspectorAsync(client))
{
await client.ShowSuccessToast($"Successfully loaded IL2CPP (v{_appModels[0].Package.Version}) data!", cancellationToken);
await client.OnImportCompleted(cancellationToken);
@@ -181,14 +191,14 @@ public class UiContext
}
}
public Task QueueExport(UiClient client, string exportFormatId, string outputDirectory,
public Task QueueExportAsync(UiClient client, string exportFormatId, string outputDirectory,
Dictionary<string, string> settings, CancellationToken cancellationToken = default)
{
_queuedExports.Add((exportFormatId, outputDirectory, settings));
return Task.CompletedTask;
}
public async Task StartExport(UiClient client, CancellationToken cancellationToken = default)
public async Task StartExportAsync(UiClient client, CancellationToken cancellationToken = default)
{
// todo: support different app model selection (when loading packages)
Debug.Assert(_appModels.Count > 0);
@@ -217,12 +227,12 @@ public class UiContext
await client.ShowSuccessToast("Export finished", cancellationToken);
}
public Task<List<string>> GetPotentialUnityVersions()
public Task<List<string>> GetPotentialUnityVersionsAsync()
{
return Task.FromResult(_potentialUnityVersions.Select(x => x.VersionRange.Min.ToString()).ToList());
}
public async Task ExportIl2CppFiles(UiClient client, string outputDirectory, CancellationToken cancellationToken = default)
public async Task ExportIl2CppFilesAsync(UiClient client, string outputDirectory, CancellationToken cancellationToken = default)
{
Debug.Assert(_appModels.Count > 0);
var pkg = _appModels[0].Package;
@@ -244,8 +254,14 @@ public class UiContext
}
}
public static Task<string> GetInspectorVersion()
public static Task<string> GetInspectorVersionAsync()
{
return Task.FromResult(typeof(UiContext).Assembly.GetAssemblyVersion() ?? "<unknown>");
}
public Task SetSettingsAsync(UiClient client, InspectorSettings settings)
{
_settings = settings;
return Task.CompletedTask;
}
}

View File

@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34227.203
# Visual Studio Version 18
VisualStudioVersion = 18.3.11222.16
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bin2Object", "Bin2Object\Bin2Object\Bin2Object.csproj", "{55382D6C-01B6-4AFD-850C-7A79DAB6F270}"
EndProject
@@ -21,6 +21,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
docs\CSProj_Preview.png = docs\CSProj_Preview.png
Directory.Build.props = Directory.Build.props
get-plugins.ps1 = get-plugins.ps1
get-plugins.sh = get-plugins.sh
docs\Ghidra_Guide.png = docs\Ghidra_Guide.png
@@ -38,6 +39,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{452BE076-A87D-4731-B223-2E249C00EBC1}"
ProjectSection(SolutionItems) = preProject
.github\workflows\build.yml = .github\workflows\build.yml
.github\workflows\release.yml = .github\workflows\release.yml
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VersionedSerialization", "VersionedSerialization\VersionedSerialization.csproj", "{803C3421-1907-4114-8B6B-F5E1789FD6A6}"

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<TargetFramework>net10.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

View File

@@ -1,4 +1,4 @@
# Il2CppInspectorRedux 2023.1
# Il2CppInspectorRedux 2025.1
Il2CppInspectorRedux helps you to reverse engineer IL2CPP applications, providing the most complete analysis currently available.
@@ -8,7 +8,7 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty
### Redux only features
* Support for metadata version 29/29.1/31/35/38, with full reconstruction of custom attributes
* Support for metadata version 29/29.1/31/35/38/39/104/105, with full reconstruction of custom attributes
* Proper extraction of static array initializer contents with their correct length
* Proper support for v27.2+ Il2CppType
* Fixed support for v24.5
@@ -32,19 +32,19 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty
> [!NOTE]
> As not all of the old UI features are implemented in the new UIs yet,
> the old ones are still built by GitHub Actions and available in the releases with the `Old` suffix.
> the old ones are still built by GitHub Actions and available in the releases with the `Legacy` suffix.
> [!IMPORTANT]
> The below README is still largely based on the original Il2CppInspector documentation,
> and may not reflect all of the changes/differences in Il2CppInspectorRedux.
> Notably, it still features the old UIs and not the new Redux ones,
> among others that have been removed in Redux.
> among other features that have been removed in Redux.
### Main features
* Output IL2CPP type definitions, metadata and method pointers as **[C# stub code](#creating-c-prototypes)**
* Create **.NET assembly shim DLLs** containing the IL2CPP application structure and metadata for use in decompilers such as [ILSpy](https://github.com/icsharpcode/ILSpy), [dnSpy](https://github.com/dnSpy/dnSpy), Unity asset loading with [AssetStudio](https://github.com/Perfare/AssetStudio) or managed proxy generation with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower)
* Create **.NET assembly shim DLLs** containing the IL2CPP application structure and metadata for use in decompilers such as [ILSpy](https://github.com/icsharpcode/ILSpy), [dnSpy](https://github.com/dnSpyEx/dnSpy), Unity asset loading with [AssetStudio](https://github.com/Perfare/AssetStudio) or managed proxy generation with [Il2CppAssemblyUnhollower](https://github.com/knah/Il2CppAssemblyUnhollower)
* Create **[C++ scaffolding](#creating-c-scaffolding-or-a-dll-injection-project)** for all types, methods, function pointers and API functions in an IL2CPP application for use in x64dbg, Cydia Substrate etc.
@@ -332,9 +332,9 @@ The `--seperate-attributes` switch directs Il2CppInspector to put assembly-level
### Adding metadata to your IDA workflow
**NOTE:** IDA 7.6+ is required, but 7.7 is recommended.
**NOTE:** IDA 7.6+ is required, but 7.7 is recommended. You also need to use Python 3.8+ to be able to run the script.
**NOTE:** Run script as-soon-as-possible after IDA loads binary into database
**NOTE:** Run script as-soon-as-possible after IDA loads binary into database!
Simply run Il2CppInspector with the `-p` switch to choose the IDA script output file. Load your binary file into IDA, press Alt+F7 and select the generated script. Observe the Output Window while IDA analyzes the file - this may take a long time.
@@ -769,6 +769,9 @@ Unity version | IL2CPP version | Support
2022.3.33-6000.2.x | 31(.1) | Working
6000.3.0a2 | 35 | Working
6000.3.0a5 | 38 | Working
6000.3.0b1 | 39 | Working
6000.5.0a3-6000.5.0a4 | 104 | Working
6000.5.0a5 | 105 | Working
Please refer to the companion repository https://github.com/nneonneo/Il2CppVersions if you would like to track the changes between each IL2CPP release version.

View File

@@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AnalyzerRoslynVersion>4.10</AnalyzerRoslynVersion>
<RoslynApiVersion>4.10.0</RoslynApiVersion>
<Nullable>enable</Nullable>

View File

@@ -1,17 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Any'">
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>