IL2CPP: Change metadata and binary to derive from BinaryObjectStream

This commit is contained in:
Katy Coe
2020-12-21 06:37:29 +01:00
parent 620d985b71
commit c00b474f33
31 changed files with 172 additions and 179 deletions

View File

@@ -13,9 +13,9 @@ namespace Il2CppInspector
{ {
internal class Il2CppBinaryARM : Il2CppBinary internal class Il2CppBinaryARM : Il2CppBinary
{ {
public Il2CppBinaryARM(IFileFormatReader stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { } public Il2CppBinaryARM(IFileFormatStream stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { }
public Il2CppBinaryARM(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null) public Il2CppBinaryARM(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null)
: base(stream, codeRegistration, metadataRegistration, statusCallback) { } : base(stream, codeRegistration, metadataRegistration, statusCallback) { }
// ARMv7-A Architecture Reference Manual: https://static.docs.arm.com/ddi0406/c/DDI0406C_C_arm_architecture_reference_manual.pdf // ARMv7-A Architecture Reference Manual: https://static.docs.arm.com/ddi0406/c/DDI0406C_C_arm_architecture_reference_manual.pdf
@@ -67,7 +67,7 @@ namespace Il2CppInspector
} }
// Section 3.1 // Section 3.1
private uint getNextThumbInstruction(IFileFormatReader image) { private uint getNextThumbInstruction(IFileFormatStream image) {
// Assume 16-bit // Assume 16-bit
uint inst = image.ReadUInt16(); uint inst = image.ReadUInt16();
@@ -123,7 +123,7 @@ namespace Il2CppInspector
private bool isBW(uint inst) => inst.Bits(27, 5) == 0b11110 && inst.Bits(14, 2) == 0b10 && inst.Bits(12, 1) == 1; private bool isBW(uint inst) => inst.Bits(27, 5) == 0b11110 && inst.Bits(14, 2) == 0b10 && inst.Bits(12, 1) == 1;
// Sweep a Thumb function and return the register values at the end (register number => value) // Sweep a Thumb function and return the register values at the end (register number => value)
private Dictionary<uint, uint> sweepThumbForAddressLoads(List<uint> func, uint baseAddress, IFileFormatReader image) { private Dictionary<uint, uint> sweepThumbForAddressLoads(List<uint> func, uint baseAddress, IFileFormatStream image) {
// List of registers and addresses loaded into them // List of registers and addresses loaded into them
var regs = new Dictionary<uint, uint>(); var regs = new Dictionary<uint, uint>();
@@ -200,7 +200,7 @@ namespace Il2CppInspector
} }
// Get a Thumb function that ends in B.W // Get a Thumb function that ends in B.W
private List<uint> getThumbFunctionAtFileOffset(IFileFormatReader image, uint loc, uint maxLength) { private List<uint> getThumbFunctionAtFileOffset(IFileFormatStream image, uint loc, uint maxLength) {
// Read a function that ends in a hard branch (B.W) or exceeds maxLength instructions // Read a function that ends in a hard branch (B.W) or exceeds maxLength instructions
var func = new List<uint>(); var func = new List<uint>();
uint inst; uint inst;
@@ -215,7 +215,7 @@ namespace Il2CppInspector
return func; return func;
} }
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) { protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc) {
// Assembly bytes to search for at start of each function // Assembly bytes to search for at start of each function
ulong metadataRegistration, codeRegistration; ulong metadataRegistration, codeRegistration;
@@ -264,7 +264,7 @@ namespace Il2CppInspector
// - B // - B
// R0 = CodeRegistration, R1 = MetadataRegistration, R2 = Il2CppCodeGenOptions // R0 = CodeRegistration, R1 = MetadataRegistration, R2 = Il2CppCodeGenOptions
var insts = image.Stream.ReadArray<uint>(pCgr, 10); // 7 instructions + 3 pointers var insts = image.ReadArray<uint>(pCgr, 10); // 7 instructions + 3 pointers
var ldrOffsets = new uint[3]; var ldrOffsets = new uint[3];
var pointers = new uint[3]; var pointers = new uint[3];

View File

@@ -12,9 +12,9 @@ namespace Il2CppInspector
// A64 ISA reference: https://static.docs.arm.com/ddi0596/a/DDI_0596_ARM_a64_instruction_set_architecture.pdf // A64 ISA reference: https://static.docs.arm.com/ddi0596/a/DDI_0596_ARM_a64_instruction_set_architecture.pdf
internal class Il2CppBinaryARM64 : Il2CppBinary internal class Il2CppBinaryARM64 : Il2CppBinary
{ {
public Il2CppBinaryARM64(IFileFormatReader stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { } public Il2CppBinaryARM64(IFileFormatStream stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { }
public Il2CppBinaryARM64(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null) public Il2CppBinaryARM64(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null)
: base(stream, codeRegistration, metadataRegistration, statusCallback) { } : base(stream, codeRegistration, metadataRegistration, statusCallback) { }
private (uint reg, ulong page)? getAdrp(uint inst, ulong pc) { private (uint reg, ulong page)? getAdrp(uint inst, ulong pc) {
@@ -70,7 +70,7 @@ namespace Il2CppInspector
private bool isB(uint inst) => inst.Bits(26, 6) == 0b_000101; private bool isB(uint inst) => inst.Bits(26, 6) == 0b_000101;
private Dictionary<uint, ulong> sweepForAddressLoads(List<uint> func, ulong baseAddress, IFileFormatReader image) { private Dictionary<uint, ulong> sweepForAddressLoads(List<uint> func, ulong baseAddress, IFileFormatStream image) {
// List of registers and addresses loaded into them // List of registers and addresses loaded into them
var regs = new Dictionary<uint, ulong>(); var regs = new Dictionary<uint, ulong>();
@@ -118,7 +118,7 @@ namespace Il2CppInspector
return regs; return regs;
} }
private List<uint> getFunctionAtFileOffset(IFileFormatReader image, uint loc, uint maxLength) { private List<uint> getFunctionAtFileOffset(IFileFormatStream image, uint loc, uint maxLength) {
// Read a function that ends in a hard branch (B) or exceeds maxLength instructions // Read a function that ends in a hard branch (B) or exceeds maxLength instructions
var func = new List<uint>(); var func = new List<uint>();
uint inst; uint inst;
@@ -141,7 +141,7 @@ namespace Il2CppInspector
// - Loads can be done either with ADRP+ADD (loads the address of the wanted struct) or ADRP+LDR (loads a pointer to the address which must be de-referenced) // - Loads can be done either with ADRP+ADD (loads the address of the wanted struct) or ADRP+LDR (loads a pointer to the address which must be de-referenced)
// - Loads do not need to be pairs of sequential instructions // - Loads do not need to be pairs of sequential instructions
// - We need to sweep the whole function from the ADRP to the next B to find an ADD or LDR with a corresponding register // - We need to sweep the whole function from the ADRP to the next B to find an ADD or LDR with a corresponding register
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) { protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc) {
// Load function into memory // Load function into memory
// In practice, the longest function length we need is not generally longer than 7 instructions (0x1C bytes) // In practice, the longest function length we need is not generally longer than 7 instructions (0x1C bytes)
var func = getFunctionAtFileOffset(image, loc, 7); var func = getFunctionAtFileOffset(image, loc, 7);

View File

@@ -13,9 +13,9 @@ namespace Il2CppInspector
{ {
internal class Il2CppBinaryX64 : Il2CppBinary internal class Il2CppBinaryX64 : Il2CppBinary
{ {
public Il2CppBinaryX64(IFileFormatReader stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { } public Il2CppBinaryX64(IFileFormatStream stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { }
public Il2CppBinaryX64(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null) public Il2CppBinaryX64(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null)
: base(stream, codeRegistration, metadataRegistration, statusCallback) { } : base(stream, codeRegistration, metadataRegistration, statusCallback) { }
// Format of 64-bit LEA: // Format of 64-bit LEA:
@@ -94,7 +94,7 @@ namespace Il2CppInspector
return ((buff[offset + 1] & 0b0011_1000) >> 3, buff[offset + 1] & 0b0000_0111); return ((buff[offset + 1] & 0b0011_1000) >> 3, buff[offset + 1] & 0b0000_0111);
} }
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) { protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc) {
// Setup // Setup
var buffSize = 0x76; // minimum number of bytes to process the longest expected function var buffSize = 0x76; // minimum number of bytes to process the longest expected function

View File

@@ -11,12 +11,12 @@ namespace Il2CppInspector
{ {
internal class Il2CppBinaryX86 : Il2CppBinary internal class Il2CppBinaryX86 : Il2CppBinary
{ {
public Il2CppBinaryX86(IFileFormatReader stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { } public Il2CppBinaryX86(IFileFormatStream stream, EventHandler<string> statusCallback = null) : base(stream, statusCallback) { }
public Il2CppBinaryX86(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null) public Il2CppBinaryX86(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null)
: base(stream, codeRegistration, metadataRegistration, statusCallback) { } : base(stream, codeRegistration, metadataRegistration, statusCallback) { }
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) { protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc) {
ulong metadata, code; ulong metadata, code;
long pCgr; long pCgr;

View File

@@ -16,6 +16,6 @@ namespace Il2CppInspector.Cpp
public static class CppCompiler public static class CppCompiler
{ {
// Attempt to guess the compiler used to build the binary via its file type // Attempt to guess the compiler used to build the binary via its file type
public static CppCompilerType GuessFromImage(IFileFormatReader image) => (image is PEReader? CppCompilerType.MSVC : CppCompilerType.GCC); public static CppCompilerType GuessFromImage(IFileFormatStream image) => (image is PEReader? CppCompilerType.MSVC : CppCompilerType.GCC);
} }
} }

View File

@@ -13,13 +13,11 @@ using System.Linq;
namespace Il2CppInspector namespace Il2CppInspector
{ {
// This is a wrapper for multiple binary files of different architectures within a single AAB // This is a wrapper for multiple binary files of different architectures within a single AAB
internal class AABReader : FileFormatReader<AABReader> internal class AABReader : FileFormatStream<AABReader>
{ {
private ZipArchive zip; private ZipArchive zip;
private ZipArchiveEntry[] binaryFiles; private ZipArchiveEntry[] binaryFiles;
public AABReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "Package.aab"; public override string DefaultFilename => "Package.aab";
protected override bool Init() { protected override bool Init() {
@@ -32,7 +30,7 @@ namespace Il2CppInspector
return false; return false;
try { try {
zip = new ZipArchive(BaseStream); zip = new ZipArchive(this);
// Get list of binary files // Get list of binary files
binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("base/lib/") && f.Name == "libil2cpp.so").ToArray(); binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("base/lib/") && f.Name == "libil2cpp.so").ToArray();
@@ -51,10 +49,10 @@ namespace Il2CppInspector
return true; return true;
} }
public override IFileFormatReader this[uint index] { public override IFileFormatStream this[uint index] {
get { get {
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}"); Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
IFileFormatReader 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
var binary = binaryFiles[index].Open(); var binary = binaryFiles[index].Open();

View File

@@ -13,13 +13,11 @@ using System.Linq;
namespace Il2CppInspector namespace Il2CppInspector
{ {
// This is a wrapper for multiple binary files of different architectures within a single APK // This is a wrapper for multiple binary files of different architectures within a single APK
internal class APKReader : FileFormatReader<APKReader> internal class APKReader : FileFormatStream<APKReader>
{ {
private ZipArchive zip; private ZipArchive zip;
private ZipArchiveEntry[] binaryFiles; private ZipArchiveEntry[] binaryFiles;
public APKReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "Package.apk"; public override string DefaultFilename => "Package.apk";
protected override bool Init() { protected override bool Init() {
@@ -32,7 +30,7 @@ namespace Il2CppInspector
return false; return false;
try { try {
zip = new ZipArchive(BaseStream); zip = new ZipArchive(this);
// Get list of binary files // Get list of binary files
binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so").ToArray(); binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so").ToArray();
@@ -51,10 +49,10 @@ namespace Il2CppInspector
return true; return true;
} }
public override IFileFormatReader this[uint index] { public override IFileFormatStream this[uint index] {
get { get {
Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}"); Console.WriteLine($"Extracting binary from {binaryFiles[index].FullName}");
IFileFormatReader 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
var binary = binaryFiles[index].Open(); var binary = binaryFiles[index].Open();

View File

@@ -17,7 +17,7 @@ namespace Il2CppInspector
{ {
internal class ElfReader32 : ElfReader<uint, elf_32_phdr, elf_32_sym, ElfReader32, Convert32> internal class ElfReader32 : ElfReader<uint, elf_32_phdr, elf_32_sym, ElfReader32, Convert32>
{ {
public ElfReader32(Stream stream) : base(stream) { public ElfReader32() : base() {
ElfReloc.GetRelocType = info => (Elf) (info & 0xff); ElfReloc.GetRelocType = info => (Elf) (info & 0xff);
ElfReloc.GetSymbolIndex = info => info >> 8; ElfReloc.GetSymbolIndex = info => info >> 8;
} }
@@ -25,12 +25,12 @@ namespace Il2CppInspector
public override int Bits => 32; public override int Bits => 32;
protected override Elf ArchClass => Elf.ELFCLASS32; protected override Elf ArchClass => Elf.ELFCLASS32;
protected override void Write(BinaryWriter writer, uint value) => writer.Write(value); protected override void WriteWord(uint value) => Write(value);
} }
internal class ElfReader64 : ElfReader<ulong, elf_64_phdr, elf_64_sym, ElfReader64, Convert64> internal class ElfReader64 : ElfReader<ulong, elf_64_phdr, elf_64_sym, ElfReader64, Convert64>
{ {
public ElfReader64(Stream stream) : base(stream) { public ElfReader64() : base() {
ElfReloc.GetRelocType = info => (Elf) (info & 0xffff_ffff); ElfReloc.GetRelocType = info => (Elf) (info & 0xffff_ffff);
ElfReloc.GetSymbolIndex = info => info >> 32; ElfReloc.GetSymbolIndex = info => info >> 32;
} }
@@ -38,7 +38,7 @@ namespace Il2CppInspector
public override int Bits => 64; public override int Bits => 64;
protected override Elf ArchClass => Elf.ELFCLASS64; protected override Elf ArchClass => Elf.ELFCLASS64;
protected override void Write(BinaryWriter writer, ulong value) => writer.Write(value); protected override void WriteWord(ulong value) => Write(value);
} }
interface IElfReader interface IElfReader
@@ -46,12 +46,12 @@ namespace Il2CppInspector
uint GetPLTAddress(); uint GetPLTAddress();
} }
internal abstract class ElfReader<TWord, TPHdr, TSym, TReader, TConvert> : FileFormatReader<TReader>, IElfReader internal abstract class ElfReader<TWord, TPHdr, TSym, TReader, TConvert> : FileFormatStream<TReader>, IElfReader
where TWord : struct where TWord : struct
where TPHdr : Ielf_phdr<TWord>, new() where TPHdr : Ielf_phdr<TWord>, new()
where TSym : Ielf_sym<TWord>, new() where TSym : Ielf_sym<TWord>, new()
where TConvert : IWordConverter<TWord>, new() where TConvert : IWordConverter<TWord>, new()
where TReader : FileFormatReader<TReader> where TReader : FileFormatStream<TReader>
{ {
private readonly TConvert conv = new TConvert(); private readonly TConvert conv = new TConvert();
@@ -115,8 +115,6 @@ namespace Il2CppInspector
private bool preferPHT = false; private bool preferPHT = false;
private bool isMemoryImage = false; private bool isMemoryImage = false;
public ElfReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "libil2cpp.so"; public override string DefaultFilename => "libil2cpp.so";
public override string Format => Bits == 32 ? "ELF" : "ELF64"; public override string Format => Bits == 32 ? "ELF" : "ELF64";
@@ -141,7 +139,7 @@ namespace Il2CppInspector
protected abstract Elf ArchClass { get; } protected abstract Elf ArchClass { get; }
protected abstract void Write(BinaryWriter writer, TWord value); protected abstract void WriteWord(TWord value);
protected override bool Init() { protected override bool Init() {
elf_header = ReadObject<elf_header<TWord>>(); elf_header = ReadObject<elf_header<TWord>>();
@@ -154,10 +152,6 @@ namespace Il2CppInspector
if ((Elf) elf_header.m_arch != ArchClass) if ((Elf) elf_header.m_arch != ArchClass)
return false; return false;
// Relocations and rebasing will modify the stream - ensure it is non-destructive
if (!(BaseStream is MemoryStream))
throw new InvalidOperationException("Input stream to ElfReader must be a MemoryStream.");
// Get PHT and SHT // Get PHT and SHT
program_header_table = ReadArray<TPHdr>(conv.Long(elf_header.e_phoff), elf_header.e_phnum); program_header_table = ReadArray<TPHdr>(conv.Long(elf_header.e_phoff), elf_header.e_phnum);
section_header_table = ReadArray<elf_shdr<TWord>>(conv.Long(elf_header.e_shoff), elf_header.e_shnum); section_header_table = ReadArray<elf_shdr<TWord>>(conv.Long(elf_header.e_shoff), elf_header.e_shnum);
@@ -283,7 +277,6 @@ namespace Il2CppInspector
} }
// Process relocations // Process relocations
using var writer = new BinaryWriter(BaseStream, Encoding.Default, true);
var relsz = Sizeof(typeof(TSym)); var relsz = Sizeof(typeof(TSym));
var currentRel = 0; var currentRel = 0;
@@ -334,7 +327,7 @@ namespace Il2CppInspector
if (result.recognized) { if (result.recognized) {
Position = MapVATR(conv.ULong(rel.Offset)); Position = MapVATR(conv.ULong(rel.Offset));
Write(writer, result.newValue); WriteWord(result.newValue);
} }
} }
Console.WriteLine($"Processed {rels.Count} relocations"); Console.WriteLine($"Processed {rels.Count} relocations");
@@ -525,12 +518,9 @@ namespace Il2CppInspector
} }
private void xorRange(int offset, int length, byte xorValue) { private void xorRange(int offset, int length, byte xorValue) {
using var writer = new BinaryWriter(BaseStream, Encoding.Default, true);
var bytes = ReadBytes(offset, length); var bytes = ReadBytes(offset, length);
bytes = bytes.Select(b => (byte) (b ^ xorValue)).ToArray(); bytes = bytes.Select(b => (byte) (b ^ xorValue)).ToArray();
writer.Seek(offset, SeekOrigin.Begin); Write(offset, bytes);
writer.Write(bytes);
} }
private void xorSection(string sectionName, byte xorValue, uint stripeSize) { private void xorSection(string sectionName, byte xorValue, uint stripeSize) {
@@ -571,8 +561,7 @@ namespace Il2CppInspector
} }
// Rewrite to stream // Rewrite to stream
using var writer = new BinaryObjectWriter(BaseStream, Endianness, true); WriteArray(conv.Long(elf_header.e_phoff), program_header_table);
writer.WriteArray(conv.Long(elf_header.e_phoff), program_header_table);
IsModified = true; IsModified = true;
// Rebase dynamic table if it exists // Rebase dynamic table if it exists
@@ -595,7 +584,7 @@ namespace Il2CppInspector
section.d_un = conv.Add(section.d_un, imageBase); section.d_un = conv.Add(section.d_un, imageBase);
// Rewrite to stream // Rewrite to stream
writer.WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt); WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
} }
private void processSymbols() { private void processSymbols() {

View File

@@ -14,23 +14,22 @@ using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector namespace Il2CppInspector
{ {
public interface IFileFormatReader public interface IFileFormatStream
{ {
BinaryObjectReader Stream { get; }
double Version { get; set; } double Version { get; set; }
long Length { get; } long Length { get; }
uint NumImages { get; } uint NumImages { get; }
string DefaultFilename { get; } string DefaultFilename { get; }
bool IsModified { get; } bool IsModified { get; }
IEnumerable<IFileFormatReader> Images { get; } // Each child image of this object (eg. 32/64-bit versions in Fat MachO file) IEnumerable<IFileFormatStream> Images { get; } // Each child image of this object (eg. 32/64-bit versions in Fat MachO file)
IFileFormatReader this[uint index] { get; } // With no additional override, one object = one file, this[0] == this IFileFormatStream this[uint index] { get; } // With no additional override, one object = one file, this[0] == this
long Position { get; set; } long Position { get; set; }
string Format { get; } string Format { get; }
string Arch { get; } string Arch { get; }
int Bits { get; } int Bits { get; }
ulong GlobalOffset { get; } // The virtual address where the code section (.text) would be loaded in memory ulong GlobalOffset { get; } // The virtual address where the code section (.text) would be loaded in memory
ulong ImageBase { get; } // The virtual address of where the image would be loaded in memory (same as GlobalOffset except for PE) ulong ImageBase { get; } // The virtual address of where the image would be loaded in memory (same as GlobalOffset except for PE)
IEnumerable<IFileFormatReader> TryNextLoadStrategy(); // Some images can be loaded multiple ways, eg. default, packed IEnumerable<IFileFormatStream> TryNextLoadStrategy(); // Some images can be loaded multiple ways, eg. default, packed
Dictionary<string, Symbol> GetSymbolTable(); Dictionary<string, Symbol> GetSymbolTable();
uint[] GetFunctionTable(); uint[] GetFunctionTable();
IEnumerable<Export> GetExports(); IEnumerable<Export> GetExports();
@@ -92,24 +91,52 @@ namespace Il2CppInspector
long[] ReadMappedWordArray(ulong uiAddr, int count); long[] ReadMappedWordArray(ulong uiAddr, int count);
List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new(); List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new();
void WriteEndianBytes(byte[] bytes);
void Write(long int64);
void Write(ulong uint64);
void Write(int int32);
void Write(uint uint32);
void Write(short int16);
void Write(ushort uint16);
void Write(long addr, byte[] bytes);
void Write(long addr, long int64);
void Write(long addr, ulong uint64);
void Write(long addr, int int32);
void Write(long addr, uint uint32);
void Write(long addr, short int16);
void Write(long addr, ushort uint16);
void Write(long addr, byte value);
void Write(long addr, bool value);
void WriteObject<T>(long addr, T obj);
void WriteObject<T>(T obj);
void WriteArray<T>(long addr, T[] array);
void WriteArray<T>(T[] array);
void WriteNullTerminatedString(long addr, string str, Encoding encoding = null);
void WriteNullTerminatedString(string str, Encoding encoding = null);
void WriteFixedLengthString(long addr, string str, int size = -1, Encoding encoding = null);
void WriteFixedLengthString(string str, int size = -1, Encoding encoding = null);
EventHandler<string> OnStatusUpdate { get; set; } EventHandler<string> OnStatusUpdate { get; set; }
public void AddPrimitiveMapping(Type objType, Type streamType);
public void CopyTo(Stream stream);
} }
public class FileFormatReader public class FileFormatStream
{ {
// Helper method to try all defined file formats when the contents of the binary is unknown // Helper method to try all defined file formats when the contents of the binary is unknown
public static IFileFormatReader Load(string filename, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) public static IFileFormatStream Load(string filename, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null)
=> Load(new FileStream(filename, FileMode.Open, FileAccess.Read), loadOptions, statusCallback); => Load(new FileStream(filename, FileMode.Open, FileAccess.Read), loadOptions, statusCallback);
public static IFileFormatReader Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) { public static IFileFormatStream Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) {
var types = Assembly.GetExecutingAssembly().DefinedTypes var types = Assembly.GetExecutingAssembly().DefinedTypes
.Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatReader)) && !x.IsGenericTypeDefinition); .Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatStream)) && !x.IsGenericTypeDefinition);
foreach (var type in types) { foreach (var type in types) {
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(Stream), typeof(LoadOptions), typeof(EventHandler<string>)}, null) null, new[] {typeof(Stream), typeof(LoadOptions), typeof(EventHandler<string>)}, null)
.Invoke(null, new object[] {stream, loadOptions, statusCallback}) is IFileFormatReader loaded) .Invoke(null, new object[] {stream, loadOptions, statusCallback}) is IFileFormatStream loaded)
return loaded; return loaded;
} }
catch (TargetInvocationException ex) { catch (TargetInvocationException ex) {
@@ -120,18 +147,12 @@ namespace Il2CppInspector
} }
} }
public abstract class FileFormatReader<T> : BinaryObjectReader, IFileFormatReader where T : FileFormatReader<T> public abstract class FileFormatStream<T> : BinaryObjectStream, IFileFormatStream where T : FileFormatStream<T>
{ {
public FileFormatReader(Stream stream) : base(stream) { }
public BinaryObjectReader Stream => this;
public abstract string DefaultFilename { get; } public abstract string DefaultFilename { get; }
public bool IsModified { get; protected set; } = false; public bool IsModified { get; protected set; } = false;
public long Length => BaseStream.Length;
public uint NumImages { get; protected set; } = 1; public uint NumImages { get; protected set; } = 1;
public ulong GlobalOffset { get; protected set; } public ulong GlobalOffset { get; protected set; }
@@ -151,7 +172,7 @@ namespace Il2CppInspector
protected void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status); protected void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status);
public IEnumerable<IFileFormatReader> Images { public IEnumerable<IFileFormatStream> Images {
get { get {
for (uint i = 0; i < NumImages; i++) for (uint i = 0; i < NumImages; i++)
yield return this[i]; yield return this[i];
@@ -165,15 +186,12 @@ namespace Il2CppInspector
public static T Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) { public static T Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) {
// Copy the original stream in case we modify it // Copy the original stream in case we modify it
var ms = new MemoryStream();
if (stream.CanSeek) if (stream.CanSeek)
stream.Position = 0; stream.Position = 0;
stream.CopyTo(ms); var binary = (T) Activator.CreateInstance(typeof(T));
stream.CopyTo(binary);
ms.Position = 0; binary.Position = 0;
var pe = (T) Activator.CreateInstance(typeof(T), ms); return binary.InitImpl(loadOptions, statusCallback) ? binary : null;
return pe.InitImpl(loadOptions, statusCallback) ? pe : null;
} }
private bool InitImpl(LoadOptions loadOptions, EventHandler<string> statusCallback) { private bool InitImpl(LoadOptions loadOptions, EventHandler<string> statusCallback) {
@@ -186,10 +204,10 @@ namespace Il2CppInspector
protected virtual bool Init() => throw new NotImplementedException(); protected virtual bool Init() => throw new NotImplementedException();
// Choose an image within the file for multi-architecture binaries // Choose an image within the file for multi-architecture binaries
public virtual IFileFormatReader this[uint index] => (index == 0)? this : throw new IndexOutOfRangeException("Binary image index out of bounds"); public virtual IFileFormatStream this[uint index] => (index == 0)? this : throw new IndexOutOfRangeException("Binary image index out of bounds");
// For images that can be loaded and then tested with Il2CppBinary in multiple ways, get the next possible version of the image // For images that can be loaded and then tested with Il2CppBinary in multiple ways, get the next possible version of the image
public virtual IEnumerable<IFileFormatReader> TryNextLoadStrategy() { yield return this; } public virtual IEnumerable<IFileFormatStream> TryNextLoadStrategy() { yield return this; }
// Find search locations in the symbol table for Il2Cpp data // Find search locations in the symbol table for Il2Cpp data
public virtual Dictionary<string, Symbol> GetSymbolTable() => new Dictionary<string, Symbol>(); public virtual Dictionary<string, Symbol> GetSymbolTable() => new Dictionary<string, Symbol>();

View File

@@ -15,8 +15,6 @@ namespace Il2CppInspector
{ {
internal class MachOReader32 : MachOReader<uint, MachOReader32, Convert32> internal class MachOReader32 : MachOReader<uint, MachOReader32, Convert32>
{ {
public MachOReader32(Stream stream) : base(stream) { }
public override int Bits => 32; public override int Bits => 32;
protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC; protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC;
@@ -37,8 +35,6 @@ namespace Il2CppInspector
internal class MachOReader64 : MachOReader<ulong, MachOReader64, Convert64> internal class MachOReader64 : MachOReader<ulong, MachOReader64, Convert64>
{ {
public MachOReader64(Stream stream) : base(stream) { }
public override int Bits => 64; public override int Bits => 64;
protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC_64; protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC_64;
@@ -59,9 +55,9 @@ namespace Il2CppInspector
// We need this convoluted generic TReader declaration so that "static T FileFormatReader.Load(Stream)" // We need this convoluted generic TReader declaration so that "static T FileFormatReader.Load(Stream)"
// is inherited to MachOReader32/64 with a correct definition of T // is inherited to MachOReader32/64 with a correct definition of T
internal abstract class MachOReader<TWord, TReader, TConvert> : FileFormatReader<TReader> internal abstract class MachOReader<TWord, TReader, TConvert> : FileFormatStream<TReader>
where TWord : struct where TWord : struct
where TReader : FileFormatReader<TReader> where TReader : FileFormatStream<TReader>
where TConvert : IWordConverter<TWord>, new() where TConvert : IWordConverter<TWord>, new()
{ {
private readonly TConvert conv = new TConvert(); private readonly TConvert conv = new TConvert();
@@ -75,8 +71,6 @@ namespace Il2CppInspector
private List<Export> exports = new List<Export>(); private List<Export> exports = new List<Export>();
protected MachOReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "app"; public override string DefaultFilename => "app";
public override string Format => "Mach-O " + (Bits == 32 ? "32-bit" : "64-bit"); public override string Format => "Mach-O " + (Bits == 32 ? "32-bit" : "64-bit");

View File

@@ -17,7 +17,7 @@ namespace Il2CppInspector
// References: // References:
// PE Header file: https://github.com/dotnet/llilc/blob/master/include/clr/ntimage.h // PE Header file: https://github.com/dotnet/llilc/blob/master/include/clr/ntimage.h
// PE format specification: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN // PE format specification: https://docs.microsoft.com/en-us/windows/win32/debug/pe-format?redirectedfrom=MSDN
internal class PEReader : FileFormatReader<PEReader> internal class PEReader : FileFormatStream<PEReader>
{ {
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private extern static IntPtr LoadLibrary(string lpLibFileName); private extern static IntPtr LoadLibrary(string lpLibFileName);
@@ -38,8 +38,6 @@ namespace Il2CppInspector
[PE.IMAGE_SCN_MEM_READ | PE.IMAGE_SCN_MEM_WRITE | PE.IMAGE_SCN_CNT_INITIALIZED_DATA] = ".data" [PE.IMAGE_SCN_MEM_READ | PE.IMAGE_SCN_MEM_WRITE | PE.IMAGE_SCN_CNT_INITIALIZED_DATA] = ".data"
}; };
public PEReader(Stream stream) : base(stream) {}
public override string DefaultFilename => "GameAssembly.dll"; public override string DefaultFilename => "GameAssembly.dll";
@@ -187,22 +185,21 @@ namespace Il2CppInspector
// Truncate memory stream at start of COFF header // Truncate memory stream at start of COFF header
var endOfSignature = ReadUInt32(0x3C) + 4; // DOS header + 4-byte PE signature var endOfSignature = ReadUInt32(0x3C) + 4; // DOS header + 4-byte PE signature
BaseStream.SetLength(endOfSignature); SetLength(endOfSignature);
// Re-write the stream (the headers are only necessary in case the user wants to save) // Re-write the stream (the headers are only necessary in case the user wants to save)
using var writer = new BinaryObjectWriter(BaseStream, Endianness, true); Position = endOfSignature;
writer.Position = endOfSignature; WriteObject(coff);
writer.WriteObject(coff); if (Bits == 32) WriteObject((PEOptHeader32) pe);
if (Bits == 32) writer.WriteObject((PEOptHeader32) pe); else WriteObject((PEOptHeader64) pe);
else writer.WriteObject((PEOptHeader64) pe); WriteArray(sections);
writer.WriteArray(sections); Write(peBytes, (int) Position, peBytes.Length - (int) Position);
writer.Write(peBytes, (int) Position, peBytes.Length - (int) Position);
IsModified = true; IsModified = true;
} }
// Raw file / unpacked file load strategies // Raw file / unpacked file load strategies
public override IEnumerable<IFileFormatReader> TryNextLoadStrategy() { public override IEnumerable<IFileFormatStream> TryNextLoadStrategy() {
// First load strategy: the regular file // First load strategy: the regular file
yield return this; yield return this;

View File

@@ -15,12 +15,10 @@ namespace Il2CppInspector
// This is a wrapper for a Linux memory dump // This is a wrapper for a Linux memory dump
// The supplied file is a text file containing the output of "cat /proc/["self"|process-id]/maps" // The supplied file is a text file containing the output of "cat /proc/["self"|process-id]/maps"
// We re-construct libil2cpp.so from the *.bin files and return it as the first image // We re-construct libil2cpp.so from the *.bin files and return it as the first image
internal class ProcessMapReader : FileFormatReader<ProcessMapReader> internal class ProcessMapReader : FileFormatStream<ProcessMapReader>
{ {
private MemoryStream il2cpp; private MemoryStream il2cpp;
public ProcessMapReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "maps.txt"; public override string DefaultFilename => "maps.txt";
protected override bool Init() { protected override bool Init() {
@@ -30,7 +28,7 @@ namespace Il2CppInspector
return false; return false;
// Get the entire stream as a string // Get the entire stream as a string
var text = System.Text.Encoding.ASCII.GetString((BaseStream as MemoryStream).ToArray()); var text = System.Text.Encoding.ASCII.GetString(ToArray());
// Line format is: https://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps // Line format is: https://stackoverflow.com/questions/1401359/understanding-linux-proc-id-maps
// xxxxxxxx-yyyyyyyy ffff zzzzzzzz aa:bb c [whitespace] [image path] // xxxxxxxx-yyyyyyyy ffff zzzzzzzz aa:bb c [whitespace] [image path]
@@ -105,10 +103,10 @@ namespace Il2CppInspector
return true; return true;
} }
public override IFileFormatReader this[uint index] { public override IFileFormatStream this[uint index] {
get { get {
// Get merged stream as ELF file // Get merged stream as ELF file
return (IFileFormatReader) ElfReader32.Load(il2cpp, LoadOptions, OnStatusUpdate) ?? ElfReader64.Load(il2cpp, LoadOptions, OnStatusUpdate); return (IFileFormatStream) ElfReader32.Load(il2cpp, LoadOptions, OnStatusUpdate) ?? ElfReader64.Load(il2cpp, LoadOptions, OnStatusUpdate);
} }
} }
} }

View File

@@ -24,10 +24,8 @@ namespace Il2CppInspector
// https://www.psxhax.com/threads/ps4-self-spkg-file-format-documentation-detailed-for-scene-devs.6636/ // https://www.psxhax.com/threads/ps4-self-spkg-file-format-documentation-detailed-for-scene-devs.6636/
// https://wiki.henkaku.xyz/vita/images/a/a2/Vita_SDK_specifications.pdf // https://wiki.henkaku.xyz/vita/images/a/a2/Vita_SDK_specifications.pdf
// https://www.psxhax.com/threads/make-fself-gui-for-flat_zs-make_fself-py-script-by-cfwprophet.3494/ // https://www.psxhax.com/threads/make-fself-gui-for-flat_zs-make_fself-py-script-by-cfwprophet.3494/
internal class SElfReader : FileFormatReader<SElfReader> internal class SElfReader : FileFormatStream<SElfReader>
{ {
public SElfReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "libil2cpp.so"; public override string DefaultFilename => "libil2cpp.so";
public override string Format => sceData.ProductType == (ulong) SElfExInfoTypes.PTYPE_FAKE? "FSELF" : "SELF"; public override string Format => sceData.ProductType == (ulong) SElfExInfoTypes.PTYPE_FAKE? "FSELF" : "SELF";

View File

@@ -9,12 +9,10 @@ using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector namespace Il2CppInspector
{ {
internal class UBReader : FileFormatReader<UBReader> internal class UBReader : FileFormatStream<UBReader>
{ {
private FatHeader header; private FatHeader header;
public UBReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "app"; public override string DefaultFilename => "app";
protected override bool Init() { protected override bool Init() {
@@ -30,7 +28,7 @@ namespace Il2CppInspector
return true; return true;
} }
public override IFileFormatReader this[uint index] { public override IFileFormatStream this[uint index] {
get { get {
Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch) Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch)
Endianness = Endianness.Big; Endianness = Endianness.Big;
@@ -41,7 +39,7 @@ namespace Il2CppInspector
Endianness = Endianness.Little; Endianness = Endianness.Little;
using var s = new MemoryStream(ReadBytes((int) arch.Size)); using var s = new MemoryStream(ReadBytes((int) arch.Size));
return (IFileFormatReader) MachOReader32.Load(s, LoadOptions, OnStatusUpdate) ?? MachOReader64.Load(s, LoadOptions, OnStatusUpdate); return (IFileFormatStream) MachOReader32.Load(s, LoadOptions, OnStatusUpdate) ?? MachOReader64.Load(s, LoadOptions, OnStatusUpdate);
} }
} }
} }

View File

@@ -17,7 +17,7 @@ namespace Il2CppInspector
{ {
public abstract partial class Il2CppBinary public abstract partial class Il2CppBinary
{ {
public IFileFormatReader Image { get; } public IFileFormatStream Image { get; }
// IL2CPP-only API exports with decrypted names // IL2CPP-only API exports with decrypted names
public Dictionary<string, ulong> APIExports { get; } = new Dictionary<string, ulong>(); public Dictionary<string, ulong> APIExports { get; } = new Dictionary<string, ulong>();
@@ -92,7 +92,7 @@ namespace Il2CppInspector
private bool isModified = false; private bool isModified = false;
public bool IsModified => Image.IsModified || isModified; public bool IsModified => Image.IsModified || isModified;
protected Il2CppBinary(IFileFormatReader stream, EventHandler<string> statusCallback = null) { protected Il2CppBinary(IFileFormatStream stream, EventHandler<string> statusCallback = null) {
Image = stream; Image = stream;
OnStatusUpdate = statusCallback; OnStatusUpdate = statusCallback;
@@ -100,7 +100,7 @@ namespace Il2CppInspector
DiscoverAPIExports(); DiscoverAPIExports();
} }
protected Il2CppBinary(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null) { protected Il2CppBinary(IFileFormatStream stream, uint codeRegistration, uint metadataRegistration, EventHandler<string> statusCallback = null) {
Image = stream; Image = stream;
OnStatusUpdate = statusCallback; OnStatusUpdate = statusCallback;
@@ -110,7 +110,7 @@ namespace Il2CppInspector
} }
// Load and initialize a binary of any supported architecture // Load and initialize a binary of any supported architecture
private static Il2CppBinary LoadImpl(IFileFormatReader stream, EventHandler<string> statusCallback) { private static Il2CppBinary LoadImpl(IFileFormatStream stream, EventHandler<string> statusCallback) {
// Get type from image architecture // Get type from image architecture
var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper()); var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper());
if (type == null) if (type == null)
@@ -118,15 +118,15 @@ namespace Il2CppInspector
// Set width of long (convert to sizeof(int) for 32-bit files) // Set width of long (convert to sizeof(int) for 32-bit files)
if (stream[0].Bits == 32) { if (stream[0].Bits == 32) {
stream[0].Stream.PrimitiveMappings.Add(typeof(long), typeof(int)); stream[0].AddPrimitiveMapping(typeof(long), typeof(int));
stream[0].Stream.PrimitiveMappings.Add(typeof(ulong), typeof(uint)); stream[0].AddPrimitiveMapping(typeof(ulong), typeof(uint));
} }
return (Il2CppBinary) Activator.CreateInstance(type, stream[0], statusCallback); return (Il2CppBinary) Activator.CreateInstance(type, stream[0], statusCallback);
} }
// Load binary without a global-metadata.dat available // Load binary without a global-metadata.dat available
public static Il2CppBinary Load(IFileFormatReader stream, double metadataVersion, EventHandler<string> statusCallback = null) { public static Il2CppBinary Load(IFileFormatStream stream, double metadataVersion, EventHandler<string> statusCallback = null) {
foreach (var loadedImage in stream.TryNextLoadStrategy()) { foreach (var loadedImage in stream.TryNextLoadStrategy()) {
var inst = LoadImpl(stream, statusCallback); var inst = LoadImpl(stream, statusCallback);
if (inst.FindRegistrationStructs(metadataVersion)) if (inst.FindRegistrationStructs(metadataVersion))
@@ -140,7 +140,7 @@ namespace Il2CppInspector
// If it is specified and both symbol table and function scanning fail, // If it is specified and both symbol table and function scanning fail,
// Metadata will be used to try to find the required structures with data analysis // Metadata will be used to try to find the required structures with data analysis
// If it is not specified, data analysis will not be performed // If it is not specified, data analysis will not be performed
public static Il2CppBinary Load(IFileFormatReader stream, Metadata metadata, EventHandler<string> statusCallback = null) { public static Il2CppBinary Load(IFileFormatStream stream, Metadata metadata, EventHandler<string> statusCallback = null) {
foreach (var loadedImage in stream.TryNextLoadStrategy()) { foreach (var loadedImage in stream.TryNextLoadStrategy()) {
var inst = LoadImpl(stream, statusCallback); var inst = LoadImpl(stream, statusCallback);
if (inst.FindRegistrationStructs(metadata)) if (inst.FindRegistrationStructs(metadata))
@@ -154,7 +154,7 @@ namespace Il2CppInspector
public void SaveToFile(string pathname) { public void SaveToFile(string pathname) {
Image.Position = 0; Image.Position = 0;
using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write)) using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write))
Image.Stream.BaseStream.CopyTo(outFile); Image.CopyTo(outFile);
} }
// Initialize binary without a global-metadata.dat available // Initialize binary without a global-metadata.dat available
@@ -244,7 +244,7 @@ namespace Il2CppInspector
} }
// Architecture-specific search function // Architecture-specific search function
protected abstract (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc); protected abstract (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc);
// Load all of the discovered metadata in the binary // Load all of the discovered metadata in the binary
private void PrepareMetadata(ulong codeRegistration, ulong metadataRegistration, Metadata metadata = null) { private void PrepareMetadata(ulong codeRegistration, ulong metadataRegistration, Metadata metadata = null) {

View File

@@ -66,7 +66,7 @@ namespace Il2CppInspector
public Dictionary<Il2CppMethodSpec, int> GenericMethodInvokerIndices => Binary.GenericMethodInvokerIndices; public Dictionary<Il2CppMethodSpec, int> GenericMethodInvokerIndices => Binary.GenericMethodInvokerIndices;
// TODO: Finish all file access in the constructor and eliminate the need for this // TODO: Finish all file access in the constructor and eliminate the need for this
public IFileFormatReader BinaryImage => Binary.Image; public IFileFormatStream BinaryImage => Binary.Image;
private (ulong MetadataAddress, object Value)? getDefaultValue(int typeIndex, int dataIndex) { private (ulong MetadataAddress, object Value)? getDefaultValue(int typeIndex, int dataIndex) {
// No default // No default
@@ -543,7 +543,7 @@ namespace Il2CppInspector
// Load the metadata file // Load the metadata file
Metadata metadata; Metadata metadata;
try { try {
metadata = new Metadata(metadataStream, statusCallback); metadata = Metadata.FromStream(metadataStream, statusCallback);
} }
catch (Exception ex) { catch (Exception ex) {
Console.Error.WriteLine(ex.Message); Console.Error.WriteLine(ex.Message);
@@ -554,9 +554,9 @@ namespace Il2CppInspector
Console.WriteLine("Detected metadata version " + metadata.Version); Console.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)
IFileFormatReader stream; IFileFormatStream stream;
try { try {
stream = FileFormatReader.Load(binaryStream, loadOptions, statusCallback); stream = FileFormatStream.Load(binaryStream, loadOptions, statusCallback);
if (stream == null) if (stream == null)
throw new InvalidOperationException("Unsupported executable file format"); throw new InvalidOperationException("Unsupported executable file format");
@@ -571,7 +571,7 @@ 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); Console.WriteLine("Container format: " + image.Format);
Console.WriteLine("Container endianness: " + ((BinaryObjectReader) image).Endianness); Console.WriteLine("Container endianness: " + ((BinaryObjectStream) image).Endianness);
Console.WriteLine("Architecture word size: {0}-bit", image.Bits); Console.WriteLine("Architecture word size: {0}-bit", image.Bits);
Console.WriteLine("Instruction set: " + image.Arch); Console.WriteLine("Instruction set: " + image.Arch);
Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset); Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);

View File

@@ -14,44 +14,60 @@ using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector namespace Il2CppInspector
{ {
public class Metadata : BinaryObjectReader public class Metadata : BinaryObjectStream
{ {
public Il2CppGlobalMetadataHeader Header { get; set; } public Il2CppGlobalMetadataHeader Header { get; set; }
public Il2CppAssemblyDefinition[] Assemblies { get; } public Il2CppAssemblyDefinition[] Assemblies { get; set; }
public Il2CppImageDefinition[] Images { get; } public Il2CppImageDefinition[] Images { get; set; }
public Il2CppTypeDefinition[] Types { get; } public Il2CppTypeDefinition[] Types { get; set; }
public Il2CppMethodDefinition[] Methods { get; } public Il2CppMethodDefinition[] Methods { get; set; }
public Il2CppParameterDefinition[] Params { get; } public Il2CppParameterDefinition[] Params { get; set; }
public Il2CppFieldDefinition[] Fields { get; } public Il2CppFieldDefinition[] Fields { get; set; }
public Il2CppFieldDefaultValue[] FieldDefaultValues { get; } public Il2CppFieldDefaultValue[] FieldDefaultValues { get; set; }
public Il2CppParameterDefaultValue[] ParameterDefaultValues { get; } public Il2CppParameterDefaultValue[] ParameterDefaultValues { get; set; }
public Il2CppPropertyDefinition[] Properties { get; } public Il2CppPropertyDefinition[] Properties { get; set; }
public Il2CppEventDefinition[] Events { get; } public Il2CppEventDefinition[] Events { get; set; }
public Il2CppGenericContainer[] GenericContainers { get; } public Il2CppGenericContainer[] GenericContainers { get; set; }
public Il2CppGenericParameter[] GenericParameters { get; } public Il2CppGenericParameter[] GenericParameters { get; set; }
public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges { get; } public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges { get; set; }
public Il2CppInterfaceOffsetPair[] InterfaceOffsets { get; } public Il2CppInterfaceOffsetPair[] InterfaceOffsets { get; set; }
public Il2CppMetadataUsageList[] MetadataUsageLists { get; } public Il2CppMetadataUsageList[] MetadataUsageLists { get; set; }
public Il2CppMetadataUsagePair[] MetadataUsagePairs { get; } public Il2CppMetadataUsagePair[] MetadataUsagePairs { get; set; }
public Il2CppFieldRef[] FieldRefs { get; } public Il2CppFieldRef[] FieldRefs { get; set; }
public int[] InterfaceUsageIndices { get; } public int[] InterfaceUsageIndices { get; set; }
public int[] NestedTypeIndices { get; } public int[] NestedTypeIndices { get; set; }
public int[] AttributeTypeIndices { get; } public int[] AttributeTypeIndices { get; set; }
public int[] GenericConstraintIndices { get; } public int[] GenericConstraintIndices { get; set; }
public uint[] VTableMethodIndices { get; } public uint[] VTableMethodIndices { get; set; }
public string[] StringLiterals { get; } public string[] StringLiterals { get; set; }
public Dictionary<int, string> Strings { get; } = new Dictionary<int, string>(); public Dictionary<int, string> Strings { get; } = new Dictionary<int, string>();
// Set if something in the metadata has been modified / decrypted // Set if something in the metadata has been modified / decrypted
public bool IsModified { get; } = false; public bool IsModified { get; private set; } = false;
public Metadata(MemoryStream stream, EventHandler<string> statusCallback = null) : base(stream) // Status update callback
private EventHandler<string> OnStatusUpdate { get; set; }
private void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status);
// Initialize metadata object from a stream
public static Metadata FromStream(MemoryStream stream, EventHandler<string> statusCallback = null) {
var metadata = new Metadata(statusCallback);
stream.Position = 0;
stream.CopyTo(metadata);
metadata.Position = 0;
metadata.Initialize();
return metadata;
}
private Metadata(EventHandler<string> statusCallback = null) : base() => OnStatusUpdate = statusCallback;
private void Initialize()
{ {
// Pre-processing hook // Pre-processing hook
var pluginResult = PluginHooks.PreProcessMetadata(stream); var pluginResult = PluginHooks.PreProcessMetadata(this);
IsModified = pluginResult.IsStreamModified; IsModified = pluginResult.IsStreamModified;
// Read metadata header // Read metadata header
@@ -185,7 +201,7 @@ namespace Il2CppInspector
if (stringOffsets.Except(Strings.Keys).Any()) { if (stringOffsets.Except(Strings.Keys).Any()) {
Console.WriteLine("Decrypting strings..."); Console.WriteLine("Decrypting strings...");
statusCallback?.Invoke(this, "Decrypting strings"); StatusUpdate("Decrypting strings");
// There may be zero-padding at the end of the last string since counts seem to be word-aligned // There may be zero-padding at the end of the last string since counts seem to be word-aligned
// Find the true location one byte after the final character of the final string // Find the true location one byte after the final character of the final string
@@ -209,13 +225,10 @@ namespace Il2CppInspector
} }
// Write changes back in case the user wants to save the metadata file // Write changes back in case the user wants to save the metadata file
using (var sw = new BinaryObjectWriter(BaseStream, Endianness, leaveOpen: true)) { Position = Header.stringOffset;
sw.Version = Version;
sw.Position = Header.stringOffset;
foreach (var str in Strings.OrderBy(s => s.Key)) foreach (var str in Strings.OrderBy(s => s.Key))
sw.WriteNullTerminatedString(str.Value); WriteNullTerminatedString(str.Value);
sw.Flush(); Flush();
}
IsModified = true; IsModified = true;
} }
@@ -235,7 +248,7 @@ namespace Il2CppInspector
public void SaveToFile(string pathname) { public void SaveToFile(string pathname) {
Position = 0; Position = 0;
using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write)) using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write))
BaseStream.CopyTo(outFile); CopyTo(outFile);
} }
internal int Sizeof(Type type) => Sizeof(type, Version); internal int Sizeof(Type type) => Sizeof(type, Version);

View File

@@ -801,17 +801,9 @@ namespace Il2CppInspector
MetadataRegistration.methodReferences = 0; MetadataRegistration.methodReferences = 0;
// Write changes to stream // Write changes to stream
using var sw = new BinaryObjectWriter(Image.Stream.BaseStream, Image.Stream.Endianness, true);
sw.Version = Image.Version;
// Set width of long (convert to sizeof(int) for 32-bit files) Image.WriteObject(Image.MapVATR(CodeRegistrationPointer), CodeRegistration);
if (Image.Bits == 32) { Image.WriteObject(Image.MapVATR(MetadataRegistrationPointer), MetadataRegistration);
sw.PrimitiveMappings.Add(typeof(long), typeof(int));
sw.PrimitiveMappings.Add(typeof(ulong), typeof(uint));
}
sw.WriteObject(Image.MapVATR(CodeRegistrationPointer), CodeRegistration);
sw.WriteObject(Image.MapVATR(MetadataRegistrationPointer), MetadataRegistration);
isModified = true; isModified = true;
StatusUpdate("Analyzing IL2CPP image"); StatusUpdate("Analyzing IL2CPP image");

View File

@@ -82,7 +82,7 @@ namespace Il2CppInspector.Model
public int WordSizeBytes => WordSizeBits / 8; public int WordSizeBytes => WordSizeBits / 8;
// The binary image // The binary image
public IFileFormatReader Image => Package.BinaryImage; public IFileFormatStream Image => Package.BinaryImage;
// The IL2CPP package for this application // The IL2CPP package for this application
public Il2CppInspector Package => TypeModel.Package; public Il2CppInspector Package => TypeModel.Package;

View File

@@ -10,7 +10,7 @@ namespace Il2CppInspector
{ {
internal class ULEB128 internal class ULEB128
{ {
public static ulong Decode(IFileFormatReader next) { public static ulong Decode(IFileFormatStream next) {
ulong uleb = 0; ulong uleb = 0;
byte b = 0x80; byte b = 0x80;
for (var shift = 0; b >> 7 == 1; shift += 7) { for (var shift = 0; b >> 7 == 1; shift += 7) {

View File

@@ -126,7 +126,7 @@ namespace Il2CppInspectorGUI
try { try {
OnStatusUpdate?.Invoke(this, "Processing metadata"); OnStatusUpdate?.Invoke(this, "Processing metadata");
metadata = new Metadata(metadataStream, StatusUpdate); metadata = Metadata.FromStream(metadataStream, StatusUpdate);
return true; return true;
} }
catch (Exception ex) { catch (Exception ex) {
@@ -150,7 +150,7 @@ namespace Il2CppInspectorGUI
OnStatusUpdate?.Invoke(this, "Processing binary"); OnStatusUpdate?.Invoke(this, "Processing binary");
// This may throw other exceptions from the individual loaders as well // This may throw other exceptions from the individual loaders as well
IFileFormatReader stream = FileFormatReader.Load(binaryStream, LoadOptions, StatusUpdate); IFileFormatStream stream = FileFormatStream.Load(binaryStream, LoadOptions, StatusUpdate);
if (stream == null) { if (stream == null) {
throw new InvalidOperationException("Could not determine the binary file format"); throw new InvalidOperationException("Could not determine the binary file format");
} }