Implement new GUI and CLI, fix misc. smaller issues (#22)

* Initial commit of new UI c# component

* Initial commit of new UI frontend component

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

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

* fix workflow errors

* update dependencies and remove cxxdemangler, as it was outdated

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

* smaller tweaks, hack around loops in cpp type layouting

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

* add basic support for processing LC_DYLD_CHAINED_FIXUPS

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

* fix symbol table loading in some modified elfs

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

* embed ui executable directly into c# assembly

* only build tauri component in c# release builds

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

* fix and enable binary ninja fake string segment support

* add support for metadata

* unify logic for getting element type index

* fix new ui not allowing script exports other than ida

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

* fix toAddr calls in ghidra script target

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

* tweak symbol reading a bit and remove sht relocation reading

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

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

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

* remove debug log line

* fix spelling mistakes in gui outputs

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

* change the default port for new ui dev to 5000

* show current version and hash in new ui footer

* seperate redux ui impl into FrontendCore project

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

* add redux CLI based on redux GUI output formats

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

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

* disable aot publish and enable single file for redux cli
This commit is contained in:
Luke
2025-08-15 21:13:32 +02:00
committed by GitHub
parent e161e0f226
commit 3439ca912b
184 changed files with 13425 additions and 964 deletions

View File

@@ -3,7 +3,113 @@ name: Il2CppInspectorRedux Build
on: [push, workflow_dispatch] on: [push, workflow_dispatch]
jobs: jobs:
build-gui: build-redux-gui: # this already includes stuff only relevant for linux/macos for when the gui is released on those platforms
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false
- name: Setup Node.JS
uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Setup Tauri dependencies (ubuntu only)
if: matrix.platform == 'ubuntu-22.04'
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install frontend dependencies
run: pnpm install
working-directory: ./Il2CppInspector.Redux.GUI.UI
- uses: actions/cache@v4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-redux-gui-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-redux-gui-
- name: Restore NuGet packages
run: dotnet restore -r win-x64 ./Il2CppInspector.Redux.GUI
# note: we embed the exe directly into the c# component, and it it is built alongside it
# in another msbuild target.
- name: Build GUI
run: dotnet publish ./Il2CppInspector.Redux.GUI/Il2CppInspector.Redux.GUI.csproj -r win-x64 --no-self-contained
- name: Copy components to output directory
run: |
mkdir ./build_output
cp ./Il2CppInspector.Redux.GUI/bin/Release/net9.0/win-x64/publish/Il2CppInspector.Redux.GUI.exe ./build_output/
- name: Upload GUI Artifact
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.GUI
path: build_output
build-redux-cli:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [ '9.0.x' ]
rid: ['win-x64', 'linux-x64', 'linux-arm64', 'osx-x64', 'osx-arm64']
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- uses: actions/cache@v3
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-cli-${{ matrix.rid }}-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-cli-${{ matrix.rid }}-
- name: Setup .NET SDK ${{ matrix.dotnet-version }}
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{ matrix.dotnet-version }}
- name: Install dependencies
run: dotnet restore -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI
- name: Build & Publish
run: dotnet publish -c Release --no-self-contained --no-restore -o ./${{ matrix.rid }} -r ${{ matrix.rid }} ./Il2CppInspector.Redux.CLI/Il2CppInspector.Redux.CLI.csproj
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }}
build-old-gui:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
@@ -32,10 +138,10 @@ jobs:
- name: Upload GUI Artifact - name: Upload GUI Artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Il2CppInspectorRedux.GUI name: Il2CppInspectorRedux.Old.GUI
path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish path: Il2CppInspector.GUI/bin/Release/net9.0-windows/win-x64/publish
build-cli: build-old-cli:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
@@ -73,5 +179,5 @@ jobs:
- name: Upload artifacts - name: Upload artifacts
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: Il2CppInspectorRedux.CLI-${{ matrix.rid }} name: Il2CppInspectorRedux.Old.CLI-${{ matrix.rid }}
path: ./${{ matrix.rid }} path: ./${{ matrix.rid }}

