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
{
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) { }
// 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
private uint getNextThumbInstruction(IFileFormatReader image) {
private uint getNextThumbInstruction(IFileFormatStream image) {
// Assume 16-bit
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;
// 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
var regs = new Dictionary<uint, uint>();
@@ -200,7 +200,7 @@ namespace Il2CppInspector
}
// 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
var func = new List<uint>();
uint inst;
@@ -215,7 +215,7 @@ namespace Il2CppInspector
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
ulong metadataRegistration, codeRegistration;
@@ -264,7 +264,7 @@ namespace Il2CppInspector
// - B
// 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 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
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) { }
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 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
var regs = new Dictionary<uint, ulong>();
@@ -118,7 +118,7 @@ namespace Il2CppInspector
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
var func = new List<uint>();
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 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
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc) {
// Load function into memory
// In practice, the longest function length we need is not generally longer than 7 instructions (0x1C bytes)
var func = getFunctionAtFileOffset(image, loc, 7);

View File

@@ -13,9 +13,9 @@ namespace Il2CppInspector
{
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) { }
// Format of 64-bit LEA:
@@ -94,7 +94,7 @@ namespace Il2CppInspector
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
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
{
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) { }
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
protected override (ulong, ulong) ConsiderCode(IFileFormatStream image, uint loc) {
ulong metadata, code;
long pCgr;

View File

@@ -16,6 +16,6 @@ namespace Il2CppInspector.Cpp
public static class CppCompiler
{
// 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
{
// 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 ZipArchiveEntry[] binaryFiles;
public AABReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "Package.aab";
protected override bool Init() {
@@ -32,7 +30,7 @@ namespace Il2CppInspector
return false;
try {
zip = new ZipArchive(BaseStream);
zip = new ZipArchive(this);
// Get list of binary files
binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("base/lib/") && f.Name == "libil2cpp.so").ToArray();
@@ -51,10 +49,10 @@ namespace Il2CppInspector
return true;
}
public override IFileFormatReader this[uint index] {
public override IFileFormatStream this[uint index] {
get {
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
var binary = binaryFiles[index].Open();

View File

@@ -13,13 +13,11 @@ using System.Linq;
namespace Il2CppInspector
{
// 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 ZipArchiveEntry[] binaryFiles;
public APKReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "Package.apk";
protected override bool Init() {
@@ -32,7 +30,7 @@ namespace Il2CppInspector
return false;
try {
zip = new ZipArchive(BaseStream);
zip = new ZipArchive(this);
// Get list of binary files
binaryFiles = zip.Entries.Where(f => f.FullName.StartsWith("lib/") && f.Name == "libil2cpp.so").ToArray();
@@ -51,10 +49,10 @@ namespace Il2CppInspector
return true;
}
public override IFileFormatReader this[uint index] {
public override IFileFormatStream this[uint index] {
get {
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
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>
{
public ElfReader32(Stream stream) : base(stream) {
public ElfReader32() : base() {
ElfReloc.GetRelocType = info => (Elf) (info & 0xff);
ElfReloc.GetSymbolIndex = info => info >> 8;
}
@@ -25,12 +25,12 @@ namespace Il2CppInspector
public override int Bits => 32;
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>
{
public ElfReader64(Stream stream) : base(stream) {
public ElfReader64() : base() {
ElfReloc.GetRelocType = info => (Elf) (info & 0xffff_ffff);
ElfReloc.GetSymbolIndex = info => info >> 32;
}
@@ -38,7 +38,7 @@ namespace Il2CppInspector
public override int Bits => 64;
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
@@ -46,12 +46,12 @@ namespace Il2CppInspector
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 TPHdr : Ielf_phdr<TWord>, new()
where TSym : Ielf_sym<TWord>, new()
where TConvert : IWordConverter<TWord>, new()
where TReader : FileFormatReader<TReader>
where TReader : FileFormatStream<TReader>
{
private readonly TConvert conv = new TConvert();
@@ -115,8 +115,6 @@ namespace Il2CppInspector
private bool preferPHT = false;
private bool isMemoryImage = false;
public ElfReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "libil2cpp.so";
public override string Format => Bits == 32 ? "ELF" : "ELF64";
@@ -141,7 +139,7 @@ namespace Il2CppInspector
protected abstract Elf ArchClass { get; }
protected abstract void Write(BinaryWriter writer, TWord value);
protected abstract void WriteWord(TWord value);
protected override bool Init() {
elf_header = ReadObject<elf_header<TWord>>();
@@ -154,10 +152,6 @@ namespace Il2CppInspector
if ((Elf) elf_header.m_arch != ArchClass)
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
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);
@@ -283,7 +277,6 @@ namespace Il2CppInspector
}
// Process relocations
using var writer = new BinaryWriter(BaseStream, Encoding.Default, true);
var relsz = Sizeof(typeof(TSym));
var currentRel = 0;
@@ -334,7 +327,7 @@ namespace Il2CppInspector
if (result.recognized) {
Position = MapVATR(conv.ULong(rel.Offset));
Write(writer, result.newValue);
WriteWord(result.newValue);
}
}
Console.WriteLine($"Processed {rels.Count} relocations");
@@ -525,12 +518,9 @@ namespace Il2CppInspector
}
private void xorRange(int offset, int length, byte xorValue) {
using var writer = new BinaryWriter(BaseStream, Encoding.Default, true);
var bytes = ReadBytes(offset, length);
bytes = bytes.Select(b => (byte) (b ^ xorValue)).ToArray();
writer.Seek(offset, SeekOrigin.Begin);
writer.Write(bytes);
Write(offset, bytes);
}
private void xorSection(string sectionName, byte xorValue, uint stripeSize) {
@@ -571,8 +561,7 @@ namespace Il2CppInspector
}
// Rewrite to stream
using var writer = new BinaryObjectWriter(BaseStream, Endianness, true);
writer.WriteArray(conv.Long(elf_header.e_phoff), program_header_table);
WriteArray(conv.Long(elf_header.e_phoff), program_header_table);
IsModified = true;
// Rebase dynamic table if it exists
@@ -595,7 +584,7 @@ namespace Il2CppInspector
section.d_un = conv.Add(section.d_un, imageBase);
// Rewrite to stream
writer.WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
WriteArray(conv.Long(PT_DYNAMIC.p_offset), dt);
}
private void processSymbols() {

View File

@@ -14,23 +14,22 @@ using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
public interface IFileFormatReader
public interface IFileFormatStream
{
BinaryObjectReader Stream { get; }
double Version { get; set; }
long Length { get; }
uint NumImages { get; }
string DefaultFilename { get; }
bool IsModified { get; }
IEnumerable<IFileFormatReader> 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
IEnumerable<IFileFormatStream> Images { get; } // Each child image of this object (eg. 32/64-bit versions in Fat MachO file)
IFileFormatStream this[uint index] { get; } // With no additional override, one object = one file, this[0] == this
long Position { get; set; }
string Format { get; }
string Arch { get; }
int Bits { get; }
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)
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();
uint[] GetFunctionTable();
IEnumerable<Export> GetExports();
@@ -92,24 +91,52 @@ namespace Il2CppInspector
long[] ReadMappedWordArray(ulong uiAddr, int count);
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; }
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
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);
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
.Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatReader)) && !x.IsGenericTypeDefinition);
.Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatStream)) && !x.IsGenericTypeDefinition);
foreach (var type in types) {
try {
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
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;
}
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 bool IsModified { get; protected set; } = false;
public long Length => BaseStream.Length;
public uint NumImages { get; protected set; } = 1;
public ulong GlobalOffset { get; protected set; }
@@ -151,7 +172,7 @@ namespace Il2CppInspector
protected void StatusUpdate(string status) => OnStatusUpdate?.Invoke(this, status);
public IEnumerable<IFileFormatReader> Images {
public IEnumerable<IFileFormatStream> Images {
get {
for (uint i = 0; i < NumImages; i++)
yield return this[i];
@@ -165,15 +186,12 @@ namespace Il2CppInspector
public static T Load(Stream stream, LoadOptions loadOptions = null, EventHandler<string> statusCallback = null) {
// Copy the original stream in case we modify it
var ms = new MemoryStream();
if (stream.CanSeek)
stream.Position = 0;
stream.CopyTo(ms);
ms.Position = 0;
var pe = (T) Activator.CreateInstance(typeof(T), ms);
return pe.InitImpl(loadOptions, statusCallback) ? pe : null;
var binary = (T) Activator.CreateInstance(typeof(T));
stream.CopyTo(binary);
binary.Position = 0;
return binary.InitImpl(loadOptions, statusCallback) ? binary : null;
}
private bool InitImpl(LoadOptions loadOptions, EventHandler<string> statusCallback) {
@@ -186,10 +204,10 @@ namespace Il2CppInspector
protected virtual bool Init() => throw new NotImplementedException();
// 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
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
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>
{
public MachOReader32(Stream stream) : base(stream) { }
public override int Bits => 32;
protected override bool checkMagicLE(MachO magic) => magic == MachO.MH_MAGIC;
@@ -37,8 +35,6 @@ namespace Il2CppInspector
internal class MachOReader64 : MachOReader<ulong, MachOReader64, Convert64>
{
public MachOReader64(Stream stream) : base(stream) { }
public override int Bits => 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)"
// 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 TReader : FileFormatReader<TReader>
where TReader : FileFormatStream<TReader>
where TConvert : IWordConverter<TWord>, new()
{
private readonly TConvert conv = new TConvert();
@@ -75,8 +71,6 @@ namespace Il2CppInspector
private List<Export> exports = new List<Export>();
protected MachOReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "app";
public override string Format => "Mach-O " + (Bits == 32 ? "32-bit" : "64-bit");

View File

@@ -17,7 +17,7 @@ namespace Il2CppInspector
// References:
// 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
internal class PEReader : FileFormatReader<PEReader>
internal class PEReader : FileFormatStream<PEReader>
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
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"
};
public PEReader(Stream stream) : base(stream) {}
public override string DefaultFilename => "GameAssembly.dll";
@@ -187,22 +185,21 @@ namespace Il2CppInspector
// Truncate memory stream at start of COFF header
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)
using var writer = new BinaryObjectWriter(BaseStream, Endianness, true);
writer.Position = endOfSignature;
writer.WriteObject(coff);
if (Bits == 32) writer.WriteObject((PEOptHeader32) pe);
else writer.WriteObject((PEOptHeader64) pe);
writer.WriteArray(sections);
writer.Write(peBytes, (int) Position, peBytes.Length - (int) Position);
Position = endOfSignature;
WriteObject(coff);
if (Bits == 32) WriteObject((PEOptHeader32) pe);
else WriteObject((PEOptHeader64) pe);
WriteArray(sections);
Write(peBytes, (int) Position, peBytes.Length - (int) Position);
IsModified = true;
}
// Raw file / unpacked file load strategies
public override IEnumerable<IFileFormatReader> TryNextLoadStrategy() {
public override IEnumerable<IFileFormatStream> TryNextLoadStrategy() {
// First load strategy: the regular file
yield return this;

View File

@@ -15,12 +15,10 @@ namespace Il2CppInspector
// 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"
// 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;
public ProcessMapReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "maps.txt";
protected override bool Init() {
@@ -30,7 +28,7 @@ namespace Il2CppInspector
return false;
// 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
// xxxxxxxx-yyyyyyyy ffff zzzzzzzz aa:bb c [whitespace] [image path]
@@ -105,10 +103,10 @@ namespace Il2CppInspector
return true;
}
public override IFileFormatReader this[uint index] {
public override IFileFormatStream this[uint index] {
get {
// 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://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/
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 Format => sceData.ProductType == (ulong) SElfExInfoTypes.PTYPE_FAKE? "FSELF" : "SELF";

View File

@@ -9,12 +9,10 @@ using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
internal class UBReader : FileFormatReader<UBReader>
internal class UBReader : FileFormatStream<UBReader>
{
private FatHeader header;
public UBReader(Stream stream) : base(stream) { }
public override string DefaultFilename => "app";
protected override bool Init() {
@@ -30,7 +28,7 @@ namespace Il2CppInspector
return true;
}
public override IFileFormatReader this[uint index] {
public override IFileFormatStream this[uint index] {
get {
Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch)
Endianness = Endianness.Big;
@@ -41,7 +39,7 @@ namespace Il2CppInspector
Endianness = Endianness.Little;
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 IFileFormatReader Image { get; }
public IFileFormatStream Image { get; }
// IL2CPP-only API exports with decrypted names
public Dictionary<string, ulong> APIExports { get; } = new Dictionary<string, ulong>();
@@ -92,7 +92,7 @@ namespace Il2CppInspector
private bool isModified = false;
public bool IsModified => Image.IsModified || isModified;
protected Il2CppBinary(IFileFormatReader stream, EventHandler<string> statusCallback = null) {
protected Il2CppBinary(IFileFormatStream stream, EventHandler<string> statusCallback = null) {
Image = stream;
OnStatusUpdate = statusCallback;
@@ -100,7 +100,7 @@ namespace Il2CppInspector
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;
OnStatusUpdate = statusCallback;
@@ -110,7 +110,7 @@ namespace Il2CppInspector
}
// 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
var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper());
if (type == null)
@@ -118,15 +118,15 @@ namespace Il2CppInspector
// Set width of long (convert to sizeof(int) for 32-bit files)
if (stream[0].Bits == 32) {
stream[0].Stream.PrimitiveMappings.Add(typeof(long), typeof(int));
stream[0].Stream.PrimitiveMappings.Add(typeof(ulong), typeof(uint));
stream[0].AddPrimitiveMapping(typeof(long), typeof(int));
stream[0].AddPrimitiveMapping(typeof(ulong), typeof(uint));
}
return (Il2CppBinary) Activator.CreateInstance(type, stream[0], statusCallback);
}
// 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()) {
var inst = LoadImpl(stream, statusCallback);
if (inst.FindRegistrationStructs(metadataVersion))
@@ -140,7 +140,7 @@ namespace Il2CppInspector
// 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
// 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()) {
var inst = LoadImpl(stream, statusCallback);
if (inst.FindRegistrationStructs(metadata))
@@ -154,7 +154,7 @@ namespace Il2CppInspector
public void SaveToFile(string pathname) {
Image.Position = 0;
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
@@ -244,7 +244,7 @@ namespace Il2CppInspector
}
// 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
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;
// 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) {
// No default
@@ -543,7 +543,7 @@ namespace Il2CppInspector
// Load the metadata file
Metadata metadata;
try {
metadata = new Metadata(metadataStream, statusCallback);
metadata = Metadata.FromStream(metadataStream, statusCallback);
}
catch (Exception ex) {
Console.Error.WriteLine(ex.Message);
@@ -554,9 +554,9 @@ namespace Il2CppInspector
Console.WriteLine("Detected metadata version " + metadata.Version);
// Load the il2cpp code file (try all available file formats)
IFileFormatReader stream;
IFileFormatStream stream;
try {
stream = FileFormatReader.Load(binaryStream, loadOptions, statusCallback);
stream = FileFormatStream.Load(binaryStream, loadOptions, statusCallback);
if (stream == null)
throw new InvalidOperationException("Unsupported executable file format");
@@ -571,7 +571,7 @@ namespace Il2CppInspector
var processors = new List<Il2CppInspector>();
foreach (var image in stream.Images) {
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("Instruction set: " + image.Arch);
Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);

View File

@@ -14,44 +14,60 @@ using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
public class Metadata : BinaryObjectReader
public class Metadata : BinaryObjectStream
{
public Il2CppGlobalMetadataHeader Header { get; set; }
public Il2CppAssemblyDefinition[] Assemblies { get; }
public Il2CppImageDefinition[] Images { get; }
public Il2CppTypeDefinition[] Types { get; }
public Il2CppMethodDefinition[] Methods { get; }
public Il2CppParameterDefinition[] Params { get; }
public Il2CppFieldDefinition[] Fields { get; }
public Il2CppFieldDefaultValue[] FieldDefaultValues { get; }
public Il2CppParameterDefaultValue[] ParameterDefaultValues { get; }
public Il2CppPropertyDefinition[] Properties { get; }
public Il2CppEventDefinition[] Events { get; }
public Il2CppGenericContainer[] GenericContainers { get; }
public Il2CppGenericParameter[] GenericParameters { get; }
public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges { get; }
public Il2CppInterfaceOffsetPair[] InterfaceOffsets { get; }
public Il2CppMetadataUsageList[] MetadataUsageLists { get; }
public Il2CppMetadataUsagePair[] MetadataUsagePairs { get; }
public Il2CppFieldRef[] FieldRefs { get; }
public Il2CppAssemblyDefinition[] Assemblies { get; set; }
public Il2CppImageDefinition[] Images { get; set; }
public Il2CppTypeDefinition[] Types { get; set; }
public Il2CppMethodDefinition[] Methods { get; set; }
public Il2CppParameterDefinition[] Params { get; set; }
public Il2CppFieldDefinition[] Fields { get; set; }
public Il2CppFieldDefaultValue[] FieldDefaultValues { get; set; }
public Il2CppParameterDefaultValue[] ParameterDefaultValues { get; set; }
public Il2CppPropertyDefinition[] Properties { get; set; }
public Il2CppEventDefinition[] Events { get; set; }
public Il2CppGenericContainer[] GenericContainers { get; set; }
public Il2CppGenericParameter[] GenericParameters { get; set; }
public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges { get; set; }
public Il2CppInterfaceOffsetPair[] InterfaceOffsets { get; set; }
public Il2CppMetadataUsageList[] MetadataUsageLists { get; set; }
public Il2CppMetadataUsagePair[] MetadataUsagePairs { get; set; }
public Il2CppFieldRef[] FieldRefs { get; set; }
public int[] InterfaceUsageIndices { get; }
public int[] NestedTypeIndices { get; }
public int[] AttributeTypeIndices { get; }
public int[] GenericConstraintIndices { get; }
public uint[] VTableMethodIndices { get; }
public string[] StringLiterals { get; }
public int[] InterfaceUsageIndices { get; set; }
public int[] NestedTypeIndices { get; set; }
public int[] AttributeTypeIndices { get; set; }
public int[] GenericConstraintIndices { get; set; }
public uint[] VTableMethodIndices { get; set; }
public string[] StringLiterals { get; set; }
public Dictionary<int, string> Strings { get; } = new Dictionary<int, string>();
// 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
var pluginResult = PluginHooks.PreProcessMetadata(stream);
var pluginResult = PluginHooks.PreProcessMetadata(this);
IsModified = pluginResult.IsStreamModified;
// Read metadata header
@@ -185,7 +201,7 @@ namespace Il2CppInspector
if (stringOffsets.Except(Strings.Keys).Any()) {
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
// 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
using (var sw = new BinaryObjectWriter(BaseStream, Endianness, leaveOpen: true)) {
sw.Version = Version;
sw.Position = Header.stringOffset;
foreach (var str in Strings.OrderBy(s => s.Key))
sw.WriteNullTerminatedString(str.Value);
sw.Flush();
}
Position = Header.stringOffset;
foreach (var str in Strings.OrderBy(s => s.Key))
WriteNullTerminatedString(str.Value);
Flush();
IsModified = true;
}
@@ -235,7 +248,7 @@ namespace Il2CppInspector
public void SaveToFile(string pathname) {
Position = 0;
using (var outFile = new FileStream(pathname, FileMode.Create, FileAccess.Write))
BaseStream.CopyTo(outFile);
CopyTo(outFile);
}
internal int Sizeof(Type type) => Sizeof(type, Version);

View File

@@ -801,17 +801,9 @@ namespace Il2CppInspector
MetadataRegistration.methodReferences = 0;
// 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)
if (Image.Bits == 32) {
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);
Image.WriteObject(Image.MapVATR(CodeRegistrationPointer), CodeRegistration);
Image.WriteObject(Image.MapVATR(MetadataRegistrationPointer), MetadataRegistration);
isModified = true;
StatusUpdate("Analyzing IL2CPP image");

View File

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

View File

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

View File

@@ -126,7 +126,7 @@ namespace Il2CppInspectorGUI
try {
OnStatusUpdate?.Invoke(this, "Processing metadata");
metadata = new Metadata(metadataStream, StatusUpdate);
metadata = Metadata.FromStream(metadataStream, StatusUpdate);
return true;
}
catch (Exception ex) {
@@ -150,7 +150,7 @@ namespace Il2CppInspectorGUI
OnStatusUpdate?.Invoke(this, "Processing binary");
// 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) {
throw new InvalidOperationException("Could not determine the binary file format");
}