mirror of
https://github.com/LukeFZ/Il2CppInspectorRedux.git
synced 2026-01-18 08:39:39 +05:00
Compare commits
53 Commits
38aa333764
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbdf9e2123 | ||
|
|
0a66441bf0 | ||
|
|
0cfb90a0b7 | ||
|
|
f757d2c4d6 | ||
|
|
41d9a52ad6 | ||
|
|
4d396847fa | ||
|
|
8c2f7a9960 | ||
|
|
9fe77fdb1e | ||
|
|
12887c99df | ||
|
|
25b9ff03e6 | ||
|
|
d314004185 | ||
|
|
776c507a36 | ||
|
|
14821d023c | ||
|
|
4b5a860381 | ||
|
|
cfccc6982c | ||
|
|
e1b64dfd65 | ||
|
|
c72bd2174e | ||
|
|
73b1594a0e | ||
|
|
67f3fbe35c | ||
|
|
64a2bb3db7 | ||
|
|
8c0ef09a77 | ||
|
|
b0a82f1dd0 | ||
|
|
77b4b60014 | ||
|
|
9d6b0c0d82 | ||
|
|
51af520405 | ||
|
|
dd50c5d89e | ||
|
|
c9197a22f7 | ||
|
|
332e3d4b27 | ||
|
|
d872ac8014 | ||
|
|
750a0c88d5 | ||
|
|
c3cc327feb | ||
|
|
f8436517dc | ||
|
|
d3d5ea4834 | ||
|
|
e25ea95ec6 | ||
|
|
746abe53e3 | ||
|
|
982396505d | ||
|
|
9718c3025b | ||
|
|
5253bfb34a | ||
|
|
9bd32cee84 | ||
|
|
16b1cffc0c | ||
|
|
0da6aa67c5 | ||
|
|
e09776b4c8 | ||
|
|
4befde8ab4 | ||
|
|
6d674ecc8c | ||
|
|
8b93dda191 | ||
|
|
bba8a2913a | ||
|
|
193395db29 | ||
|
|
481d05668d | ||
|
|
ca6c958f9a | ||
|
|
7a621b40c6 | ||
|
|
1a418280fb | ||
|
|
f1a69cafe3 | ||
|
|
e5f2fa703d |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
Il2CppTests/TestExpectedResults/* linguist-vendored
|
||||
Il2CppInspector.Common/Cpp/Il2CppAPIHeaders/* linguist-vendored
|
||||
Il2CppInspector.Common/Cpp/UnityHeaders/* linguist-vendored
|
||||
132
.github/workflows/build.yml
vendored
132
.github/workflows/build.yml
vendored
@@ -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
33
.github/workflows/release.yml
vendored
Normal 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
2
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "Bin2Object"]
|
||||
path = Bin2Object
|
||||
url = https://github.com/djkaty/Bin2Object
|
||||
url = https://github.com/LukeFZ/Bin2Object
|
||||
|
||||
Submodule Bin2Object updated: 3b09026a0d...dd090deedb
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
47
Il2CppInspector.CLI/PathUtils.cs
Normal file
47
Il2CppInspector.CLI/PathUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
178
Il2CppInspector.Common/Cpp/CppTypeDependencyGraph.cs
Normal file
178
Il2CppInspector.Common/Cpp/CppTypeDependencyGraph.cs
Normal 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 };
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Diagnostics;
|
||||
#nullable enable
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Reflection;
|
||||
|
||||
2421
Il2CppInspector.Common/Cpp/UnityHeaders/104-6000.5.0a3.h
vendored
Normal file
2421
Il2CppInspector.Common/Cpp/UnityHeaders/104-6000.5.0a3.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2421
Il2CppInspector.Common/Cpp/UnityHeaders/105-6000.5.0a5.h
vendored
Normal file
2421
Il2CppInspector.Common/Cpp/UnityHeaders/105-6000.5.0a5.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2421
Il2CppInspector.Common/Cpp/UnityHeaders/35-6000.3.0a2.h
vendored
Normal file
2421
Il2CppInspector.Common/Cpp/UnityHeaders/35-6000.3.0a2.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2421
Il2CppInspector.Common/Cpp/UnityHeaders/38-6000.3.0a5.h
vendored
Normal file
2421
Il2CppInspector.Common/Cpp/UnityHeaders/38-6000.3.0a5.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
2421
Il2CppInspector.Common/Cpp/UnityHeaders/39-6000.3.0b1.h
vendored
Normal file
2421
Il2CppInspector.Common/Cpp/UnityHeaders/39-6000.3.0b1.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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->WithMetadataValue('ReferenceSourceTarget', 'ProjectReference'))" />
|
||||
|
||||
45
Il2CppInspector.Common/Next/Metadata/EventIndex.cs
Normal file
45
Il2CppInspector.Common/Next/Metadata/EventIndex.cs
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
55
Il2CppInspector.Common/Next/Metadata/IIndexType.cs
Normal file
55
Il2CppInspector.Common/Next/Metadata/IIndexType.cs
Normal 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>();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
namespace Il2CppInspector.Next.Metadata;
|
||||
|
||||
using StringIndex = int;
|
||||
using MethodIndex = int;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
[VersionedStruct]
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
using StringIndex = int;
|
||||
using AssemblyIndex = int;
|
||||
using MethodIndex = int;
|
||||
using CustomAttributeIndex = int;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using VersionedSerialization.Attributes;
|
||||
namespace Il2CppInspector.Next.Metadata;
|
||||
|
||||
using StringIndex = int;
|
||||
using ParameterIndex = int;
|
||||
|
||||
[VersionedStruct]
|
||||
public partial record struct Il2CppMethodDefinition
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
namespace Il2CppInspector.Next.Metadata;
|
||||
|
||||
using ParameterIndex = int;
|
||||
using DefaultValueDataIndex = int;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ using VersionedSerialization.Attributes;
|
||||
namespace Il2CppInspector.Next.Metadata;
|
||||
|
||||
using StringIndex = int;
|
||||
using MethodIndex = int;
|
||||
|
||||
[VersionedStruct]
|
||||
public partial record struct Il2CppPropertyDefinition
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
45
Il2CppInspector.Common/Next/Metadata/InterfacesIndex.cs
Normal file
45
Il2CppInspector.Common/Next/Metadata/InterfacesIndex.cs
Normal 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
|
||||
}
|
||||
45
Il2CppInspector.Common/Next/Metadata/MethodIndex.cs
Normal file
45
Il2CppInspector.Common/Next/Metadata/MethodIndex.cs
Normal 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
|
||||
}
|
||||
45
Il2CppInspector.Common/Next/Metadata/NestedTypeIndex.cs
Normal file
45
Il2CppInspector.Common/Next/Metadata/NestedTypeIndex.cs
Normal 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
|
||||
}
|
||||
45
Il2CppInspector.Common/Next/Metadata/ParameterIndex.cs
Normal file
45
Il2CppInspector.Common/Next/Metadata/ParameterIndex.cs
Normal 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
|
||||
}
|
||||
45
Il2CppInspector.Common/Next/Metadata/PropertyIndex.cs
Normal file
45
Il2CppInspector.Common/Next/Metadata/PropertyIndex.cs
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Collections.Frozen;
|
||||
|
||||
namespace Il2CppInspector.Next.NameTranslation;
|
||||
|
||||
public sealed record NameTranslationInfo(
|
||||
FrozenDictionary<string, string> NameTranslation,
|
||||
FrozenDictionary<string, string> ClassNamespaces
|
||||
);
|
||||
@@ -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..];
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using VersionedSerialization;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
#nullable enable
|
||||
using System.Collections.Immutable;
|
||||
using VersionedSerialization;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
48
Il2CppInspector.GUI/PathUtils.cs
Normal file
48
Il2CppInspector.GUI/PathUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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?");
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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.")]
|
||||
@@ -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,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
|
||||
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
|
||||
3
Il2CppInspector.Redux.FrontendCore/InspectorSettings.cs
Normal file
3
Il2CppInspector.Redux.FrontendCore/InspectorSettings.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace Il2CppInspector.Redux.FrontendCore;
|
||||
|
||||
public sealed record InspectorSettings(ulong ImageBase = 0, string? NameTranslationMapPath = null);
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<InvariantGlobalization>true</InvariantGlobalization>
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
17
README.md
17
README.md
@@ -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.
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<AnalyzerRoslynVersion>4.10</AnalyzerRoslynVersion>
|
||||
<RoslynApiVersion>4.10.0</RoslynApiVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user