3
.gitignore vendored
View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,12 +6,13 @@
*/ */
using Il2CppInspector.Next; using Il2CppInspector.Next;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using Spectre.Console;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics; using System.Diagnostics;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Il2CppInspector.Next.BinaryMetadata;
using Il2CppInspector.Next.Metadata;
using VersionedSerialization; using VersionedSerialization;
namespace Il2CppInspector namespace Il2CppInspector
@@ -191,7 +192,7 @@ namespace Il2CppInspector
var symbols = Image.GetSymbolTable(); var symbols = Image.GetSymbolTable();
if (symbols.Any()) { if (symbols.Any()) {
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries"); AnsiConsole.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
symbols.TryGetValue("g_CodeRegistration", out var code); symbols.TryGetValue("g_CodeRegistration", out var code);
symbols.TryGetValue("g_MetadataRegistration", out var metadata); symbols.TryGetValue("g_MetadataRegistration", out var metadata);
@@ -202,13 +203,13 @@ namespace Il2CppInspector
symbols.TryGetValue("_g_MetadataRegistration", out metadata); symbols.TryGetValue("_g_MetadataRegistration", out metadata);
if (code != null && metadata != null) { if (code != null && metadata != null) {
Console.WriteLine("Required structures acquired from symbol lookup"); AnsiConsole.WriteLine("Required structures acquired from symbol lookup");
return (code.VirtualAddress, metadata.VirtualAddress); return (code.VirtualAddress, metadata.VirtualAddress);
} else { } else {
Console.WriteLine("No matches in symbol table"); AnsiConsole.WriteLine("No matches in symbol table");
} }
} else if (symbols != null) { } else if (symbols != null) {
Console.WriteLine("No symbol table present in binary file"); AnsiConsole.WriteLine("No symbol table present in binary file");
} else { } else {
Console.WriteLine("Symbol table search not implemented for this binary format"); Console.WriteLine("Symbol table search not implemented for this binary format");
} }
@@ -227,12 +228,12 @@ namespace Il2CppInspector
var (code, metadata) = ConsiderCode(Image, loc); var (code, metadata) = ConsiderCode(Image, loc);
if (code != 0) { if (code != 0) {
RegistrationFunctionPointer = loc + Image.GlobalOffset; RegistrationFunctionPointer = loc + Image.GlobalOffset;
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer); AnsiConsole.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
return (code, metadata); return (code, metadata);
} }
} }
Console.WriteLine("No matches via code heuristics"); AnsiConsole.WriteLine("No matches via code heuristics");
return null; return null;
} }
@@ -244,11 +245,11 @@ namespace Il2CppInspector
var (codePtr, metadataPtr) = ImageScan(Metadata); var (codePtr, metadataPtr) = ImageScan(Metadata);
if (codePtr == 0) { if (codePtr == 0) {
Console.WriteLine("No matches via data heuristics"); AnsiConsole.WriteLine("No matches via data heuristics");
return null; return null;
} }
Console.WriteLine("Required structures acquired from data heuristics"); AnsiConsole.WriteLine("Required structures acquired from data heuristics");
return (codePtr, metadataPtr); return (codePtr, metadataPtr);
} }
@@ -274,8 +275,8 @@ namespace Il2CppInspector
var pointerSize = Image.Bits == 32 ? 4u : 8u; var pointerSize = Image.Bits == 32 ? 4u : 8u;
Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration)); AnsiConsole.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration)); AnsiConsole.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
// Root structures from which we find everything else // Root structures from which we find everything else
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration); CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
@@ -337,7 +338,15 @@ namespace Il2CppInspector
} }
// Read method invoker pointer indices - one per method // Read method invoker pointer indices - one per method
MethodInvokerIndices.Add(module, Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int) module.MethodPointerCount)); try
{
MethodInvokerIndices.Add(module,
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
}
catch (InvalidOperationException)
{
MethodInvokerIndices.Add(module, [..new int[(int)module.MethodPointerCount]]);
}
} }
} }
@@ -398,12 +407,21 @@ namespace Il2CppInspector
var type = TypeReferences[i]; var type = TypeReferences[i];
if (type.Type.IsTypeDefinitionEnum()) if (type.Type.IsTypeDefinitionEnum())
{ {
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize; if (type.Data.Type.PointerValue >= baseDefinitionPtr)
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize;
Debug.Assert(Metadata!.Types.Length > type.Data.KlassIndex);
} }
else if (type.Type.IsGenericParameterEnum()) else if (type.Type.IsGenericParameterEnum())
{ {
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize; if (type.Data.Type.PointerValue >= baseGenericPtr)
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize;
Debug.Assert(Metadata!.GenericParameters.Length > type.Data.KlassIndex);
} }
Debug.Assert((long)type.Data.Value >= 0);
builder.Add(type); builder.Add(type);
} }
TypeReferences = builder.MoveToImmutable(); TypeReferences = builder.MoveToImmutable();

