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:
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,6 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
// A field in a C++ type
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
/// <summary>
|
||||
@@ -65,9 +62,9 @@ namespace Il2CppInspector.Cpp
|
||||
// Uniquely name an object within the parent namespace
|
||||
public string GetName(T t) {
|
||||
// If we've named this particular object before, just return that name
|
||||
string name;
|
||||
if (names.TryGetValue(t, out name))
|
||||
if (names.TryGetValue(t, out var name))
|
||||
return name;
|
||||
|
||||
// Obtain the mangled name for the object
|
||||
name = keyFunc(t);
|
||||
// Check if the mangled name has been given to another object - if it has,
|
||||
|
||||
@@ -456,4 +456,14 @@ namespace Il2CppInspector.Cpp
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public class CppForwardDefinitionType : CppType
|
||||
{
|
||||
public CppForwardDefinitionType(string name) : base(name)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override string ToString(string format = "") => $"struct {Name};";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
|
||||
namespace Il2CppInspector.Cpp
|
||||
{
|
||||
@@ -23,7 +19,7 @@ namespace Il2CppInspector.Cpp
|
||||
public Dictionary<string, CppType> Types { get; }
|
||||
|
||||
// All of the literal typedef aliases
|
||||
public Dictionary<string, CppType> TypedefAliases { get; } = new Dictionary<string, CppType>();
|
||||
public Dictionary<string, CppType> TypedefAliases { get; } = [];
|
||||
|
||||
public CppType this[string s] => Types.ContainsKey(s)? Types[s] :
|
||||
TypedefAliases.ContainsKey(s)? TypedefAliases[s].AsAlias(s) : null;
|
||||
@@ -34,7 +30,8 @@ namespace Il2CppInspector.Cpp
|
||||
// Architecture width in bits (32/64) - to determine pointer sizes
|
||||
public int WordSize { get; }
|
||||
|
||||
private Dictionary<string, ComplexValueType> complexTypeMap = new Dictionary<string, ComplexValueType> {
|
||||
private Dictionary<string, ComplexValueType> complexTypeMap = new()
|
||||
{
|
||||
["struct"] = ComplexValueType.Struct,
|
||||
["union"] = ComplexValueType.Union,
|
||||
["enum"] = ComplexValueType.Enum
|
||||
@@ -44,22 +41,23 @@ namespace Il2CppInspector.Cpp
|
||||
private string currentGroup = string.Empty;
|
||||
public void SetGroup(string group) => currentGroup = group;
|
||||
|
||||
private static readonly List<CppType> primitiveTypes = new List<CppType> {
|
||||
new CppType("uint8_t", 8),
|
||||
new CppType("uint16_t", 16),
|
||||
new CppType("uint32_t", 32),
|
||||
new CppType("uint64_t", 64),
|
||||
new CppType("int8_t", 8),
|
||||
new CppType("int16_t", 16),
|
||||
new CppType("int32_t", 32),
|
||||
new CppType("int64_t", 64),
|
||||
new CppType("char", 8),
|
||||
new CppType("int", 32),
|
||||
new CppType("float", 32),
|
||||
new CppType("double", 64),
|
||||
new CppType("bool", 8),
|
||||
new CppType("void", 0)
|
||||
};
|
||||
private static readonly List<CppType> primitiveTypes =
|
||||
[
|
||||
new("uint8_t", 8),
|
||||
new("uint16_t", 16),
|
||||
new("uint32_t", 32),
|
||||
new("uint64_t", 64),
|
||||
new("int8_t", 8),
|
||||
new("int16_t", 16),
|
||||
new("int32_t", 32),
|
||||
new("int64_t", 64),
|
||||
new("char", 8),
|
||||
new("int", 32),
|
||||
new("float", 32),
|
||||
new("double", 64),
|
||||
new("bool", 8),
|
||||
new("void", 0)
|
||||
];
|
||||
|
||||
public CppTypeCollection(int wordSize) {
|
||||
if (wordSize != 32 && wordSize != 64)
|
||||
@@ -538,15 +536,18 @@ namespace Il2CppInspector.Cpp
|
||||
public CppComplexType Struct(string name = "", int alignmentBytes = 0) {
|
||||
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
|
||||
return (CppComplexType) cppType;
|
||||
|
||||
var type = new CppComplexType(ComplexValueType.Struct) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Add(type);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public CppComplexType Union(string name = "", int alignmentBytes = 0) {
|
||||
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
|
||||
return (CppComplexType) cppType;
|
||||
|
||||
var type = new CppComplexType(ComplexValueType.Union) {Name = name, Group = currentGroup, AlignmentBytes = alignmentBytes};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Add(type);
|
||||
@@ -554,9 +555,13 @@ namespace Il2CppInspector.Cpp
|
||||
}
|
||||
|
||||
public CppEnumType Enum(CppType underlyingType, string name = "") {
|
||||
if (!string.IsNullOrEmpty(name) && Types.TryGetValue(name, out var cppType))
|
||||
return (CppEnumType)cppType;
|
||||
|
||||
var type = new CppEnumType(underlyingType) {Name = name, Group = currentGroup};
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
Add(type);
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -585,11 +590,17 @@ namespace Il2CppInspector.Cpp
|
||||
cppTypes.AddFromDeclarationText(apis);
|
||||
|
||||
// Don't allow any of the header type names or primitive type names to be re-used
|
||||
foreach (var type in cppTypes.Types.Values)
|
||||
declGen?.TypeNamespace.TryReserveName(type.Name);
|
||||
foreach (var type in cppTypes.Types.Keys)
|
||||
{
|
||||
declGen?.TypeNamespace.TryReserveName(type);
|
||||
declGen?.GlobalsNamespace.TryReserveName(type);
|
||||
}
|
||||
|
||||
foreach (var typedef in cppTypes.TypedefAliases.Values)
|
||||
declGen?.GlobalsNamespace.TryReserveName(typedef.Name);
|
||||
foreach (var typedef in cppTypes.TypedefAliases.Keys)
|
||||
{
|
||||
declGen?.TypeNamespace.TryReserveName(typedef);
|
||||
declGen?.GlobalsNamespace.TryReserveName(typedef);
|
||||
}
|
||||
|
||||
cppTypes.SetGroup("");
|
||||
|
||||
|
||||
@@ -5,13 +5,14 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Il2CppInspector.Next;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Next;
|
||||
using VersionedSerialization;
|
||||
|
||||
namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
@@ -140,7 +141,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
// No il2cpp exports? Just return the earliest version from the header range
|
||||
// The API version may be incorrect but should be a subset of the real API and won't cause C++ compile errors
|
||||
if (!exports.Any()) {
|
||||
Console.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project");
|
||||
AnsiConsole.WriteLine("No IL2CPP API exports found in binary - IL2CPP APIs will be unavailable in C++ project");
|
||||
|
||||
return typeHeaders.Select(t => new UnityHeaders(t,
|
||||
apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList();
|
||||
@@ -161,7 +162,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
|
||||
if (apiMatches.Any()) {
|
||||
// Intersect all API ranges with all header ranges to produce final list of possible ranges
|
||||
Console.WriteLine("IL2CPP API discovery was successful");
|
||||
AnsiConsole.WriteLine("IL2CPP API discovery was successful");
|
||||
|
||||
return typeHeaders.SelectMany(
|
||||
t => apiMatches.Where(a => t.VersionRange.Intersect(a.VersionRange) != null)
|
||||
@@ -170,7 +171,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
|
||||
|
||||
// None of the possible API versions match the binary
|
||||
// Select the oldest API version from the group - C++ project compilation will fail
|
||||
Console.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial");
|
||||
AnsiConsole.WriteLine("No exact match for IL2CPP APIs found in binary - IL2CPP API availability in C++ project will be partial");
|
||||
|
||||
return typeHeaders.Select(t => new UnityHeaders(t,
|
||||
apis.Last(a => a.VersionRange.Intersect(t.VersionRange) != null))).ToList();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -51,7 +52,7 @@ namespace Il2CppInspector
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
IFileFormatStream loaded = null;
|
||||
|
||||
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -51,7 +52,7 @@ namespace Il2CppInspector
|
||||
|
||||
public override IFileFormatStream this[uint index] {
|
||||
get {
|
||||
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
|
||||
IFileFormatStream loaded = null;
|
||||
|
||||
// ZipArchiveEntry does not support seeking so we have to close and re-open for each possible load format
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@@ -160,7 +161,7 @@ namespace Il2CppInspector
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
|
||||
AnsiConsole.WriteLine($"Got exception {ex} while parsing SHT - reverting to PHT");
|
||||
preferPHT = true;
|
||||
SHT = [];
|
||||
}
|
||||
@@ -170,12 +171,12 @@ namespace Il2CppInspector
|
||||
// These can happen as a result of conversions from other formats to ELF,
|
||||
// or if the SHT has been deliberately stripped
|
||||
if (!SHT.Any()) {
|
||||
Console.WriteLine("ELF binary has no SHT - reverting to PHT");
|
||||
AnsiConsole.WriteLine("ELF binary has no SHT - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
else if (SHT.All(s => conv.ULong(s.sh_addr) == 0ul)) {
|
||||
Console.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
|
||||
AnsiConsole.WriteLine("ELF binary SHT is all-zero - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
|
||||
@@ -192,7 +193,7 @@ namespace Il2CppInspector
|
||||
|
||||
// If the first file offset of the first PHT is zero, assume a dumped image
|
||||
if (PHT.Any(t => conv.ULong(t.p_vaddr) == 0ul)) {
|
||||
Console.WriteLine("ELF binary appears to be a dumped memory image");
|
||||
AnsiConsole.WriteLine("ELF binary appears to be a dumped memory image");
|
||||
isMemoryImage = true;
|
||||
}
|
||||
preferPHT = true;
|
||||
@@ -202,7 +203,7 @@ namespace Il2CppInspector
|
||||
else {
|
||||
var shtOverlap = shtShouldBeOrdered.Aggregate((x, y) => x <= y? y : ulong.MaxValue) == ulong.MaxValue;
|
||||
if (shtOverlap) {
|
||||
Console.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
|
||||
AnsiConsole.WriteLine("ELF binary SHT contains invalid ranges - reverting to PHT");
|
||||
preferPHT = true;
|
||||
}
|
||||
}
|
||||
@@ -223,7 +224,20 @@ namespace Il2CppInspector
|
||||
|
||||
// Get dynamic table if it exists (must be done after rebasing)
|
||||
if (GetProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC)
|
||||
DynamicTable = ReadArray<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
|
||||
{
|
||||
// Important: do not use p_offset here!
|
||||
// Only load sections should be loaded, which should also include the memory region that contains the dynamic section.
|
||||
// This just provides the virtual address of the section.
|
||||
// Some binaries may use the offset here to point to a fake version of the dynamic section,
|
||||
// making relocation resolution and subsequent analysis fail.
|
||||
// Reference for Android:
|
||||
// phdr_table_get_dynamic_section, https://cs.android.com/android/platform/superproject/main/+/main:bionic/linker/linker_phdr.cpp
|
||||
|
||||
var dynamicAddr = conv.ULong(PT_DYNAMIC.p_vaddr);
|
||||
var dynamicSize = (int)(conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>)));
|
||||
|
||||
DynamicTable = ReadMappedArray<elf_dynamic<TWord>>(dynamicAddr, dynamicSize);
|
||||
}
|
||||
|
||||
// Get offset of code section
|
||||
var codeSegment = PHT.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X);
|
||||
@@ -254,21 +268,6 @@ namespace Il2CppInspector
|
||||
|
||||
StatusUpdate("Finding relocations");
|
||||
|
||||
// Two types: add value from offset in image, and add value from specified addend
|
||||
foreach (var relSection in GetSections(Elf.SHT_REL)) {
|
||||
reverseMapExclusions.Add(((uint) conv.Int(relSection.sh_offset), (uint) (conv.Int(relSection.sh_offset) + conv.Int(relSection.sh_size) - 1)));
|
||||
rels.UnionWith(
|
||||
from rel in ReadArray<elf_rel<TWord>>(conv.Long(relSection.sh_offset), conv.Int(conv.Div(relSection.sh_size, relSection.sh_entsize)))
|
||||
select new ElfReloc(rel, SHT[relSection.sh_link].sh_offset));
|
||||
}
|
||||
|
||||
foreach (var relaSection in GetSections(Elf.SHT_RELA)) {
|
||||
reverseMapExclusions.Add(((uint) conv.Int(relaSection.sh_offset), (uint) (conv.Int(relaSection.sh_offset) + conv.Int(relaSection.sh_size) - 1)));
|
||||
rels.UnionWith(
|
||||
from rela in ReadArray<elf_rela<TWord>>(conv.Long(relaSection.sh_offset), conv.Int(conv.Div(relaSection.sh_size, relaSection.sh_entsize)))
|
||||
select new ElfReloc(rela, SHT[relaSection.sh_link].sh_offset));
|
||||
}
|
||||
|
||||
// Relocations in dynamic section
|
||||
if (GetDynamicEntry(Elf.DT_REL) is elf_dynamic<TWord> dt_rel) {
|
||||
var dt_rel_count = conv.Int(conv.Div(GetDynamicEntry(Elf.DT_RELSZ).d_un, GetDynamicEntry(Elf.DT_RELENT).d_un));
|
||||
@@ -291,7 +290,7 @@ namespace Il2CppInspector
|
||||
}
|
||||
|
||||
// Process relocations
|
||||
var relsz = Sizeof(typeof(TSym));
|
||||
var relsz = (uint)Sizeof(typeof(TSym));
|
||||
|
||||
var currentRel = 0;
|
||||
var totalRel = rels.Count();
|
||||
@@ -301,7 +300,26 @@ namespace Il2CppInspector
|
||||
if (currentRel % 1000 == 0)
|
||||
StatusUpdate($"Processing relocations ({currentRel * 100 / totalRel:F0}%)");
|
||||
|
||||
var symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz).st_value; // S
|
||||
TWord symValue;
|
||||
|
||||
try
|
||||
{
|
||||
// man this really needs a full overhaul
|
||||
symValue = ReadMappedObject<TSym>(conv.ULong(rel.SymbolTable) + conv.ULong(rel.SymbolIndex) * relsz)
|
||||
.st_value; // S
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
try
|
||||
{
|
||||
symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz)
|
||||
.st_value; // S
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore relocations into memory addresses not mapped from the image
|
||||
try {
|
||||
@@ -344,7 +362,7 @@ namespace Il2CppInspector
|
||||
WriteWord(result.newValue);
|
||||
}
|
||||
}
|
||||
Console.WriteLine($"Processed {rels.Count} relocations");
|
||||
AnsiConsole.WriteLine($"Processed {rels.Count} relocations");
|
||||
|
||||
// Build symbol and export tables
|
||||
processSymbols();
|
||||
@@ -388,7 +406,8 @@ namespace Il2CppInspector
|
||||
WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
|
||||
}
|
||||
|
||||
private void processSymbols() {
|
||||
private void processSymbols()
|
||||
{
|
||||
StatusUpdate("Processing symbols");
|
||||
|
||||
// Three possible symbol tables in ELF files
|
||||
@@ -436,7 +455,15 @@ namespace Il2CppInspector
|
||||
symbolTable.Clear();
|
||||
var exportTable = new Dictionary<string, Export>();
|
||||
|
||||
foreach (var pTab in pTables) {
|
||||
var alreadyProcessed = new List<(TWord offset, TWord count)>();
|
||||
|
||||
foreach (var pTab in pTables)
|
||||
{
|
||||
if (alreadyProcessed.Any(x =>
|
||||
conv.ULong(x.offset) == conv.ULong(pTab.offset)))
|
||||
continue;
|
||||
|
||||
alreadyProcessed.Add((pTab.offset, pTab.count));
|
||||
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
|
||||
|
||||
foreach (var symbol in symbol_table)
|
||||
@@ -463,7 +490,7 @@ namespace Il2CppInspector
|
||||
var symbolItem = new Symbol {Name = name, Type = type, VirtualAddress = conv.ULong(symbol.st_value) };
|
||||
symbolTable.TryAdd(name, symbolItem);
|
||||
if (symbol.st_shndx != (ushort) Elf.SHN_UNDEF)
|
||||
exportTable.TryAdd(name, new Export {Name = symbolItem.DemangledName, VirtualAddress = conv.ULong(symbol.st_value)});
|
||||
exportTable.TryAdd(name, new Export {Name = symbolItem.Name, VirtualAddress = conv.ULong(symbol.st_value)});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -176,7 +176,7 @@ namespace Il2CppInspector
|
||||
try {
|
||||
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
|
||||
null, new[] { typeof(BinaryObjectStream), typeof(LoadOptions), typeof(EventHandler<string>) }, null)
|
||||
.Invoke(null, new object[] { binaryObjectStream, loadOptions, statusCallback }) is IFileFormatStream loaded) {
|
||||
.Invoke(null, [binaryObjectStream, loadOptions, statusCallback]) is IFileFormatStream loaded) {
|
||||
|
||||
loaded.IsModified |= preProcessResult.IsStreamModified;
|
||||
return loaded;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -31,6 +32,7 @@ namespace Il2CppInspector
|
||||
LC_DYLD_INFO_ONLY = 0x80000022,
|
||||
LC_FUNCTION_STARTS = 0x26,
|
||||
LC_ENCRYPTION_INFO_64 = 0x2C,
|
||||
LC_DYLD_CHAINED_FIXUPS = 0x80000034,
|
||||
|
||||
CPU_TYPE_X86 = 7,
|
||||
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
|
||||
@@ -172,4 +174,47 @@ namespace Il2CppInspector
|
||||
public bool r_extern => ((r_data >> 27) & 1) == 1;
|
||||
public uint r_type => r_data >> 28;
|
||||
}
|
||||
|
||||
[VersionedStruct]
|
||||
public partial struct MachODyldChainedFixupsHeader
|
||||
{
|
||||
public uint FixupsVersion;
|
||||
public uint StartsOffset;
|
||||
public uint ImportsOffset;
|
||||
public uint SymbolsOffset;
|
||||
public uint ImportsCount;
|
||||
public uint ImportsFormat;
|
||||
public uint SymbolsFormat;
|
||||
}
|
||||
|
||||
[VersionedStruct]
|
||||
public partial struct MachODyldChainedStartsInSegment
|
||||
{
|
||||
public const ushort DYLD_CHAINED_PTR_START_NONE = 0xffff;
|
||||
|
||||
public uint StructSize;
|
||||
public ushort PageSize;
|
||||
public ushort PointerFormat;
|
||||
public ulong SegmentOffset;
|
||||
public uint MaxValidPointer;
|
||||
public ushort PageCount;
|
||||
}
|
||||
|
||||
public enum MachODyldChainedPtr
|
||||
{
|
||||
DYLD_CHAINED_PTR_64 = 2,
|
||||
DYLD_CHAINED_PTR_64_OFFSET = 6,
|
||||
}
|
||||
|
||||
[VersionedStruct]
|
||||
public partial struct MachODyldChainedPtr64Rebase
|
||||
{
|
||||
private ulong _value;
|
||||
|
||||
public ulong Target => _value & 0xfffffffff;
|
||||
public ulong High8 => (_value >> 36) & 0xff;
|
||||
public ulong Reserved => (_value >> (36 + 8)) & 0x7f;
|
||||
public ulong Next => (_value >> (36 + 8 + 7)) & 0xfff;
|
||||
public bool Bind => ((_value >> (36 + 8 + 7 + 12)) & 0x1) == 0x1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -172,15 +173,21 @@ namespace Il2CppInspector
|
||||
if (encryptionInfo.CryptID != 0)
|
||||
throw new NotImplementedException("This Mach-O executable is encrypted with FairPlay DRM and cannot be processed. Please provide a decrypted version of the executable.");
|
||||
break;
|
||||
|
||||
case MachO.LC_DYLD_CHAINED_FIXUPS:
|
||||
var chainedFixupsInfo = ReadObject<MachOLinkEditDataCommand>();
|
||||
ApplyChainedFixups(chainedFixupsInfo);
|
||||
break;
|
||||
}
|
||||
|
||||
// There might be other data after the load command so always use the specified total size to step forwards
|
||||
Position = startPos + loadCommand.Size;
|
||||
}
|
||||
|
||||
// Note: Some binaries do not have __mod_init_func, but instead just __init_offset with offsets to the init functions. This check is disabled.
|
||||
// Must find __mod_init_func
|
||||
if (funcTab == null)
|
||||
return false;
|
||||
//if (funcTab == null)
|
||||
// return false;
|
||||
|
||||
// Process relocations
|
||||
foreach (var section in machoSections) {
|
||||
@@ -188,7 +195,7 @@ namespace Il2CppInspector
|
||||
|
||||
// TODO: Implement Mach-O relocations
|
||||
if (rels.Any()) {
|
||||
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
|
||||
AnsiConsole.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -282,7 +289,7 @@ namespace Il2CppInspector
|
||||
: SymbolType.Unknown;
|
||||
|
||||
if (type == SymbolType.Unknown) {
|
||||
Console.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} " + CxxDemangler.CxxDemangler.Demangle(name));
|
||||
AnsiConsole.WriteLine($"Unknown symbol type: {((int) ntype):x2} {value:x16} {name}");
|
||||
}
|
||||
|
||||
// Ignore duplicates
|
||||
@@ -290,7 +297,82 @@ namespace Il2CppInspector
|
||||
}
|
||||
}
|
||||
|
||||
public override uint[] GetFunctionTable() => ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
|
||||
private void ApplyChainedFixups(in MachOLinkEditDataCommand info)
|
||||
{
|
||||
var chainedFixupsHeader = ReadVersionedObject<MachODyldChainedFixupsHeader>(info.Offset);
|
||||
if (chainedFixupsHeader.FixupsVersion != 0)
|
||||
{
|
||||
AnsiConsole.WriteLine($"Unsupported chained fixups version: {chainedFixupsHeader.FixupsVersion}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (chainedFixupsHeader.ImportsFormat != 1 /* DYLD_CHAINED_IMPORT */)
|
||||
{
|
||||
AnsiConsole.WriteLine($"Unsupported chained fixups import format: {chainedFixupsHeader.ImportsFormat}");
|
||||
return;
|
||||
}
|
||||
|
||||
//var importsBase = info.Offset + chainedFixupsHeader.ImportsOffset;
|
||||
//var imports = ReadPrimitiveArray<uint>(importsBase,
|
||||
// chainedFixupsHeader.ImportsCount);
|
||||
|
||||
//var symbolsBase = info.Offset + chainedFixupsHeader.SymbolsOffset; // todo: apparently this supports zlib
|
||||
|
||||
var startsBase = info.Offset + chainedFixupsHeader.StartsOffset;
|
||||
var segmentCount = ReadPrimitive<uint>(startsBase);
|
||||
var segmentStartOffsets = ReadPrimitiveArray<uint>(startsBase + 4, segmentCount);
|
||||
|
||||
foreach (var startOffset in segmentStartOffsets)
|
||||
{
|
||||
if (startOffset == 0)
|
||||
continue;
|
||||
|
||||
var startsInfo = ReadVersionedObject<MachODyldChainedStartsInSegment>(startsBase + startOffset);
|
||||
if (startsInfo.SegmentOffset == 0)
|
||||
continue;
|
||||
|
||||
var pointerFormat = (MachODyldChainedPtr)startsInfo.PointerFormat;
|
||||
|
||||
var pages = ReadPrimitiveArray<ushort>(
|
||||
startsBase + startOffset + MachODyldChainedStartsInSegment.Size(), startsInfo.PageCount);
|
||||
|
||||
for (var i = 0; i < pages.Length; i++)
|
||||
{
|
||||
var page = pages[i];
|
||||
if (page == MachODyldChainedStartsInSegment.DYLD_CHAINED_PTR_START_NONE)
|
||||
continue;
|
||||
|
||||
var chainOffset = startsInfo.SegmentOffset + (ulong)(i * startsInfo.PageSize) + page;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var currentEntry = ReadVersionedObject<MachODyldChainedPtr64Rebase>((long)chainOffset);
|
||||
|
||||
var fixedValue = 0ul;
|
||||
|
||||
if (!currentEntry.Bind) // todo: bind
|
||||
{
|
||||
fixedValue = pointerFormat switch
|
||||
{
|
||||
MachODyldChainedPtr.DYLD_CHAINED_PTR_64
|
||||
or MachODyldChainedPtr.DYLD_CHAINED_PTR_64_OFFSET
|
||||
=> currentEntry.High8 << 56 | currentEntry.Target,
|
||||
_ => fixedValue
|
||||
};
|
||||
|
||||
Write((long)chainOffset, fixedValue);
|
||||
}
|
||||
|
||||
if (currentEntry.Next == 0)
|
||||
break;
|
||||
|
||||
chainOffset += currentEntry.Next * 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override uint[] GetFunctionTable() => funcTab == null ? [] : ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
|
||||
|
||||
public override Dictionary<string, Symbol> GetSymbolTable() => symbolTable;
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text.RegularExpressions;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -102,7 +103,7 @@ namespace Il2CppInspector
|
||||
// Unpacking must be done starting here, one byte after the end of the headers
|
||||
// Packed or previously packed with Themida? This is purely for information
|
||||
if (sections.FirstOrDefault(x => x.Name == ".themida") is PESection _)
|
||||
Console.WriteLine("Themida protection detected");
|
||||
AnsiConsole.WriteLine("Themida protection detected");
|
||||
|
||||
// Packed with anything (including Themida)?
|
||||
mightBePacked = sections.FirstOrDefault(x => x.Name == ".rdata") is null;
|
||||
@@ -114,26 +115,31 @@ namespace Il2CppInspector
|
||||
section.Name = wantedSectionTypes[section.Characteristics];
|
||||
|
||||
// Get base of code
|
||||
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
|
||||
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections
|
||||
.FirstOrDefault(x => x.Characteristics.HasFlag(PE.IMAGE_SCN_MEM_EXECUTE))?.PointerToRawData ?? 0;
|
||||
|
||||
// Confirm that .rdata section begins at same place as IAT
|
||||
var rData = sections.First(x => x.Name == ".rdata");
|
||||
mightBePacked |= rData.VirtualAddress != IATStart;
|
||||
var rData = sections.FirstOrDefault(x => x.Name == ".rdata");
|
||||
mightBePacked |= rData == null || rData.VirtualAddress != IATStart;
|
||||
if (rData != null)
|
||||
{
|
||||
// Calculate start of function pointer table
|
||||
pFuncTable = rData.PointerToRawData + IATSize;
|
||||
|
||||
// Calculate start of function pointer table
|
||||
pFuncTable = rData.PointerToRawData + IATSize;
|
||||
|
||||
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
|
||||
Position = pFuncTable;
|
||||
if (pe is PEOptHeader32) {
|
||||
while (ReadUInt32() != 0)
|
||||
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
|
||||
Position = pFuncTable;
|
||||
if (pe is PEOptHeader32)
|
||||
{
|
||||
while (ReadUInt32() != 0)
|
||||
pFuncTable += 4;
|
||||
pFuncTable += 4;
|
||||
pFuncTable += 4;
|
||||
}
|
||||
else {
|
||||
while (ReadUInt64() != 0)
|
||||
}
|
||||
else
|
||||
{
|
||||
while (ReadUInt64() != 0)
|
||||
pFuncTable += 8;
|
||||
pFuncTable += 8;
|
||||
pFuncTable += 8;
|
||||
}
|
||||
}
|
||||
|
||||
// In the fist go round, we signal that this is at least a valid PE file; we don't try to unpack yet
|
||||
|
||||
@@ -4,11 +4,12 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
@@ -100,7 +101,7 @@ namespace Il2CppInspector
|
||||
// Get the entire remaining chunk, or to the end of the file if it doesn't contain the end of the chunk
|
||||
var length = (uint) Math.Min(chunk.Memory.End - memoryNext, source.Length);
|
||||
|
||||
Console.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}");
|
||||
AnsiConsole.WriteLine($"Writing {length:x8} bytes from {Path.GetFileName(file.Name)} +{fileStart:x8} ({memoryNext:x8}) to target {il2cpp.Position:x8}");
|
||||
|
||||
// Can't use Stream.CopyTo as it doesn't support length parameter
|
||||
var buffer = new byte[length];
|
||||
|
||||
@@ -20,7 +20,5 @@ namespace Il2CppInspector
|
||||
public ulong VirtualAddress { get; set; }
|
||||
public string Name { get; set; }
|
||||
public SymbolType Type { get; set; }
|
||||
|
||||
public string DemangledName => CxxDemangler.CxxDemangler.Demangle(Name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,13 @@
|
||||
*/
|
||||
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using Spectre.Console;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using VersionedSerialization;
|
||||
|
||||
namespace Il2CppInspector
|
||||
@@ -191,7 +192,7 @@ namespace Il2CppInspector
|
||||
var symbols = Image.GetSymbolTable();
|
||||
|
||||
if (symbols.Any()) {
|
||||
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
|
||||
AnsiConsole.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
|
||||
|
||||
symbols.TryGetValue("g_CodeRegistration", out var code);
|
||||
symbols.TryGetValue("g_MetadataRegistration", out var metadata);
|
||||
@@ -202,13 +203,13 @@ namespace Il2CppInspector
|
||||
symbols.TryGetValue("_g_MetadataRegistration", out metadata);
|
||||
|
||||
if (code != null && metadata != null) {
|
||||
Console.WriteLine("Required structures acquired from symbol lookup");
|
||||
AnsiConsole.WriteLine("Required structures acquired from symbol lookup");
|
||||
return (code.VirtualAddress, metadata.VirtualAddress);
|
||||
} else {
|
||||
Console.WriteLine("No matches in symbol table");
|
||||
AnsiConsole.WriteLine("No matches in symbol table");
|
||||
}
|
||||
} else if (symbols != null) {
|
||||
Console.WriteLine("No symbol table present in binary file");
|
||||
AnsiConsole.WriteLine("No symbol table present in binary file");
|
||||
} else {
|
||||
Console.WriteLine("Symbol table search not implemented for this binary format");
|
||||
}
|
||||
@@ -227,12 +228,12 @@ namespace Il2CppInspector
|
||||
var (code, metadata) = ConsiderCode(Image, loc);
|
||||
if (code != 0) {
|
||||
RegistrationFunctionPointer = loc + Image.GlobalOffset;
|
||||
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
|
||||
AnsiConsole.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
|
||||
return (code, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("No matches via code heuristics");
|
||||
AnsiConsole.WriteLine("No matches via code heuristics");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -244,11 +245,11 @@ namespace Il2CppInspector
|
||||
|
||||
var (codePtr, metadataPtr) = ImageScan(Metadata);
|
||||
if (codePtr == 0) {
|
||||
Console.WriteLine("No matches via data heuristics");
|
||||
AnsiConsole.WriteLine("No matches via data heuristics");
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine("Required structures acquired from data heuristics");
|
||||
AnsiConsole.WriteLine("Required structures acquired from data heuristics");
|
||||
return (codePtr, metadataPtr);
|
||||
}
|
||||
|
||||
@@ -274,8 +275,8 @@ namespace Il2CppInspector
|
||||
|
||||
var pointerSize = Image.Bits == 32 ? 4u : 8u;
|
||||
|
||||
Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
|
||||
Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
|
||||
AnsiConsole.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, Image.MapVATR(codeRegistration));
|
||||
AnsiConsole.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", Image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, Image.MapVATR(metadataRegistration));
|
||||
|
||||
// Root structures from which we find everything else
|
||||
CodeRegistration = Image.ReadMappedVersionedObject<Il2CppCodeRegistration>(codeRegistration);
|
||||
@@ -337,7 +338,15 @@ namespace Il2CppInspector
|
||||
}
|
||||
|
||||
// Read method invoker pointer indices - one per method
|
||||
MethodInvokerIndices.Add(module, Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int) module.MethodPointerCount));
|
||||
try
|
||||
{
|
||||
MethodInvokerIndices.Add(module,
|
||||
Image.ReadMappedPrimitiveArray<int>(module.InvokerIndices, (int)module.MethodPointerCount));
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
MethodInvokerIndices.Add(module, [..new int[(int)module.MethodPointerCount]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,12 +407,21 @@ namespace Il2CppInspector
|
||||
var type = TypeReferences[i];
|
||||
if (type.Type.IsTypeDefinitionEnum())
|
||||
{
|
||||
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize;
|
||||
if (type.Data.Type.PointerValue >= baseDefinitionPtr)
|
||||
type.Data.Value = (type.Data.Type.PointerValue - baseDefinitionPtr) / definitionSize;
|
||||
|
||||
Debug.Assert(Metadata!.Types.Length > type.Data.KlassIndex);
|
||||
}
|
||||
else if (type.Type.IsGenericParameterEnum())
|
||||
{
|
||||
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize;
|
||||
if (type.Data.Type.PointerValue >= baseGenericPtr)
|
||||
type.Data.Value = (type.Data.Type.PointerValue - baseGenericPtr) / genericParameterSize;
|
||||
|
||||
Debug.Assert(Metadata!.GenericParameters.Length > type.Data.KlassIndex);
|
||||
}
|
||||
|
||||
Debug.Assert((long)type.Data.Value >= 0);
|
||||
|
||||
builder.Add(type);
|
||||
}
|
||||
TypeReferences = builder.MoveToImmutable();
|
||||
|
||||
@@ -5,8 +5,11 @@
|
||||
*/
|
||||
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using Il2CppInspector.Utils;
|
||||
using NoisyCowStudios.Bin2Object;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -14,8 +17,6 @@ using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using VersionedSerialization;
|
||||
|
||||
namespace Il2CppInspector
|
||||
@@ -410,7 +411,7 @@ namespace Il2CppInspector
|
||||
if (metadataFile != null) {
|
||||
// Extract the metadata file to memory
|
||||
if (!silent)
|
||||
Console.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting metadata from (archive){Path.DirectorySeparatorChar}{metadataFile.FullName}");
|
||||
|
||||
metadataMemoryStream = new MemoryStream();
|
||||
using var metadataStream = metadataFile.Open();
|
||||
@@ -428,7 +429,7 @@ namespace Il2CppInspector
|
||||
// IPAs will only have one binary (which may or may not be a UB covering multiple architectures)
|
||||
if (ipaBinaryFolder != null) {
|
||||
if (!silent)
|
||||
Console.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
|
||||
AnsiConsole.WriteLine($"Extracting binary from {zipStreams.First()}{Path.DirectorySeparatorChar}{binaryFiles.First().FullName}");
|
||||
|
||||
// Extract the binary file or package to memory
|
||||
binaryMemoryStream = new MemoryStream();
|
||||
@@ -531,7 +532,7 @@ namespace Il2CppInspector
|
||||
return null;
|
||||
}
|
||||
|
||||
Console.WriteLine("Detected metadata version " + metadata.Version);
|
||||
AnsiConsole.WriteLine("Detected metadata version " + metadata.Version);
|
||||
|
||||
// Load the il2cpp code file (try all available file formats)
|
||||
IFileFormatStream stream;
|
||||
@@ -559,16 +560,16 @@ namespace Il2CppInspector
|
||||
|
||||
var processors = new List<Il2CppInspector>();
|
||||
foreach (var image in stream.Images) {
|
||||
Console.WriteLine("Container format: " + image.Format);
|
||||
Console.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness);
|
||||
Console.WriteLine("Architecture word size: {0}-bit", image.Bits);
|
||||
Console.WriteLine("Instruction set: " + image.Arch);
|
||||
Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);
|
||||
AnsiConsole.WriteLine("Container format: " + image.Format);
|
||||
AnsiConsole.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness);
|
||||
AnsiConsole.WriteLine("Architecture word size: {0}-bit", image.Bits);
|
||||
AnsiConsole.WriteLine("Instruction set: " + image.Arch);
|
||||
AnsiConsole.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);
|
||||
|
||||
// Architecture-agnostic load attempt
|
||||
try {
|
||||
if (Il2CppBinary.Load(image, metadata, statusCallback) is Il2CppBinary binary) {
|
||||
Console.WriteLine("IL2CPP binary version " + image.Version);
|
||||
AnsiConsole.WriteLine("IL2CPP binary version " + image.Version);
|
||||
|
||||
processors.Add(new Il2CppInspector(binary, metadata));
|
||||
}
|
||||
|
||||
@@ -92,7 +92,7 @@ namespace Il2CppInspector
|
||||
// Set object versioning for Bin2Object from metadata version
|
||||
Version = new StructVersion(Header.Version);
|
||||
|
||||
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V310) {
|
||||
if (Version < MetadataVersions.V160 || Version > MetadataVersions.V350) {
|
||||
throw new InvalidOperationException($"The supplied metadata file is not of a supported version ({Header.Version}).");
|
||||
}
|
||||
|
||||
@@ -215,9 +215,26 @@ namespace Il2CppInspector
|
||||
else {
|
||||
var stringLiteralList = ReadVersionedObjectArray<Il2CppStringLiteral>(Header.StringLiteralOffset, Header.StringLiteralSize / Sizeof<Il2CppStringLiteral>());
|
||||
|
||||
StringLiterals = new string[stringLiteralList.Length];
|
||||
for (var i = 0; i < stringLiteralList.Length; i++)
|
||||
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length);
|
||||
if (Version >= MetadataVersions.V350)
|
||||
{
|
||||
StringLiterals = new string[stringLiteralList.Length - 1];
|
||||
for (var i = 0; i < stringLiteralList.Length; i++)
|
||||
{
|
||||
var currentStringDataIndex = stringLiteralList[i].DataIndex;
|
||||
var nextStringDataIndex = stringLiteralList[i + 1].DataIndex;
|
||||
var stringLength = nextStringDataIndex - currentStringDataIndex;
|
||||
|
||||
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + currentStringDataIndex, stringLength);
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
StringLiterals = new string[stringLiteralList.Length];
|
||||
for (var i = 0; i < stringLiteralList.Length; i++)
|
||||
StringLiterals[i] = ReadFixedLengthString(Header.StringLiteralDataOffset + stringLiteralList[i].DataIndex, (int)stringLiteralList[i].Length);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Post-processing hook
|
||||
|
||||
@@ -41,10 +41,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="dnlib" Version="4.4.0" />
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="1.4.0" />
|
||||
<PackageReference Include="CxxDemangler" Version="0.2.4.11">
|
||||
<NoWarn>NU1605</NoWarn>
|
||||
</PackageReference>
|
||||
<PackageReference Include="McMaster.NETCore.Plugins" Version="2.0.0" />
|
||||
<PackageReference Include="Spectre.Console" Version="0.50.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -14,6 +14,7 @@ using Il2CppInspector.Cpp;
|
||||
using Il2CppInspector.Cpp.UnityHeaders;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Reflection;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Il2CppInspector.Model
|
||||
{
|
||||
@@ -40,6 +41,9 @@ namespace Il2CppInspector.Model
|
||||
// The types are ordered to enable the production of code output without forward dependencies
|
||||
public List<CppType> DependencyOrderedCppTypes { get; private set; }
|
||||
|
||||
// Required forward definition types for the C++ type definitions
|
||||
public List<CppType> RequiredForwardDefinitions { get; private set; } = [];
|
||||
|
||||
// Composite mapping of all the .NET methods in the IL2CPP binary
|
||||
public MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod> Methods { get; } = new MultiKeyDictionary<MethodBase, CppFnPtrType, AppMethod>();
|
||||
|
||||
@@ -149,12 +153,12 @@ namespace Il2CppInspector.Model
|
||||
UnityHeaders = unityVersion != null ? UnityHeaders.GetHeadersForVersion(unityVersion) : UnityHeaders.GuessHeadersForBinary(TypeModel.Package.Binary).Last();
|
||||
UnityVersion = unityVersion ?? UnityHeaders.VersionRange.Min;
|
||||
|
||||
Console.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})");
|
||||
AnsiConsole.WriteLine($"Selected Unity version(s) {UnityHeaders.VersionRange} (types: {UnityHeaders.TypeHeaderResource.VersionRange}, APIs: {UnityHeaders.APIHeaderResource.VersionRange})");
|
||||
|
||||
// Check for matching metadata and binary versions
|
||||
if (UnityHeaders.MetadataVersion != Image.Version) {
|
||||
Console.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
|
||||
$" does not match metadata version {Image.Version}.");
|
||||
AnsiConsole.WriteLine($"Warning: selected version {UnityVersion} (metadata version {UnityHeaders.MetadataVersion})" +
|
||||
$" does not match metadata version {Image.Version}.");
|
||||
}
|
||||
|
||||
// Initialize declaration generator to process every type in the binary
|
||||
@@ -236,8 +240,18 @@ namespace Il2CppInspector.Model
|
||||
break;
|
||||
case MetadataUsageType.MethodDef or MetadataUsageType.MethodRef:
|
||||
var method = TypeModel.GetMetadataUsageMethod(usage);
|
||||
|
||||
declarationGenerator.IncludeMethod(method);
|
||||
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||
var definitions = declarationGenerator.GenerateRemainingTypeDeclarations();
|
||||
if (definitions == null)
|
||||
{
|
||||
// if we end up here, type generation has failed
|
||||
// todo: this try/catch is a massive hack to sidestep the original issue of generation failing,
|
||||
// todo: this needs to be improved.
|
||||
break;
|
||||
}
|
||||
|
||||
AddTypes(definitions);
|
||||
|
||||
// Any method here SHOULD already be in the Methods list
|
||||
// but we have seen one example where this is not the case for a MethodDef
|
||||
@@ -247,6 +261,7 @@ namespace Il2CppInspector.Model
|
||||
Methods.Add(method, fnPtr, new AppMethod(method, fnPtr) { Group = Group });
|
||||
}
|
||||
Methods[method].MethodInfoPtrAddress = address;
|
||||
|
||||
break;
|
||||
|
||||
// FieldInfo is used for array initializers.
|
||||
@@ -294,6 +309,8 @@ namespace Il2CppInspector.Model
|
||||
declarationGenerator.IncludeType(type);
|
||||
AddTypes(declarationGenerator.GenerateRemainingTypeDeclarations());
|
||||
|
||||
RequiredForwardDefinitions = declarationGenerator.GenerateRequiredForwardDefinitions();
|
||||
|
||||
// Restore stdout
|
||||
Console.SetOut(stdout);
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ public partial record struct Il2CppCodeRegistration
|
||||
[NativeInteger]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
|
||||
[VersionCondition(GreaterThan = "35.0")]
|
||||
public uint UnresolvedIndirectCallCount; // UnresolvedVirtualCallCount pre 29.1
|
||||
|
||||
[VersionCondition(GreaterThan = "22.0")]
|
||||
@@ -84,10 +85,12 @@ public partial record struct Il2CppCodeRegistration
|
||||
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
|
||||
[VersionCondition(GreaterThan = "35.0")]
|
||||
public Pointer<Il2CppMethodPointer> UnresolvedInstanceCallWrappers;
|
||||
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2022"), VersionCondition(EqualTo = "31.0", IncludingTag = "2022")]
|
||||
[VersionCondition(EqualTo = "29.0", IncludingTag = "2023"), VersionCondition(EqualTo = "31.0", IncludingTag = "2023")]
|
||||
[VersionCondition(GreaterThan = "35.0")]
|
||||
public Pointer<Il2CppMethodPointer> UnresolvedStaticCallPointers;
|
||||
|
||||
[NativeInteger]
|
||||
|
||||
@@ -18,7 +18,7 @@ public partial record struct Il2CppMethodDefinition
|
||||
public TypeDefinitionIndex DeclaringType { get; private set; }
|
||||
public TypeIndex ReturnType { get; private set; }
|
||||
|
||||
[VersionCondition(EqualTo = "31.0")]
|
||||
[VersionCondition(GreaterThan = "31.0")]
|
||||
public uint ReturnParameterToken { get; private set; }
|
||||
|
||||
public ParameterIndex ParameterStart { get; private set; }
|
||||
|
||||
@@ -6,6 +6,7 @@ using StringLiteralIndex = int;
|
||||
[VersionedStruct]
|
||||
public partial record struct Il2CppStringLiteral
|
||||
{
|
||||
[VersionCondition(LessThan = "31.0")]
|
||||
public uint Length { get; private set; }
|
||||
public StringLiteralIndex DataIndex { get; private set; }
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Reflection;
|
||||
using VersionedSerialization;
|
||||
using VersionedSerialization.Attributes;
|
||||
|
||||
namespace Il2CppInspector.Next.Metadata;
|
||||
@@ -32,6 +33,8 @@ public partial record struct Il2CppTypeDefinition
|
||||
|
||||
public TypeIndex DeclaringTypeIndex { get; private set; }
|
||||
public TypeIndex ParentIndex { get; private set; }
|
||||
|
||||
[VersionCondition(LessThan = "31.0")]
|
||||
public TypeIndex ElementTypeIndex { get; private set; }
|
||||
|
||||
[VersionCondition(LessThan = "24.1")]
|
||||
@@ -80,4 +83,9 @@ public partial record struct Il2CppTypeDefinition
|
||||
public uint Token { get; private set; }
|
||||
|
||||
public readonly bool IsValid => NameIndex != 0;
|
||||
|
||||
public int GetEnumElementTypeIndex(StructVersion version)
|
||||
=> version >= MetadataVersions.V350
|
||||
? ParentIndex
|
||||
: ElementTypeIndex;
|
||||
}
|
||||
@@ -28,4 +28,7 @@ public static class MetadataVersions
|
||||
|
||||
// No tag - 29.0/31.0
|
||||
public static readonly string Tag2022 = "2022"; // 29.1/31.1
|
||||
|
||||
// Unity 6000.3.0a2
|
||||
public static readonly StructVersion V350 = new(35);
|
||||
}
|
||||
@@ -430,7 +430,7 @@ namespace Il2CppInspector.Outputs
|
||||
if (method.VirtualAddress.HasValue) {
|
||||
var args = new List<(string,object)> {
|
||||
("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
|
||||
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))),
|
||||
("Offset", $"0x{model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start):X}"),
|
||||
("VA", method.VirtualAddress.Value.Start.ToAddressString())
|
||||
};
|
||||
if (method.Definition.Slot != ushort.MaxValue)
|
||||
@@ -470,7 +470,7 @@ namespace Il2CppInspector.Outputs
|
||||
return def.AddAttribute(module, attributeAttribute,
|
||||
("Name", ca.AttributeType.Name),
|
||||
("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()),
|
||||
("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start)))
|
||||
("Offset", $"0x{model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start):X}")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -44,8 +44,13 @@ namespace Il2CppInspector.Outputs
|
||||
|
||||
// Write primitive type definitions for when we're not including other headers
|
||||
writeCode($"""
|
||||
#define IS_LIBCLANG_DECOMPILER (defined(_IDACLANG_) || defined(_BINARYNINJA_))
|
||||
#define IS_DECOMPILER (defined(_GHIDRA_) || defined(_IDA_) || IS_LIBCLANG_DECOMPILER)
|
||||
#if defined(_IDACLANG_) || defined(_BINARYNINJA_)
|
||||
#define IS_LIBCLANG_DECOMPILER
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDA_) || defined(IS_LIBCLANG_DECOMPILER)
|
||||
#define IS_DECOMPILER
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(_IDA_)
|
||||
typedef unsigned __int8 uint8_t;
|
||||
@@ -58,7 +63,7 @@ namespace Il2CppInspector.Outputs
|
||||
typedef __int64 int64_t;
|
||||
#endif
|
||||
|
||||
#if IS_LIBCLANG_DECOMPILER
|
||||
#if defined(IS_LIBCLANG_DECOMPILER)
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
@@ -67,21 +72,26 @@ namespace Il2CppInspector.Outputs
|
||||
typedef short int16_t;
|
||||
typedef int int32_t;
|
||||
typedef long int64_t;
|
||||
|
||||
#ifdef linux
|
||||
#undef linux
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || IS_LIBCLANG_DECOMPILER
|
||||
#endif
|
||||
|
||||
#if defined(_GHIDRA_) || defined(IS_LIBCLANG_DECOMPILER)
|
||||
typedef int{_model.Package.BinaryImage.Bits}_t intptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t uintptr_t;
|
||||
typedef uint{_model.Package.BinaryImage.Bits}_t size_t;
|
||||
#endif
|
||||
|
||||
#if !IS_DECOMPILER
|
||||
#ifndef IS_DECOMPILER
|
||||
#define _CPLUSPLUS_
|
||||
#endif
|
||||
""");
|
||||
|
||||
if (_useBetterArraySize)
|
||||
writeCode("#define actual_il2cpp_array_size_t il2cpp_array_size_t");
|
||||
writeCode("#define il2cpp_array_size_t actual_il2cpp_array_size_t");
|
||||
|
||||
writeSectionHeader("IL2CPP internal types");
|
||||
writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits));
|
||||
@@ -94,9 +104,7 @@ namespace Il2CppInspector.Outputs
|
||||
{
|
||||
int32_t size;
|
||||
actual_il2cpp_array_size_t value;
|
||||
} better_il2cpp_array_size_t;
|
||||
|
||||
#define better_il2cpp_array_size_t il2cpp_array_size_t
|
||||
} il2cpp_array_size_t;
|
||||
""");
|
||||
|
||||
if (_model.TargetCompiler == CppCompilerType.MSVC)
|
||||
@@ -115,17 +123,20 @@ namespace Il2CppInspector.Outputs
|
||||
}
|
||||
|
||||
// C does not support namespaces
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("#ifndef IS_DECOMPILER");
|
||||
writeCode("namespace app {");
|
||||
writeCode("#endif");
|
||||
writeLine("");
|
||||
|
||||
writeForwardDefinitions();
|
||||
|
||||
writeTypesForGroup("Required forward definitions", "required_forward_definitions");
|
||||
writeTypesForGroup("Application types from method calls", "types_from_methods");
|
||||
writeTypesForGroup("Application types from generic methods", "types_from_generic_methods");
|
||||
writeTypesForGroup("Application types from usages", "types_from_usages");
|
||||
writeTypesForGroup("Application unused value types", "unused_concrete_types");
|
||||
|
||||
writeCode("#if !IS_DECOMPILER");
|
||||
writeCode("#ifndef IS_DECOMPILER");
|
||||
writeCode("}");
|
||||
writeCode("#endif");
|
||||
}
|
||||
@@ -306,19 +317,32 @@ namespace Il2CppInspector.Outputs
|
||||
writeLine("");
|
||||
}
|
||||
|
||||
private void writeTypesForGroup(string header, string group) {
|
||||
private void writeForwardDefinitions()
|
||||
{
|
||||
writeSectionHeader("Required forward definitions");
|
||||
foreach (var cppType in _model.RequiredForwardDefinitions)
|
||||
writeCode(cppType.ToString());
|
||||
}
|
||||
|
||||
private void writeTypesForGroup(string header, string group)
|
||||
{
|
||||
writeSectionHeader(header);
|
||||
foreach (var cppType in _model.GetDependencyOrderedCppTypeGroup(group))
|
||||
if (cppType is CppEnumType) {
|
||||
{
|
||||
if (cppType is CppEnumType)
|
||||
{
|
||||
// Ghidra can't process C++ enum base types
|
||||
writeCode("#if defined(_CPLUSPLUS_)");
|
||||
writeCode(cppType.ToString());
|
||||
writeCode("#else");
|
||||
writeCode(cppType.ToString("c"));
|
||||
writeCode("#endif");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
writeCode(cppType.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCode(string text) {
|
||||
|
||||
@@ -217,7 +217,6 @@ namespace Il2CppInspector.Outputs
|
||||
foreach (var symbol in symbols) {
|
||||
writeObject(() => {
|
||||
writeName(symbol.VirtualAddress, symbol.Name);
|
||||
writer.WriteString("demangledName", symbol.DemangledName);
|
||||
writer.WriteString("type", symbol.Type.ToString());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# import sys
|
||||
# from datetime import datetime
|
||||
# from typing import Literal
|
||||
# bv: BinaryView = None # type: ignore
|
||||
#except:
|
||||
# pass
|
||||
# try:
|
||||
# from typing import TYPE_CHECKING
|
||||
# if TYPE_CHECKING:
|
||||
# from ..shared_base import BaseStatusHandler, BaseDisassemblerInterface, ScriptContext
|
||||
# import json
|
||||
# import os
|
||||
# import sys
|
||||
# from datetime import datetime
|
||||
# from typing import Literal
|
||||
# bv: BinaryView = None # type: ignore
|
||||
# except:
|
||||
# pass
|
||||
|
||||
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
|
||||
class BinaryNinjaDisassemblerInterface(BaseDisassemblerInterface):
|
||||
# this is implemented,
|
||||
# however the write API does not seem to work properly here (possibly a bug),
|
||||
# so this is disabled for now
|
||||
supports_fake_string_segment: bool = False
|
||||
supports_fake_string_segment: bool = True
|
||||
|
||||
_status: BaseStatusHandler
|
||||
|
||||
_view: BinaryView
|
||||
_undo_id: str
|
||||
_components: dict[str, Component]
|
||||
_type_cache: dict[str, Type]
|
||||
_function_type_cache: dict[str, Type]
|
||||
_status: BaseStatusHandler
|
||||
|
||||
_address_size: int
|
||||
_endianness: Literal["little", "big"]
|
||||
_view: BinaryView
|
||||
_undo_id: str
|
||||
_components: dict[str, Component]
|
||||
_type_cache: dict[str, Type]
|
||||
_function_type_cache: dict[str, Type]
|
||||
|
||||
TYPE_PARSER_OPTIONS = [
|
||||
"--target=x86_64-pc-linux",
|
||||
"-x", "c++",
|
||||
"-D_BINARYNINJA_=1"
|
||||
]
|
||||
_address_size: int
|
||||
_endianness: Literal["little", "big"]
|
||||
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
TYPE_PARSER_OPTIONS = ["--target=x86_64-pc-linux", "-x", "c++", "-D_BINARYNINJA_=1"]
|
||||
|
||||
def _get_or_create_type(self, type: str) -> Type:
|
||||
if type.startswith("struct "):
|
||||
type = type[len("struct "):]
|
||||
elif type.startswith("class "):
|
||||
type = type[len("class "):]
|
||||
def __init__(self, status: BaseStatusHandler):
|
||||
self._status = status
|
||||
|
||||
if type in self._type_cache:
|
||||
return self._type_cache[type]
|
||||
|
||||
if type.endswith("*"):
|
||||
base_type = self._get_or_create_type(type[:-1].strip())
|
||||
def _get_or_create_type(self, type: str) -> Type:
|
||||
if type.startswith("struct "):
|
||||
type = type[len("struct ") :]
|
||||
elif type.startswith("class "):
|
||||
type = type[len("class ") :]
|
||||
|
||||
parsed = PointerType.create(self._view.arch, base_type) # type: ignore
|
||||
else:
|
||||
parsed = self._view.get_type_by_name(type)
|
||||
if parsed is None:
|
||||
parsed, errors = self._view.parse_type_string(type)
|
||||
if type in self._type_cache:
|
||||
return self._type_cache[type]
|
||||
|
||||
self._type_cache[type] = parsed
|
||||
return parsed
|
||||
if type.endswith("*"):
|
||||
base_type = self._get_or_create_type(type[:-1].strip())
|
||||
|
||||
def _parse_type_source(self, types: str, filename: str | None = None):
|
||||
parsed_types, errors = TypeParser.default.parse_types_from_source(
|
||||
types,
|
||||
filename if filename else "types.hpp",
|
||||
self._view.platform if self._view.platform is not None else Platform["windows-x86_64"],
|
||||
self._view,
|
||||
self.TYPE_PARSER_OPTIONS
|
||||
)
|
||||
parsed = PointerType.create(self._view.arch, base_type) # type: ignore
|
||||
else:
|
||||
parsed = self._view.get_type_by_name(type)
|
||||
if parsed is None:
|
||||
parsed, errors = self._view.parse_type_string(type)
|
||||
|
||||
if parsed_types is None:
|
||||
log_error("Failed to import types.")
|
||||
log_error(errors)
|
||||
return None
|
||||
|
||||
return parsed_types
|
||||
self._type_cache[type] = parsed
|
||||
return parsed
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return CURRENT_PATH
|
||||
def _parse_type_source(self, types: str, filename: str | None = None):
|
||||
parsed_types, errors = TypeParser.default.parse_types_from_source(
|
||||
types,
|
||||
filename if filename else "types.hpp",
|
||||
self._view.platform
|
||||
if self._view.platform is not None
|
||||
else Platform["windows-x86_64"],
|
||||
self._view,
|
||||
self.TYPE_PARSER_OPTIONS,
|
||||
)
|
||||
|
||||
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 = {}
|
||||
if parsed_types is None:
|
||||
log_error("Failed to import types.")
|
||||
log_error(errors)
|
||||
return None
|
||||
|
||||
self._address_size = self._view.address_size
|
||||
self._endianness = "little" if self._view.endianness == Endianness.LittleEndian else "big"
|
||||
|
||||
self._status.update_step("Parsing header")
|
||||
return parsed_types
|
||||
|
||||
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f:
|
||||
parsed_types = self._parse_type_source(f.read(), "il2cpp.hpp")
|
||||
if parsed_types is None:
|
||||
return
|
||||
def get_script_directory(self) -> str:
|
||||
return CURRENT_PATH
|
||||
|
||||
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._status.update_progress(1)
|
||||
return True
|
||||
self._address_size = self._view.address_size
|
||||
self._endianness = (
|
||||
"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):
|
||||
self._view.commit_undo_actions(self._undo_id)
|
||||
self._view.set_analysis_hold(False)
|
||||
self._view.update_analysis()
|
||||
with open(os.path.join(self.get_script_directory(), "il2cpp.h"), "r") as f:
|
||||
parsed_types = self._parse_type_source(f.read(), "il2cpp.hpp")
|
||||
if parsed_types is None:
|
||||
return
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
if self._view.get_function_at(address) is not None:
|
||||
return
|
||||
|
||||
self._view.create_user_function(address)
|
||||
self._status.update_step("Importing header types", len(parsed_types.types))
|
||||
|
||||
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 import_progress_func(progress: int, total: int):
|
||||
self._status.update_progress(1)
|
||||
return True
|
||||
|
||||
def set_data_type(self, address: int, type: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
dtype = self._get_or_create_type(type)
|
||||
if var is None:
|
||||
self._view.define_user_data_var(address, dtype)
|
||||
else:
|
||||
var.type = dtype
|
||||
self._view.define_user_types(
|
||||
[(x.name, x.type) for x in parsed_types.types], import_progress_func
|
||||
)
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
if type in self._function_type_cache:
|
||||
function.type = self._function_type_cache[type] # type: ignore
|
||||
else:
|
||||
#log_info(f"skipping function type setting for {address}, {type}")
|
||||
#pass
|
||||
function.type = type.replace("this", "`this`")
|
||||
def on_finish(self):
|
||||
self._view.commit_undo_actions(self._undo_id)
|
||||
self._view.set_analysis_hold(False)
|
||||
self._view.update_analysis()
|
||||
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
self._view.set_comment_at(address, cmt)
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
if self._view.get_function_at(address) is not None:
|
||||
return
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
self._view.create_user_function(address)
|
||||
|
||||
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):
|
||||
var = self._view.get_data_var_at(address)
|
||||
if var is None:
|
||||
return
|
||||
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
var.name = get_qualified_name(demangled)
|
||||
else:
|
||||
var.name = name
|
||||
def set_data_type(self, address: int, type: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
dtype = self._get_or_create_type(type)
|
||||
if var is None:
|
||||
self._view.define_user_data_var(address, dtype)
|
||||
else:
|
||||
var.type = dtype
|
||||
|
||||
def set_function_name(self, address: int, name: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
def set_function_type(self, address: int, type: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
function.name = get_qualified_name(demangled)
|
||||
#function.type = type - this does not work due to the generated types not being namespaced. :(
|
||||
else:
|
||||
function.name = name
|
||||
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 add_cross_reference(self, from_address: int, to_address: int):
|
||||
self._view.add_user_data_ref(from_address, to_address)
|
||||
def set_data_comment(self, address: int, cmt: str):
|
||||
self._view.set_comment_at(address, cmt)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
self._view.define_user_type(None, type_def)
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
# optional
|
||||
def _get_or_create_component(self, name: str):
|
||||
if name in self._components:
|
||||
return self._components[name]
|
||||
|
||||
current = name
|
||||
if current.count("/") != 0:
|
||||
split_idx = current.rindex("/")
|
||||
parent, child = current[:split_idx], current[split_idx:]
|
||||
parent = self._get_or_create_component(name)
|
||||
component = self._view.create_component(child, parent)
|
||||
else:
|
||||
component = self._view.create_component(name)
|
||||
function.comment = cmt
|
||||
|
||||
self._components[name] = component
|
||||
return component
|
||||
def set_data_name(self, address: int, name: str):
|
||||
var = self._view.get_data_var_at(address)
|
||||
if var is None:
|
||||
return
|
||||
|
||||
def add_function_to_group(self, address: int, group: str):
|
||||
return
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
self._get_or_create_component(group).add_function(function)
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
var.name = get_qualified_name(demangled)
|
||||
else:
|
||||
var.name = name
|
||||
|
||||
def 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
|
||||
def set_function_name(self, address: int, name: str):
|
||||
function = self._view.get_function_at(address)
|
||||
if function 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
|
||||
if name.startswith("_Z"):
|
||||
type, demangled = demangle_gnu3(self._view.arch, name, self._view)
|
||||
function.name = get_qualified_name(demangled)
|
||||
# function.type = type - this does not work due to the generated types not being namespaced. :(
|
||||
else:
|
||||
function.name = name
|
||||
|
||||
# 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))
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self._view.add_user_data_ref(from_address, to_address)
|
||||
|
||||
self._view.add_user_segment(last_end_addr, size, 0, 0, SegmentFlag.SegmentContainsData)
|
||||
self._view.add_user_section(name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics)
|
||||
return last_end_addr
|
||||
|
||||
def write_string(self, address: int, value: str) -> int:
|
||||
encoded = value.encode() + b"\x00"
|
||||
self._view.write(address, encoded)
|
||||
return len(encoded)
|
||||
def import_c_typedef(self, type_def: str):
|
||||
self._view.define_user_type(None, type_def)
|
||||
|
||||
def write_address(self, address: int, value: int):
|
||||
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
|
||||
# optional
|
||||
def _get_or_create_component(self, name: str):
|
||||
if name in self._components:
|
||||
return self._components[name]
|
||||
|
||||
current = name
|
||||
if current.count("/") != 0:
|
||||
split_idx = current.rindex("/")
|
||||
parent, child = current[:split_idx], current[split_idx:]
|
||||
parent = self._get_or_create_component(name)
|
||||
component = self._view.create_component(child, parent)
|
||||
else:
|
||||
component = self._view.create_component(name)
|
||||
|
||||
self._components[name] = component
|
||||
return component
|
||||
|
||||
def add_function_to_group(self, address: int, group: str):
|
||||
return
|
||||
function = self._view.get_function_at(address)
|
||||
if function is None:
|
||||
return
|
||||
|
||||
self._get_or_create_component(group).add_function(function)
|
||||
|
||||
def cache_function_types(self, signatures: list[str]):
|
||||
function_sigs = set(signatures)
|
||||
if len(function_sigs) == 0:
|
||||
return
|
||||
|
||||
typestr = ";\n".join(function_sigs).replace("this", "_this") + ";"
|
||||
parsed_types = self._parse_type_source(typestr, "cached_types.hpp")
|
||||
if parsed_types is None:
|
||||
return
|
||||
|
||||
# bv.parse_types_from_source returns a dict in the functions field.
|
||||
# TypeParser.parse_types_from_source does not.
|
||||
for function_sig, function in zip(function_sigs, parsed_types.functions):
|
||||
self._function_type_cache[function_sig] = function.type
|
||||
|
||||
# only required if supports_fake_string_segment == True
|
||||
def create_fake_segment(self, name: str, size: int) -> int:
|
||||
last_end_addr = self._view.mapped_address_ranges[-1].end
|
||||
if last_end_addr % 0x1000 != 0:
|
||||
last_end_addr += 0x1000 - (last_end_addr % 0x1000)
|
||||
|
||||
self._view.memory_map.add_memory_region(
|
||||
f"mem_{name}",
|
||||
last_end_addr,
|
||||
bytes(size),
|
||||
SegmentFlag.SegmentContainsData | SegmentFlag.SegmentReadable,
|
||||
)
|
||||
|
||||
self._view.add_user_section(
|
||||
name, last_end_addr, size, SectionSemantics.ReadOnlyDataSectionSemantics
|
||||
)
|
||||
|
||||
return last_end_addr
|
||||
|
||||
def write_string(self, address: int, value: str) -> int:
|
||||
encoded = value.encode() + b"\x00"
|
||||
self._view.write(address, encoded)
|
||||
return len(encoded)
|
||||
|
||||
def write_address(self, address: int, value: int):
|
||||
self._view.write(address, value.to_bytes(self._address_size, self._endianness))
|
||||
|
||||
|
||||
class BinaryNinjaStatusHandler(BaseStatusHandler):
|
||||
def __init__(self, thread: BackgroundTaskThread):
|
||||
self.step = "Initializing"
|
||||
self.max_items = 0
|
||||
self.current_items = 0
|
||||
self.start_time = datetime.now()
|
||||
self.step_start_time = self.start_time
|
||||
self.last_updated_time = datetime.min
|
||||
self._thread = thread
|
||||
|
||||
def initialize(self): pass
|
||||
def __init__(self, thread: BackgroundTaskThread):
|
||||
self.step = "Initializing"
|
||||
self.max_items = 0
|
||||
self.current_items = 0
|
||||
self.start_time = datetime.now()
|
||||
self.step_start_time = self.start_time
|
||||
self.last_updated_time = datetime.min
|
||||
self._thread = thread
|
||||
|
||||
def update(self):
|
||||
if self.was_cancelled():
|
||||
raise RuntimeError("Cancelled script.")
|
||||
def initialize(self):
|
||||
pass
|
||||
|
||||
current_time = datetime.now()
|
||||
if 0.5 > (current_time - self.last_updated_time).total_seconds():
|
||||
return
|
||||
def update(self):
|
||||
if self.was_cancelled():
|
||||
raise RuntimeError("Cancelled script.")
|
||||
|
||||
self.last_updated_time = current_time
|
||||
current_time = datetime.now()
|
||||
if 0.5 > (current_time - self.last_updated_time).total_seconds():
|
||||
return
|
||||
|
||||
step_time = current_time - self.step_start_time
|
||||
total_time = current_time - self.start_time
|
||||
self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})"
|
||||
self.last_updated_time = current_time
|
||||
|
||||
def update_step(self, step, max_items = 0):
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
self.step_start_time = datetime.now()
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
step_time = current_time - self.step_start_time
|
||||
total_time = current_time - self.start_time
|
||||
self._thread.progress = f"Processing IL2CPP metadata: {self.step} ({self.current_items}/{self.max_items}), elapsed: {step_time} ({total_time})"
|
||||
|
||||
def update_progress(self, new_progress = 1):
|
||||
self.current_items += new_progress
|
||||
self.update()
|
||||
def update_step(self, step, max_items=0):
|
||||
self.step = step
|
||||
self.max_items = max_items
|
||||
self.current_items = 0
|
||||
self.step_start_time = datetime.now()
|
||||
self.last_updated_time = datetime.min
|
||||
self.update()
|
||||
|
||||
def was_cancelled(self): return False
|
||||
def update_progress(self, new_progress=1):
|
||||
self.current_items += new_progress
|
||||
self.update()
|
||||
|
||||
def was_cancelled(self):
|
||||
return False
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
# Entry point
|
||||
class Il2CppTask(BackgroundTaskThread):
|
||||
def __init__(self):
|
||||
BackgroundTaskThread.__init__(self, "Processing IL2CPP metadata...", False)
|
||||
def __init__(self):
|
||||
BackgroundTaskThread.__init__(self, "Processing IL2CPP metadata...", False)
|
||||
|
||||
def run(self):
|
||||
status = BinaryNinjaStatusHandler(self)
|
||||
backend = BinaryNinjaDisassemblerInterface(status)
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
def run(self):
|
||||
status = BinaryNinjaStatusHandler(self)
|
||||
backend = BinaryNinjaDisassemblerInterface(status)
|
||||
context = ScriptContext(backend, status)
|
||||
context.process()
|
||||
|
||||
Il2CppTask().start()
|
||||
|
||||
Il2CppTask().start()
|
||||
|
||||
@@ -6,6 +6,7 @@ from ghidra.program.model.symbol import SourceType
|
||||
from ghidra.program.model.symbol import RefType
|
||||
from ghidra.app.cmd.label import DemanglerCmd
|
||||
from ghidra.app.services import DataTypeManagerService
|
||||
from java.lang import Long
|
||||
|
||||
#try:
|
||||
# from typing import TYPE_CHECKING
|
||||
@@ -21,6 +22,9 @@ from ghidra.app.services import DataTypeManagerService
|
||||
class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
supports_fake_string_segment = False
|
||||
|
||||
def _to_address(self, value):
|
||||
return toAddr(Long(value))
|
||||
|
||||
def get_script_directory(self) -> str:
|
||||
return getSourceFile().getParentFile().toString()
|
||||
|
||||
@@ -39,7 +43,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
# Without this, Ghidra may not analyze the binary correctly and you will just waste your time
|
||||
# If 0 doesn't work for you, replace it with the base address from the output of the CLI or GUI
|
||||
if currentProgram.getExecutableFormat().endswith('(ELF)'):
|
||||
currentProgram.setImageBase(toAddr(0), True)
|
||||
currentProgram.setImageBase(self._to_address(0), True)
|
||||
|
||||
# Don't trigger decompiler
|
||||
setAnalysisOption(currentProgram, "Call Convention ID", "false")
|
||||
@@ -48,7 +52,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
pass
|
||||
|
||||
def define_function(self, address: int, end: int | None = None):
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
# Don't override existing functions
|
||||
fn = getFunctionAt(address)
|
||||
if fn is None:
|
||||
@@ -61,7 +65,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
|
||||
t = getDataTypes(type)[0]
|
||||
a = ArrayDataType(t, count, t.getLength())
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
removeDataAt(address)
|
||||
createData(address, a)
|
||||
|
||||
@@ -71,7 +75,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
|
||||
try:
|
||||
t = getDataTypes(type)[0]
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
removeDataAt(address)
|
||||
createData(address, t)
|
||||
except:
|
||||
@@ -79,16 +83,16 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
|
||||
def set_function_type(self, address: int, type: str):
|
||||
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):
|
||||
setEOLComment(toAddr(address), cmt)
|
||||
setEOLComment(self._to_address(address), cmt)
|
||||
|
||||
def set_function_comment(self, address: int, cmt: str):
|
||||
setPlateComment(toAddr(address), cmt)
|
||||
setPlateComment(self._to_address(address), cmt)
|
||||
|
||||
def set_data_name(self, address: int, name: str):
|
||||
address = toAddr(address)
|
||||
address = self._to_address(address)
|
||||
|
||||
if len(name) > 2000:
|
||||
print("Name length exceeds 2000 characters, skipping (%s)" % name)
|
||||
@@ -107,7 +111,7 @@ class GhidraDisassemblerInterface(BaseDisassemblerInterface):
|
||||
return self.set_data_name(address, name)
|
||||
|
||||
def add_cross_reference(self, from_address: int, to_address: int):
|
||||
self.xrefs.addMemoryReference(toAddr(from_address), toAddr(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
self.xrefs.addMemoryReference(self._to_address(from_address), self._to_address(to_address), RefType.DATA, SourceType.USER_DEFINED, 0)
|
||||
|
||||
def import_c_typedef(self, type_def: str):
|
||||
# Code declarations are not supported in Ghidra
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
All rights reserved.
|
||||
*/
|
||||
|
||||
using Il2CppInspector.PluginAPI;
|
||||
// This is the ONLY line to update when the API version changes
|
||||
using Il2CppInspector.PluginAPI.V100;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Spectre.Console;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
@@ -12,11 +17,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using McMaster.NETCore.Plugins;
|
||||
using Il2CppInspector.PluginAPI;
|
||||
|
||||
// This is the ONLY line to update when the API version changes
|
||||
using Il2CppInspector.PluginAPI.V100;
|
||||
|
||||
namespace Il2CppInspector
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
|
||||
@@ -784,7 +785,9 @@ namespace Il2CppInspector.Reflection
|
||||
// Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum)
|
||||
if (Definition.Bitfield.EnumType) {
|
||||
IsEnum = true;
|
||||
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.ElementTypeIndex);
|
||||
|
||||
var enumUnderlyingTypeIndex = Definition.GetEnumElementTypeIndex(Assembly.Model.Package.Version);
|
||||
enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, enumUnderlyingTypeIndex);
|
||||
}
|
||||
|
||||
// Pass-by-reference type
|
||||
|
||||
@@ -165,10 +165,9 @@ namespace Il2CppInspector.Reflection
|
||||
// Generic type definitions have an invoker index of -1
|
||||
foreach (var method in MethodsByDefinitionIndex) {
|
||||
var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition);
|
||||
if (index != -1) {
|
||||
if (MethodInvokers[index] == null)
|
||||
MethodInvokers[index] = new MethodInvoker(method, index);
|
||||
|
||||
if (index != -1)
|
||||
{
|
||||
MethodInvokers[index] ??= new MethodInvoker(method, index);
|
||||
method.Invoker = MethodInvokers[index];
|
||||
}
|
||||
}
|
||||
@@ -176,10 +175,11 @@ namespace Il2CppInspector.Reflection
|
||||
// Create method invokers sourced from generic method invoker indices
|
||||
foreach (var spec in GenericMethods.Keys) {
|
||||
if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) {
|
||||
if (MethodInvokers[index] == null)
|
||||
MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index);
|
||||
|
||||
GenericMethods[spec].Invoker = MethodInvokers[index];
|
||||
if (index != -1)
|
||||
{
|
||||
MethodInvokers[index] ??= new MethodInvoker(GenericMethods[spec], index);
|
||||
GenericMethods[spec].Invoker = MethodInvokers[index];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Diagnostics;
|
||||
using Il2CppInspector.Next;
|
||||
using Il2CppInspector.Next.BinaryMetadata;
|
||||
using Il2CppInspector.Next.Metadata;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace Il2CppInspector.Utils;
|
||||
|
||||
@@ -122,7 +123,7 @@ public static class BlobReader
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Console.WriteLine($"Found invalid compressed int at metadata address 0x{address:x8}. Reading as normal int.");
|
||||
AnsiConsole.WriteLine($"Found invalid compressed int at metadata address 0x{address:x8}. Reading as normal int.");
|
||||
return blob.ReadInt32(address);
|
||||
}
|
||||
}
|
||||
@@ -142,7 +143,7 @@ public static class BlobReader
|
||||
}
|
||||
catch (InvalidDataException)
|
||||
{
|
||||
Console.WriteLine($"Found invalid compressed uint at metadata address 0x{address:x8}. Reading as normal uint.");
|
||||
AnsiConsole.WriteLine($"Found invalid compressed uint at metadata address 0x{address:x8}. Reading as normal uint.");
|
||||
return blob.ReadUInt32(address);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +164,9 @@ public static class BlobReader
|
||||
var typeHandle = inspector.TypeReferences[typeIndex].Data.KlassIndex;
|
||||
enumType = inspector.TypeDefinitions[typeHandle];
|
||||
|
||||
var elementTypeHandle = inspector.TypeReferences[enumType.ElementTypeIndex].Data.KlassIndex;
|
||||
var elementTypeIndex = enumType.GetEnumElementTypeIndex(inspector.Version);
|
||||
|
||||
var elementTypeHandle = inspector.TypeReferences[elementTypeIndex].Data.KlassIndex;
|
||||
var elementType = inspector.TypeDefinitions[elementTypeHandle];
|
||||
typeEnum = inspector.TypeReferences[elementType.ByValTypeIndex].Type;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user