View File

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

View File

@@ -92,7 +92,7 @@ namespace Il2CppInspector
// Set object versioning for Bin2Object from metadata version // Set object versioning for Bin2Object from metadata version
Version = new StructVersion(Header.Version); Version = new StructVersion(Header.Version);
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) { if (Version < MetadataVersions.V160 || Version > MetadataVersions.V350) {
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version})."); throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version}).");
} }
@@ -215,9 +215,26 @@ namespace Il2CppInspector
else { else {
var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>()); var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>());
StringLiterals = new string[stringLiteralList.Length]; if (Version >= MetadataVersions.V350)
for (var i = 0; i < stringLiteralList.Length; i++) {
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length); StringLiterals = new string[stringLiteralList.Length - 1];
for (var i = 0; i < stringLiteralList.Length; i++)
{
var currentStringDataIndex = stringLiteralList[i].DataIndex;
var nextStringDataIndex = stringLiteralList[i + 1].DataIndex;
var stringLength = nextStringDataIndex - currentStringDataIndex;
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + currentStringDataIndex, stringLength);
}
}
else
{
StringLiterals = new string[stringLiteralList.Length];
for (var i = 0; i < stringLiteralList.Length; i++)
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length);
}
} }
// Post-processing hook // Post-processing hook

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ public partial record struct Il2CppMethodDefinition
public TypeDefinitionIndex DeclaringType { get; private set; } public TypeDefinitionIndex DeclaringType { get; private set; }
public TypeIndex ReturnType { get; private set; } public TypeIndex ReturnType { get; private set; }
[VersionCondition(EqualTo = "31.0")] [VersionCondition(GreaterThan = "31.0")]
public uint ReturnParameterToken { get; private set; } public uint ReturnParameterToken { get; private set; }
public ParameterIndex ParameterStart { get; private set; } public ParameterIndex ParameterStart { get; private set; }

View File

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

View File

@@ -1,4 +1,5 @@
using System.Reflection; using System.Reflection;
using VersionedSerialization;
using VersionedSerialization.Attributes; using VersionedSerialization.Attributes;
namespace Il2CppInspector.Next.Metadata; namespace Il2CppInspector.Next.Metadata;
@@ -32,6 +33,8 @@ public partial record struct Il2CppTypeDefinition
public TypeIndex DeclaringTypeIndex { get; private set; } public TypeIndex DeclaringTypeIndex { get; private set; }
public TypeIndex ParentIndex { get; private set; } public TypeIndex ParentIndex { get; private set; }
[VersionCondition(LessThan = "31.0")]
public TypeIndex ElementTypeIndex { get; private set; } public TypeIndex ElementTypeIndex { get; private set; }
[VersionCondition(LessThan = "24.1")] [VersionCondition(LessThan = "24.1")]
@@ -80,4 +83,9 @@ public partial record struct Il2CppTypeDefinition
public uint Token { get; private set; } public uint Token { get; private set; }
public readonly bool IsValid => NameIndex != 0; public readonly bool IsValid => NameIndex != 0;
public int GetEnumElementTypeIndex(StructVersion version)
=> version >= MetadataVersions.V350
? ParentIndex
: ElementTypeIndex;
} }

View File

@@ -28,4 +28,7 @@ public static class MetadataVersions
// No tag - 29.0/31.0 // No tag - 29.0/31.0
public static readonly string Tag2022 = "2022"; // 29.1/31.1 public static readonly string Tag2022 = "2022"; // 29.1/31.1
// Unity 6000.3.0a2
public static readonly StructVersion V350 = new(35);
} }

View File

@@ -430,7 +430,7 @@ namespace Il2CppInspector.Outputs
if (method.VirtualAddress.HasValue) { if (method.VirtualAddress.HasValue) {
var args = new List<(string,object)> { var args = new List<(string,object)> {
("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), ("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))), ("Offset", $"0x{model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start):X}"),
("VA", method.VirtualAddress.Value.Start.ToAddressString()) ("VA", method.VirtualAddress.Value.Start.ToAddressString())
}; };
if (method.Definition.Slot != ushort.MaxValue) if (method.Definition.Slot != ushort.MaxValue)
@@ -470,7 +470,7 @@ namespace Il2CppInspector.Outputs
return def.AddAttribute(module, attributeAttribute, return def.AddAttribute(module, attributeAttribute,
("Name", ca.AttributeType.Name), ("Name", ca.AttributeType.Name),
("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), ("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start))) ("Offset", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@@ -0,0 +1,46 @@
{
"name": "il2cppinspectorredux",
"version": "0.1.0",
"description": "",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"tauri": "tauri"
},
"license": "MIT",
"dependencies": {
"@microsoft/signalr": "^8.0.7",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-opener": "^2"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.6",
"@sveltejs/kit": "^2.9.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"@tailwindcss/typography": "^0.5.16",
"@tauri-apps/cli": "^2",
"autoprefixer": "^10.4.20",
"bits-ui": "1.0.0-next.78",
"clsx": "^2.1.1",
"lucide-svelte": "^0.473.0",
"mode-watcher": "^0.5.0",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.10",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"svelte-sonner": "^0.3.28",
"tailwind-merge": "^2.6.0",
"tailwind-variants": "^0.3.1",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"typescript": "~5.6.2",
"vite": "^6.0.3"
},
"packageManager": "pnpm@10.0.0+sha512.b8fef5494bd3fe4cbd4edabd0745df2ee5be3e4b0b8b08fa643aa3e4c6702ccc0f00d68fa8a8c9858a735a0032485a44990ed2810526c875e416f001b17df12b"
}

2349
Il2CppInspector.Redux.GUI.UI/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
};

View File

@@ -0,0 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# Generated by Tauri
# will have schema files for capabilities auto-completion
/gen/schemas

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,26 @@
[package]
name = "il2cppinspectorredux"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "il2cppinspectorredux_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-opener = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-plugin-dialog = "2"

View File

@@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@@ -0,0 +1,13 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main"
],
"permissions": [
"core:default",
"opener:default",
"dialog:default"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -0,0 +1,20 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn get_signalr_url() -> String {
let args: Vec<String> = std::env::args().collect();
if args.len() < 2 {
return String::from("");
}
return args[1].clone();
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_opener::init())
.invoke_handler(tauri::generate_handler![get_signalr_url])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View File

@@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
il2cppinspectorredux_lib::run()
}

View File

@@ -0,0 +1,35 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Il2CppInspectorRedux",
"version": "0.1.0",
"identifier": "xyz.lukefz.il2cppinspectorredux",
"build": {
"beforeDevCommand": "pnpm dev",
"devUrl": "http://localhost:1420",
"beforeBuildCommand": "pnpm build",
"frontendDist": "../build"
},
"app": {
"windows": [
{
"title": "Il2CppInspectorRedux",
"width": 800,
"height": 600
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

View File

@@ -0,0 +1,66 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;;
--popover-foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 72.22% 50.59%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 5.9% 10%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--card: 240 10% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 240 10% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 240 5.9% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 240 3.7% 15.9%;
--muted-foreground: 240 5% 64.9%;
--accent: 240 3.7% 15.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 240 4.9% 83.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@layer base {
:root {
--header-height: 60px;
--footer-height: 30px;
--main-height: calc(100vh - var(--header-height) - var(--footer-height));
}
}

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