Refactor solution layout

This commit is contained in:
Katy Coe
2020-02-06 02:51:42 +01:00
parent 66b8e30586
commit e971cb8502
49 changed files with 72 additions and 50 deletions

View File

@@ -0,0 +1,282 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace Il2CppInspector
{
internal class Il2CppBinaryARM : Il2CppBinary
{
public Il2CppBinaryARM(IFileFormatReader stream) : base(stream) { }
public Il2CppBinaryARM(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
// Thumb 2 Supplement Reference Manual: http://class.ece.iastate.edu/cpre288/resources/docs/Thumb-2SupplementReferenceManual.pdf
// Section 3.1
private uint getNextThumbInstruction(IFileFormatReader image) {
// Assume 16-bit
uint inst = image.ReadUInt16();
// Is 32-bit?
if (inst.Bits(13, 15) == 0b111)
if (inst.Bits(11, 2) != 0b00)
inst = (inst << 16) + image.ReadUInt16();
return inst;
}
// Page 4-166 (MOVS encoding T1, MOVW encoding T3), Page 4-171 (MOVT)
// In Thumb, an 8-byte MOV instruction is MOVW followed by MOVT
private enum Thumb : uint
{
MovW = 0b100100,
MovT = 0b101100
}
private (uint reg_d, ushort imm)? getMovImm(uint inst, Thumb movType) {
uint reg_d, imm;
// Encoding T1
if (inst.Bits(11, 21) == 0b00100 && movType == Thumb.MovW) {
imm = inst.Bits(0, 8);
reg_d = inst.Bits(8, 3);
return (reg_d, (ushort) imm);
}
// Encoding T3
if (inst.Bits(20, 6) != (uint) movType || inst.Bits(27, 5) != 0b11110 || inst.Bits(15, 1) != 0)
return null;
imm = (inst.Bits(16, 4) << 12) + (inst.Bits(26, 1) << 11) + (inst.Bits(12, 3) << 8) + inst.Bits(0, 8);
reg_d = inst.Bits(8, 4);
return (reg_d, (ushort) imm);
}
// Section 4.6.4 (ADD encoding T2)
private (uint reg_dn, uint reg_m)? getAddReg(uint inst) {
if (inst.Bits(8, 8) != 0b_0100_0100)
return null;
var reg_dn = (inst.Bits(7, 1) << 3) + inst.Bits(0, 3);
var reg_m = inst.Bits(3, 4);
return (reg_dn, reg_m);
}
// Section 4.6.43 (LDR encoding T1)
private (uint reg_n, uint reg_t, ushort imm)? getLdrImm(uint inst) {
if (inst.Bits(11, 5) != 0b_01101)
return null;
var reg_n = inst.Bits(3, 3);
var reg_t = inst.Bits(0, 3);
var imm = inst.Bits(6, 5);
return (reg_n, reg_t, (ushort) imm);
}
// Section 4.6.12 (B.W encoding T4; for encoding T3, flip bit 12)
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) {
// List of registers and addresses loaded into them
var regs = new Dictionary<uint, uint>();
// Program counter is R15 in ARM
// https://www.scss.tcd.ie/~waldroj/3d1/arm_arm.pdf states:
// For a Thumb instruction, the value read is the address of the instruction plus 4 bytes
regs.Add(15, baseAddress + 4);
// Iterate each instruction
foreach (var inst in func) {
var accepted = false;
// Is it a MOVW?
if (getMovImm(inst, Thumb.MovW) is (uint movw_reg_d, ushort movw_imm)) {
if (regs.ContainsKey(movw_reg_d))
regs[movw_reg_d] = movw_imm; // Clears top 16 bits
else
regs.Add(movw_reg_d, movw_imm);
accepted = true;
}
// Is it a MOVT?
if (getMovImm(inst, Thumb.MovT) is (uint movt_reg_d, ushort movt_imm)) {
if (regs.ContainsKey(movt_reg_d))
regs[movt_reg_d] |= (uint) movt_imm << 16;
else
regs.Add(movt_reg_d, (uint) movt_imm << 16);
accepted = true;
}
// Is it a pointer de-reference (LDR Rt, [Rn, #imm])?
if (getLdrImm(inst) is (uint ldr_reg_n, uint ldr_reg_t, ushort ldr_imm)) {
// The code below works in the generic case for all Rt, Rn and #imm,
// but for our scan we want to restrict it such that Rt == Rn and #imm == 0
// otherwise we might pick up functions we don't want
if (ldr_reg_n == ldr_reg_t && ldr_imm == 0)
if (regs.ContainsKey(ldr_reg_n)) {
var offset = (regs[ldr_reg_n] & 0xffff_fffe) + ldr_imm;
var value = image.ReadUInt32(image.MapVATR(offset));
if (regs.ContainsKey(ldr_reg_t))
regs[ldr_reg_t] = value;
else
regs.Add(ldr_reg_t, value);
accepted = true;
}
}
// Is it an ADD Rdn, Rm?
if (getAddReg(inst) is (uint add_reg_dn, uint add_reg_m)) {
if (regs.ContainsKey(add_reg_dn) && regs.ContainsKey(add_reg_m)) {
regs[add_reg_dn] += regs[add_reg_m];
accepted = true;
}
}
// is it the end?
if (isBW(inst))
accepted = true;
// In our scan, we will ONLY accept one of the above instructions
if (!accepted)
return null;
// Advance program counter which we need to calculate ADDs with PC as operand correctly
regs[15] += inst.Bits(29, 3) == 0b111 ? 4u : 2u;
}
return regs;
}
// Get a Thumb function that ends in B.W
private List<uint> getThumbFunctionAtFileOffset(IFileFormatReader 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;
image.Position = loc;
do {
inst = getNextThumbInstruction(image);
func.Add(inst);
} while (!isBW(inst) && func.Count < maxLength);
return func;
}
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
// Assembly bytes to search for at start of each function
ulong metadataRegistration, codeRegistration;
// ARMv7 based on ELF GOT
// Il2CppCodeRegistration.cpp initializer
image.Position = loc;
var buff = image.ReadBytes(0xc);
// LDR R0, [PC, #0x1C]; LDR R1, [PC, #0x1C]; LDR R2, [PC, #0x1C]
if (new byte[] { 0x1c, 0x0, 0x9f, 0xe5, 0x1c, 0x10, 0x9f, 0xe5, 0x1c, 0x20, 0x9f, 0xe5 }.SequenceEqual(buff)) {
// Get offset to all addresses
// The +8 is because in ARM, PC always contains the currently executing instruction + 8
var offset = image.ReadUInt32(loc + 0x24) + loc + 0xc + 8;
// Get pointer to Il2CppCodegenRegistration(void)
var pCgr = image.ReadUInt32(loc + 0x2C) + offset;
// Read pointer table at end of function
codeRegistration = image.ReadUInt32(pCgr + 0x28) + offset;
var pMetadataRegistration = image.ReadUInt32(pCgr + 0x2C) + offset;
metadataRegistration = image.ReadUInt32(pMetadataRegistration);
return (codeRegistration, metadataRegistration);
}
// ARMv7
// Il2CppCodeRegistration.cpp initializer
image.Position = loc;
buff = image.ReadBytes(0x18);
// Check for ADD R0, PC, R0; ADD R1, PC, R1 near the end of the function
if (new byte[] {0x00, 0x00, 0x8F, 0xE0, 0x01, 0x10, 0x8F, 0xE0}.SequenceEqual(buff.Skip(0x10))
// Check for LDR R1, [PC, #x] where x is an offset to *Il2CppCodegenRegistration
&& new byte[] {0x10, 0x9F, 0xE5}.SequenceEqual(buff.Skip(0x9).Take(3))) {
// Read offset in LDR operand plus pointer table at end of function to find pCgr
var pCgr = buff[8] + loc + 0x10;
image.Position = pCgr;
pCgr = image.ReadUInt32() + loc + 0x1c;
// void Il2CppCodegenRegistration()
// Read pointer table at end of function
image.Position = pCgr + 0x1C;
var pMetadata = image.ReadUInt32() + pCgr + 0x14;
codeRegistration = image.ReadUInt32() + pCgr + 0x18;
image.Position = image.MapVATR(pMetadata);
metadataRegistration = image.ReadUInt32();
return (codeRegistration, metadataRegistration);
}
// Thumb-2
// We use a method similar to the linear sweep in Il2CppBinaryARM64; see the comments there for details
loc &= 0xffff_fffe;
image.Position = loc;
// Load function into memory
// In practice, the longest function length we need is not generally longer than 11 instructions
var func = getThumbFunctionAtFileOffset(image, loc, 11);
// Don't accept functions longer than 10 instructions (in this case, the last instruction won't be a B.W)
if (!isBW(func[^1]))
return (0, 0);
// Get a list of registers and values in them at the end of the function
var regs = sweepThumbForAddressLoads(func, (uint) image.GlobalOffset + loc, image);
if (regs == null)
return (0, 0);
uint r0, r1;
// Is it the Il2CppCodeRegistration.cpp initializer?
// R0-R3 + PC will be set and they will be the only registers set
// R2 and R3 must be zero
if (regs.Count() == 5 && regs.TryGetValue(0, out _) && regs.TryGetValue(1, out r1)
&& regs.TryGetValue(2, out uint r2) && regs.TryGetValue(3, out uint r3)) {
if (r2 == 0 && r3 == 0) {
// Load up the function whose address is in R1
func = getThumbFunctionAtFileOffset(image, image.MapVATR(r1 & 0xffff_fffe), 11);
if (!isBW(func[^1]))
return (0, 0);
regs = sweepThumbForAddressLoads(func, r1 & 0xffff_fffe, image);
}
}
// Is it Il2CppCodegenRegistration(void)?
// In v21 and later, R0-R2 + PC will be set and they will be the only registers set
// Pre-v21, R0-R1 + PC will be the only registers set
if (image.Version >= 21 && regs.Count == 4 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1) && regs.TryGetValue(2, out uint _))
return (r0 & 0xffff_fffe, r1 & 0xffff_fffe);
if (image.Version < 21 && regs.Count == 3 && regs.TryGetValue(0, out r0) && regs.TryGetValue(1, out r1))
return (r0 & 0xffff_fffe, r1 & 0xffff_fffe);
return (0, 0);
}
}
}

View File

@@ -0,0 +1,185 @@
/*
Copyright 2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
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) : base(stream) { }
public Il2CppBinaryARM64(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
private (uint reg, ulong page)? getAdrp(uint inst, ulong pc) {
if ((inst.Bits(24, 8) & 0b_1000_1111) != 1 << 7)
return null;
var addendLo = inst.Bits(29, 2);
var addendHi = inst.Bits(5, 19);
var addend = (addendHi << 14) + (addendLo << 12);
var page = pc & ~((1Lu << 12) - 1);
var reg = inst.Bits(0, 5);
return (reg, page + addend);
}
// https://static.docs.arm.com/100878/0100/fundamentals_of_armv8_a_100878_0100_en.pdf states:
// Unlike ARMv7-A, there is no implied offset of 4 or 8 bytes
private (uint reg, ulong addr)? getAdr(uint inst, ulong pc) {
if (inst.Bits(24, 5) != 0b10000 || inst.Bits(31, 1) != 0)
return null;
ulong imm = (inst.Bits(5, 19) << 2) + inst.Bits(29, 2);
// Sign extend the 21-bit number to 64 bits
imm = (imm & (1 << 20)) == 0 ? imm : imm | unchecked((ulong) -(1 << 21));
var reg = inst.Bits(0, 5);
return (reg, pc + imm);
}
private (uint reg_n, uint reg_d, uint imm)? getAdd64(uint inst) {
if (inst.Bits(22, 10) != 0b_1001_0001_00)
return null;
var imm = inst.Bits(10, 12);
var reg_n = inst.Bits(5, 5);
var reg_d = inst.Bits(0, 5);
return (reg_n, reg_d, imm);
}
private (uint reg_t, uint reg_n, uint simm)? getLdr64ImmOffset(uint inst) {
if (inst.Bits(22, 10) != 0b_11_1110_0101)
return null;
var imm = inst.Bits(10, 12);
var reg_t = inst.Bits(0, 5);
var reg_n = inst.Bits(5, 5);
return (reg_t, reg_n, imm);
}
private bool isB(uint inst) => inst.Bits(26, 6) == 0b_000101;
private Dictionary<uint, ulong> sweepForAddressLoads(List<uint> func, ulong baseAddress, IFileFormatReader image) {
// List of registers and addresses loaded into them
var regs = new Dictionary<uint, ulong>();
// Iterate each instruction
var pc = baseAddress;
foreach (var inst in func) {
// Is it an ADRP Xn, #page?
if (getAdrp(inst, pc) is (uint reg, ulong page)) {
// If we've had an earlier ADRP for the same register, we'll discard the previous load
if (regs.ContainsKey(reg))
regs[reg] = page;
else
regs.Add(reg, page);
}
if (getAdr(inst, pc) is (uint reg_adr, ulong addr)) {
if (regs.ContainsKey(reg_adr))
regs[reg_adr] = addr;
else
regs.Add(reg_adr, addr);
}
// Is it an ADD Xm, Xn, #offset?
if (getAdd64(inst) is (uint reg_n, uint reg_d, uint imm)) {
// We are only interested in registers that have already had an ADRP, and the ADD must be to itself
if (reg_n == reg_d && regs.ContainsKey(reg_d))
regs[reg_d] += imm;
}
// Is it an LDR Xm, [Xn, #offset]?
if (getLdr64ImmOffset(inst) is (uint reg_t, uint reg_ldr_n, uint simm)) {
// We are only interested in registers that have already had an ADRP, and the LDR must be to itself
if (reg_t == reg_ldr_n && regs.ContainsKey(reg_ldr_n)) {
regs[reg_ldr_n] += simm * 8; // simm is a byte offset in a multiple of 8
// Now we have a pointer address, dereference it
regs[reg_ldr_n] = image.ReadUInt64(image.MapVATR(regs[reg_ldr_n]));
}
}
// Advance program counter which we need to calculate ADRP pages correctly
pc += 4;
}
return regs;
}
private List<uint> getFunctionAtFileOffset(IFileFormatReader 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;
image.Position = loc;
do {
inst = image.ReadUInt32();
func.Add(inst);
} while (!isB(inst) && func.Count < maxLength);
return func;
}
// The method for ARM64:
// - We want to extract values for CodeRegistration and MetadataRegistration from Il2CppCodegenRegistration(void)
// - One of the functions supplied will be either Il2CppCodeGenRegistration or an initializer for Il2CppCodeGenRegistration.cpp
// - The initializer (if present) loads a pointer to Il2CppCodegenRegistration in X1, if the function isn't in the function table
// - Il2CppCodegenRegistration loads CodeRegistration into X0, MetadataRegistration into X1 and Il2CppCodeGenOptions into X2
// - 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) {
// 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);
// Don't accept functions longer than 7 instructions (in this case, the last instruction won't be a B)
if (!isB(func[^1]))
return (0, 0);
// Get a list of registers and values in them at the end of the function
var regs = sweepForAddressLoads(func, image.GlobalOffset + loc, image);
// Is it the Il2CppCodeRegistration.cpp initializer?
// X0-X1 will be set and they will be the only registers set
if (regs.Count == 2 && regs.TryGetValue(0, out _) && regs.TryGetValue(1, out ulong x1)) {
// Load up the function whose address is in X1
func = getFunctionAtFileOffset(image, (uint) image.MapVATR(x1), 7);
if (!isB(func[^1]))
return (0, 0);
regs = sweepForAddressLoads(func, x1, image);
}
// Is it Il2CppCodegenRegistration(void)?
// In v21 and later, X0-X2 will be set and they will be the only registers set
// Pre-v21, X0-X1 will be the only registers set
if (image.Version >= 21 && regs.Count == 3 && regs.TryGetValue(0, out ulong x0) && regs.TryGetValue(1, out x1) && regs.TryGetValue(2, out ulong _))
return (x0, x1);
if (image.Version < 21 && regs.Count == 2 && regs.TryGetValue(0, out x0) && regs.TryGetValue(1, out x1))
return (x0, x1);
return (0, 0);
}
}
internal static class UIntExtensions
{
// Return count bits starting at bit low of integer x
public static uint Bits(this uint x, int low, int count) => (x >> low) & (uint) ((1 << count) - 1);
}
}

View File

@@ -0,0 +1,163 @@
/*
Copyright 2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Linq;
namespace Il2CppInspector
{
internal class Il2CppBinaryX64 : Il2CppBinary
{
public Il2CppBinaryX64(IFileFormatReader stream) : base(stream) { }
public Il2CppBinaryX64(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
// Format of 64-bit LEA:
// 0x48 - REX prefix signifying 64-bit mode with 64-bit operand size (REX prefix bits: Volume 2A, page 2-9)
// 8x8D - LEA opcode (8D /r, LEA r64, m)
// 0xX5 - bottom 3 bits = 101 to indicate subsequent operand is a 32-bit displacement; middle 3 bits = register number; top 2 bits = 00
// Bytes 03-06 - 32-bit displacement
// Register numbers: 00 = RAX, 01 = RCX, 10 = RDX, 11 = RBX
// See: https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
// Chapter 2.1, 2.1.3, 2.1.5 table 2-2, page 3-537
// NOTE: There is a chance of false positives because of x86's variable instruction length architecture
private (int foundOffset, int reg, uint operand)? findLea(byte[] buff, int offset, int searchDistance) {
// Find first LEA but don't search too far
var opcode = new byte[] { 0x48, 0x8D };
int i, index;
for (i = offset, index = 0; i < offset + searchDistance && i < buff.Length && index < opcode.Length; i++)
if (buff[i] != opcode[index++]) {
index = 0;
// Maybe we're starting a new match
if (buff[i] != opcode[index++])
index = 0;
}
if (index < opcode.Length)
return null;
var lea = getLea(buff, (int) i - 2);
return (i - 2, lea.Value.reg, lea.Value.operand);
}
private (int reg, uint operand)? getLea(byte[] buff, int offset) {
if (buff[offset] != 0x48 || buff[offset + 1] != 0x8D)
return null;
// Found LEA RnX, [RIP + disp32]
var reg = (buff[offset + 2] >> 3) & 7;
var operand = BitConverter.ToUInt32(buff, offset + 3);
return (reg, operand);
}
// 0x40 to set 64-bit mode with 32-bit register size, 0x50+rd to push specified register number
// Volume 2B, page 4-511
private bool isPushR32(byte[] buff, int offset) => buff[offset] == 0x40 && buff[offset + 1] >= 0x50 && buff[offset + 1] < 0x58;
// 0b0100_0X0Y to set 64-bit mode, 0x33 for XOR, 0b11_XXX_YYY for register numbers
// Volume 2C, page 5-278
private (int reg_op1, int reg_op2)? getXorR64R64(byte[] buff, int offset) {
if ((buff[offset] & 0b1111_1010) != 0b_0100_0000 || buff[offset + 1] != 0x33 || (buff[offset + 2] & 0b1100_0000) != 0b1100_0000)
return null;
return (((buff[offset] & 0b0000_0100) << 1) + ((buff[offset + 2] & 0b0011_1000) >> 3),
((buff[offset] & 0b0000_0001) << 3) + (buff[offset + 2] & 0b0000_0111));
}
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
// We have seen two versions of the initializer:
// 1. Regular version
// 2. Inlined version with il2cpp::utils::RegisterRuntimeInitializeAndCleanup(CallbackFunction, CallbackFunction, order)
// Version 1 passes "this" in rcx and the arguments in rdx (our wanted pointer), r8d (always zero) and r9d (always zero)
// Version 2 has a standard prologue and loads the wanted pointer into rax (lea rax)
(int reg, uint operand)? lea;
ulong pCgr = 0;
image.Position = loc;
var buff = image.ReadBytes(0x20); // arbitrary number of bytes, but enough to process the function
// Check for regular version
var xor = getXorR64R64(buff, 0); // 3 bytes
if (xor != null && xor.Value.reg_op1 == xor.Value.reg_op2) {
lea = getLea(buff, 3); // 7 bytes
if (lea != null) {
xor = getXorR64R64(buff, 10);
if (xor != null && xor.Value.reg_op1 == xor.Value.reg_op2) {
// We found Il2CppCodegenRegistration(void)
pCgr = image.GlobalOffset + loc + 10 + lea.Value.operand;
}
}
}
// Check for inlined version
if (pCgr == 0) {
// Check for prologue
if (isPushR32(buff, 0)) {
// Linear sweep for LEA
var leaInlined = findLea(buff, 2, 0x1E); // 0x20 - 2
if (leaInlined == null)
return (0, 0);
// LEA is 7 bytes long
pCgr = image.GlobalOffset + loc + (uint) leaInlined.Value.foundOffset + 7 + leaInlined.Value.operand;
}
}
if (pCgr == 0)
return (0, 0);
// Assume we've found the pointer to Il2CppCodegenRegistration(void) and jump there
try {
Image.Position = Image.MapVATR(pCgr);
}
// Couldn't map virtual address to data in file, so it's not this function
catch (InvalidOperationException) {
return (0, 0);
}
// Find the first 2 LEAs which we'll hope contain pointers to CodeRegistration and MetadataRegistration
// There are two options here:
// 1. il2cpp::vm::MetadataCache::Register is called directly with arguments in rcx, rdx and r8 (lea, lea, lea, jmp)
// 2. The two functions being inlined. The arguments are loaded sequentially into rax after the prologue
// By ignoring the REX R flag (bit 2 of the instruction prefix) which specifies an extension bit to the register operand,
// we skip over "lea r8". This will leave us with two LEAs containing our desired pointers.
buff = image.ReadBytes(0x40);
// LEA is 7 bytes long
var lea1 = findLea(buff, 0, 0x40 - 7);
if (lea1 == null)
return (0, 0);
var lea2 = findLea(buff, lea1.Value.foundOffset + 7, 0x40 - lea1.Value.foundOffset - 7);
if (lea2 == null)
return (0, 0);
// Use the original pointer found, not the file location + GlobalOffset because the data may be in a different section
var ptr1 = pCgr + (uint) lea1.Value.foundOffset + 7 + lea1.Value.operand;
var ptr2 = pCgr + (uint) lea2.Value.foundOffset + 7 + lea2.Value.operand;
// RCX and RDX argument passing?
if (lea1.Value.reg == 2 /* RDX */ && lea2.Value.reg == 1 /* RCX */)
return (ptr2, ptr1);
// RAX sequential loading?
if (lea1.Value.reg == 0 /* RAX */ && lea2.Value.reg == 0 /* RAX */)
return (ptr1, ptr2);
return (0, 0);
}
}
}

View File

@@ -0,0 +1,103 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Linq;
namespace Il2CppInspector
{
internal class Il2CppBinaryX86 : Il2CppBinary
{
public Il2CppBinaryX86(IFileFormatReader stream) : base(stream) { }
public Il2CppBinaryX86(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) : base(stream, codeRegistration, metadataRegistration) { }
protected override (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc) {
ulong metadata, code;
long pCgr;
// x86
// Assembly bytes to search for at start of each function
var bytes = new byte[] {0x6A, 0x00, 0x6A, 0x00, 0x68};
image.Position = loc;
var buff = image.ReadBytes(5);
if (bytes.SequenceEqual(buff)) {
// Next 4 bytes are the function pointer being pushed onto the stack
pCgr = image.ReadUInt32();
// Start of next instruction
if (image.ReadByte() != 0xB9)
return (0, 0);
// Jump to Il2CppCodegenRegistration
image.Position = image.MapVATR((ulong) pCgr + 6);
metadata = image.ReadUInt32();
image.Position = image.MapVATR((ulong) pCgr + 11);
code = image.ReadUInt32();
return (code, metadata);
}
// x86 based on ELF PLT
if (image is IElfReader elf) {
var plt = elf.GetPLTAddress();
// push ebp; mov ebp, esp; push ebx; and esp, 0FFFFFFF0h; sub esp, 20h; call $+5; pop ebx
bytes = new byte[]
{0x55, 0x89, 0xE5, 0x53, 0x83, 0xE4, 0xF0, 0x83, 0xEC, 0x20, 0xE8, 0x00, 0x00, 0x00, 0x00, 0x5B};
image.Position = loc;
buff = image.ReadBytes(16);
if (!bytes.SequenceEqual(buff))
return (0, 0);
// lea eax, (pCgr - offset)[ebx] (Position + 6 is the opcode lea eax; Position + 8 is the operand)
image.Position += 6;
// Ensure it's lea eax, #address
if (image.ReadUInt16() != 0x838D)
return (0, 0);
try {
pCgr = image.MapVATR(image.ReadUInt32() + plt);
}
// Could not find a mapping in the section table
catch (InvalidOperationException) {
return (0, 0);
}
// Extract Metadata pointer
// An 0x838D opcode indicates LEA (no indirection)
image.Position = pCgr + 0x20;
var opcode = image.ReadUInt16();
metadata = image.ReadUInt32() + plt;
// An 8x838B opcode indicates MOV (pointer indirection)
if (opcode == 0x838B) {
image.Position = image.MapVATR(metadata);
metadata = image.ReadUInt32();
}
if (opcode != 0x838B && opcode != 0x838D)
return (0, 0);
// Repeat the same logic for extracting the Code pointer
image.Position = pCgr + 0x2A;
opcode = image.ReadUInt16();
code = image.ReadUInt32() + plt;
if (opcode == 0x838B) {
image.Position = image.MapVATR(code);
code = image.ReadUInt32();
}
if (opcode != 0x838B && opcode != 0x838D)
return (0, 0);
return (code, metadata);
}
return (0, 0);
}
}
}

View File

@@ -0,0 +1,318 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector
{
internal class ElfReader32 : ElfReader<uint, elf_32_phdr, elf_32_sym, ElfReader32, Convert32>
{
public ElfReader32(Stream stream) : base(stream) {
ElfReloc.GetRelocType = info => (Elf) (info & 0xff);
ElfReloc.GetSymbolIndex = info => info >> 8;
}
public override int Bits => 32;
protected override Elf ArchClass => Elf.ELFCLASS32;
protected override void Write(BinaryWriter writer, uint value) => writer.Write(value);
}
internal class ElfReader64 : ElfReader<ulong, elf_64_phdr, elf_64_sym, ElfReader64, Convert64>
{
public ElfReader64(Stream stream) : base(stream) {
ElfReloc.GetRelocType = info => (Elf) (info & 0xffff_ffff);
ElfReloc.GetSymbolIndex = info => info >> 32;
}
public override int Bits => 64;
protected override Elf ArchClass => Elf.ELFCLASS64;
protected override void Write(BinaryWriter writer, ulong value) => writer.Write(value);
}
interface IElfReader
{
uint GetPLTAddress();
}
internal abstract class ElfReader<TWord, TPHdr, TSym, TReader, TConvert> : FileFormatReader<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>
{
private readonly TConvert conv = new TConvert();
// Internal relocation entry helper
protected class ElfReloc
{
public Elf Type;
public TWord Offset;
public TWord? Addend;
public TWord SymbolTable;
public TWord SymbolIndex;
// Equality based on target address
public override bool Equals(object obj) => obj is ElfReloc reloc && Equals(reloc);
public bool Equals(ElfReloc other) {
return Offset.Equals(other.Offset);
}
public override int GetHashCode() => Offset.GetHashCode();
// Cast operators (makes the below code MUCH easier to read)
public ElfReloc(elf_rel<TWord> rel, TWord symbolTable) {
Offset = rel.r_offset;
Addend = null;
Type = GetRelocType(rel.r_info);
SymbolIndex = GetSymbolIndex(rel.r_info);
SymbolTable = symbolTable;
}
public ElfReloc(elf_rela<TWord> rela, TWord symbolTable)
: this(new elf_rel<TWord> { r_info = rela.r_info, r_offset = rela.r_offset }, symbolTable) =>
Addend = rela.r_addend;
public static Func<TWord, Elf> GetRelocType;
public static Func<TWord, TWord> GetSymbolIndex;
}
// See also: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/sizeof
private int Sizeof(Type type) {
int size = 0;
foreach (var i in type.GetTypeInfo().GetFields()) {
if (i.FieldType == typeof(byte) || i.FieldType == typeof(sbyte))
size += sizeof(byte);
if (i.FieldType == typeof(long) || i.FieldType == typeof(ulong))
size += sizeof(ulong);
if (i.FieldType == typeof(int) || i.FieldType == typeof(uint))
size += sizeof(uint);
if (i.FieldType == typeof(short) || i.FieldType == typeof(ushort))
size += sizeof(ushort);
}
return size;
}
private TPHdr[] program_header_table;
private elf_shdr<TWord>[] section_header_table;
private elf_dynamic<TWord>[] dynamic_table;
private elf_header<TWord> elf_header;
public ElfReader(Stream stream) : base(stream) { }
public override string Format => Bits == 32 ? "ELF" : "ELF64";
public override string Arch => (Elf) elf_header.e_machine switch {
Elf.EM_386 => "x86",
Elf.EM_ARM => "ARM",
Elf.EM_X86_64 => "x64",
Elf.EM_AARCH64 => "ARM64",
_ => "Unsupported"
};
public override int Bits => (elf_header.m_arch == (uint) Elf.ELFCLASS64) ? 64 : 32;
private elf_shdr<TWord> getSection(Elf sectionIndex) => section_header_table.FirstOrDefault(x => x.sh_type == (uint) sectionIndex);
private IEnumerable<elf_shdr<TWord>> getSections(Elf sectionIndex) => section_header_table.Where(x => x.sh_type == (uint) sectionIndex);
private TPHdr getProgramHeader(Elf programIndex) => program_header_table.FirstOrDefault(x => x.p_type == (uint) programIndex);
private elf_dynamic<TWord> getDynamic(Elf dynamicIndex) => dynamic_table?.FirstOrDefault(x => (Elf) conv.ULong(x.d_tag) == dynamicIndex);
protected abstract Elf ArchClass { get; }
protected abstract void Write(BinaryWriter writer, TWord value);
protected override bool Init() {
elf_header = ReadObject<elf_header<TWord>>();
// Check for magic bytes
if ((Elf) elf_header.m_dwFormat != Elf.ELFMAG)
return false;
// 64-bit not supported
if ((Elf) elf_header.m_arch != ArchClass)
return false;
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);
if (getProgramHeader(Elf.PT_DYNAMIC) is TPHdr PT_DYNAMIC)
dynamic_table = ReadArray<elf_dynamic<TWord>>(conv.Long(PT_DYNAMIC.p_offset), (int) (conv.Long(PT_DYNAMIC.p_filesz) / Sizeof(typeof(elf_dynamic<TWord>))));
// Get offset of code section
var codeSegment = program_header_table.First(x => ((Elf) x.p_flags & Elf.PF_X) == Elf.PF_X);
GlobalOffset = conv.ULong(conv.Sub(codeSegment.p_vaddr, codeSegment.p_offset));
// Find all relocations; target address => (rela header (rels are converted to rela), symbol table base address, is rela?)
var rels = new HashSet<ElfReloc>();
// Two types: add value from offset in image, and add value from specified addend
foreach (var relSection in getSections(Elf.SHT_REL))
rels.UnionWith(
from rel in ReadArray<elf_rel<TWord>>(conv.Long(relSection.sh_offset), conv.Int(conv.Div(relSection.sh_size, relSection.sh_entsize)))
select new ElfReloc(rel, section_header_table[relSection.sh_link].sh_offset));
foreach (var relaSection in getSections(Elf.SHT_RELA))
rels.UnionWith(
from rela in ReadArray<elf_rela<TWord>>(conv.Long(relaSection.sh_offset), conv.Int(conv.Div(relaSection.sh_size, relaSection.sh_entsize)))
select new ElfReloc(rela, section_header_table[relaSection.sh_link].sh_offset));
// Relocations in dynamic section
if (getDynamic(Elf.DT_REL) is elf_dynamic<TWord> dt_rel) {
var dt_rel_count = conv.Div(getDynamic(Elf.DT_RELSZ).d_un, getDynamic(Elf.DT_RELENT).d_un);
var dt_rel_list = ReadArray<elf_rel<TWord>>(MapVATR(conv.ULong(dt_rel.d_un)), conv.Int(dt_rel_count));
var dt_symtab = getDynamic(Elf.DT_SYMTAB).d_un;
rels.UnionWith(from rel in dt_rel_list select new ElfReloc(rel, dt_symtab));
}
if (getDynamic(Elf.DT_RELA) is elf_dynamic<TWord> dt_rela) {
var dt_rela_count = conv.Div(getDynamic(Elf.DT_RELASZ).d_un, getDynamic(Elf.DT_RELAENT).d_un);
var dt_rela_list = ReadArray<elf_rela<TWord>>(MapVATR(conv.ULong(dt_rela.d_un)), conv.Int(dt_rela_count));
var dt_symtab = getDynamic(Elf.DT_SYMTAB).d_un;
rels.UnionWith(from rela in dt_rela_list select new ElfReloc(rela, dt_symtab));
}
// Process relocations
// WARNING: This modifies the stream passed in the constructor
if (BaseStream is FileStream)
throw new InvalidOperationException("Input stream to ElfReader is a file. Please supply a mutable stream source.");
var writer = new BinaryWriter(BaseStream);
var relsz = Sizeof(typeof(TSym));
foreach (var rel in rels) {
var symValue = ReadObject<TSym>(conv.Long(rel.SymbolTable) + conv.Long(rel.SymbolIndex) * relsz).st_value; // S
// Ignore relocations into memory addresses not mapped from the image
try {
Position = MapVATR(conv.ULong(rel.Offset));
}
catch (InvalidOperationException) {
continue;
}
// The addend is specified in the struct for rela, and comes from the target location for rel
var addend = rel.Addend ?? ReadObject<TWord>(); // A
// Only handle relocation types we understand, skip the rest
// Relocation types from https://docs.oracle.com/cd/E23824_01/html/819-0690/chapter6-54839.html#scrolltoc
// and https://studfiles.net/preview/429210/page:18/
// and http://infocenter.arm.com/help/topic/com.arm.doc.ihi0056b/IHI0056B_aaelf64.pdf (AArch64)
(TWord newValue, bool recognized) result = (rel.Type, (Elf) elf_header.e_machine) switch {
(Elf.R_ARM_ABS32, Elf.EM_ARM) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_ARM_REL32, Elf.EM_ARM) => (conv.Add(conv.Sub(symValue, rel.Offset), addend), true), // S - P + A
(Elf.R_ARM_COPY, Elf.EM_ARM) => (symValue, true), // S
(Elf.R_AARCH64_ABS64, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_AARCH64_PREL64, Elf.EM_AARCH64) => (conv.Sub(conv.Add(symValue, addend), rel.Offset), true), // S + A - P
(Elf.R_AARCH64_GLOB_DAT, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_AARCH64_JUMP_SLOT, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_AARCH64_RELATIVE, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // Delta(S) + A
(Elf.R_386_32, Elf.EM_386) => (conv.Add(symValue, addend), true), // S + A
(Elf.R_386_PC32, Elf.EM_386) => (conv.Sub(conv.Add(symValue, addend), rel.Offset), true), // S + A - P
(Elf.R_386_GLOB_DAT, Elf.EM_386) => (symValue, true), // S
(Elf.R_386_JMP_SLOT, Elf.EM_386) => (symValue, true), // S
(Elf.R_AMD64_64, Elf.EM_AARCH64) => (conv.Add(symValue, addend), true), // S + A
_ => (default(TWord), false)
};
if (result.recognized) {
Position = MapVATR(conv.ULong(rel.Offset));
Write(writer, result.newValue);
}
}
Console.WriteLine($"Processed {rels.Count} relocations");
return true;
}
public override Dictionary<string, ulong> GetSymbolTable() {
// Three possible symbol tables in ELF files
var pTables = new List<(TWord offset, TWord count, TWord strings)>();
// String table (a sequence of null-terminated strings, total length in sh_size
var SHT_STRTAB = getSection(Elf.SHT_STRTAB);
if (SHT_STRTAB != null) {
// Section header shared object symbol table (.symtab)
if (getSection(Elf.SHT_SYMTAB) is elf_shdr<TWord> SHT_SYMTAB)
pTables.Add((SHT_SYMTAB.sh_offset, conv.Div(SHT_SYMTAB.sh_size, SHT_SYMTAB.sh_entsize), SHT_STRTAB.sh_offset));
// Section header executable symbol table (.dynsym)
if (getSection(Elf.SHT_DYNSYM) is elf_shdr<TWord> SHT_DYNSYM)
pTables.Add((SHT_DYNSYM.sh_offset, conv.Div(SHT_DYNSYM.sh_size, SHT_DYNSYM.sh_entsize), SHT_STRTAB.sh_offset));
}
// Symbol table in dynamic section (DT_SYMTAB)
// Normally the same as .dynsym except that .dynsym may be removed in stripped binaries
// Dynamic string table
if (getDynamic(Elf.DT_STRTAB) is elf_dynamic<TWord> DT_STRTAB) {
if (getDynamic(Elf.DT_SYMTAB) is elf_dynamic<TWord> DT_SYMTAB) {
// Find the next pointer in the dynamic table to calculate the length of the symbol table
var end = (from x in dynamic_table where conv.Gt(x.d_un, DT_SYMTAB.d_un) orderby x.d_un select x).First().d_un;
// Dynamic symbol table
pTables.Add((
conv.FromUInt(MapVATR(conv.ULong(DT_SYMTAB.d_un))),
conv.Div(conv.Sub(end, DT_SYMTAB.d_un), Sizeof(typeof(TSym))),
DT_STRTAB.d_un
));
}
}
// Now iterate through all of the symbol and string tables we found to build a full list
var symbolTable = new Dictionary<string, ulong>();
foreach (var pTab in pTables) {
var symbol_table = ReadArray<TSym>(conv.Long(pTab.offset), conv.Int(pTab.count));
foreach (var symbol in symbol_table) {
var name = ReadNullTerminatedString(conv.Long(pTab.strings) + symbol.st_name);
// Avoid duplicates
symbolTable.TryAdd(name, conv.ULong(symbol.st_value));
}
}
return symbolTable;
}
public override uint[] GetFunctionTable() {
// INIT_ARRAY contains a list of pointers to initialization functions (not all functions in the binary)
// INIT_ARRAYSZ contains the size of INIT_ARRAY
var init = MapVATR(conv.ULong(getDynamic(Elf.DT_INIT_ARRAY).d_un));
var size = getDynamic(Elf.DT_INIT_ARRAYSZ).d_un;
return conv.UIntArray(ReadArray<TWord>(init, conv.Int(size) / (Bits / 8))).Select(x => MapVATR(x)).ToArray();
}
// Map a virtual address to an offset into the image file. Throws an exception if the virtual address is not mapped into the file.
// Note if uiAddr is a valid segment but filesz < memsz and the adjusted uiAddr falls between the range of filesz and memsz,
// an exception will be thrown. This area of memory is assumed to contain all zeroes.
public override uint MapVATR(ulong uiAddr) {
// Additions in the argument to MapVATR may cause an overflow which should be discarded for 32-bit files
if (Bits == 32)
uiAddr &= 0xffff_ffff;
var program_header_table = this.program_header_table.First(x => uiAddr >= conv.ULong(x.p_vaddr) && uiAddr <= conv.ULong(conv.Add(x.p_vaddr, x.p_filesz)));
return (uint) (uiAddr - conv.ULong(conv.Sub(program_header_table.p_vaddr, program_header_table.p_offset)));
}
// Get the address of the procedure linkage table (.got.plt) which is needed for some disassemblies
public uint GetPLTAddress() => (uint) conv.ULong(getDynamic(Elf.DT_PLTGOT).d_un);
}
}

View File

@@ -0,0 +1,153 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
public interface IFileFormatReader
{
BinaryObjectReader Stream { get; }
double Version { get; set; }
long Length { get; }
uint NumImages { get; }
IEnumerable<IFileFormatReader> Images { get; }
IFileFormatReader this[uint index] { get; }
long Position { get; set; }
string Format { get; }
string Arch { get; }
int Bits { get; }
ulong GlobalOffset { get; }
Dictionary<string, ulong> GetSymbolTable();
uint[] GetFunctionTable();
U ReadMappedObject<U>(ulong uiAddr) where U : new();
U[] ReadMappedArray<U>(ulong uiAddr, int count) where U : new();
long[] ReadMappedWordArray(ulong uiAddr, int count);
uint MapVATR(ulong uiAddr);
byte[] ReadBytes(int count);
ulong ReadUInt64();
ulong ReadUInt64(long uiAddr);
uint ReadUInt32();
uint ReadUInt32(long uiAddr);
ushort ReadUInt16();
ushort ReadUInt16(long uiAddr);
byte ReadByte();
byte ReadByte(long uiAddr);
long ReadWord();
long ReadWord(long uiAddr);
U ReadObject<U>() where U : new();
string ReadMappedNullTerminatedString(ulong uiAddr);
List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new();
}
internal class FileFormatReader
{
// Helper method to try all defined file formats when the contents of the binary is unknown
public static IFileFormatReader Load(string filename) => Load(new FileStream(filename, FileMode.Open, FileAccess.Read));
public static IFileFormatReader Load(Stream stream) {
var types = Assembly.GetExecutingAssembly().DefinedTypes
.Where(x => x.ImplementedInterfaces.Contains(typeof(IFileFormatReader)) && !x.IsGenericTypeDefinition);
foreach (var type in types) {
if (type.GetMethod("Load", BindingFlags.FlattenHierarchy | BindingFlags.Static | BindingFlags.Public,
null, new [] {typeof(Stream)}, null)
.Invoke(null, new object[] { stream }) is IFileFormatReader loaded)
return loaded;
}
return null;
}
}
internal class FileFormatReader<T> : BinaryObjectReader, IFileFormatReader where T : FileFormatReader<T>
{
public FileFormatReader(Stream stream) : base(stream) { }
public BinaryObjectReader Stream => this;
public long Length => BaseStream.Length;
public uint NumImages { get; protected set; } = 1;
public ulong GlobalOffset { get; protected set; }
public virtual string Format => throw new NotImplementedException();
public virtual string Arch => throw new NotImplementedException();
public virtual int Bits => throw new NotImplementedException();
public IEnumerable<IFileFormatReader> Images {
get {
for (uint i = 0; i < NumImages; i++)
yield return this[i];
}
}
public static T Load(string filename) {
using var stream = new FileStream(filename, FileMode.Open, FileAccess.Read);
return Load(stream);
}
public static T Load(Stream stream) {
// Copy the original stream in case we modify it
var ms = new MemoryStream();
stream.Position = 0;
stream.CopyTo(ms);
ms.Position = 0;
var pe = (T) Activator.CreateInstance(typeof(T), ms);
return pe.Init() ? pe : null;
}
// Confirm file is valid and set up RVA mappings
protected virtual bool Init() => throw new NotImplementedException();
// Choose a sub-binary within the image for multi-architecture binaries
public virtual IFileFormatReader this[uint index] => (index == 0)? this : throw new IndexOutOfRangeException("Binary image index out of bounds");
// Find search locations in the symbol table for Il2Cpp data
public virtual Dictionary<string, ulong> GetSymbolTable() => null;
// Find search locations in the machine code for Il2Cpp data
public virtual uint[] GetFunctionTable() => throw new NotImplementedException();
// Map an RVA to an offset into the file image
// No mapping by default
public virtual uint MapVATR(ulong uiAddr) => (uint) uiAddr;
// Read a file format dependent word (32 or 64 bits)
// The primitive mappings in Bin2Object will automatically read a uint if the file is 32-bit
public long ReadWord() => ReadObject<long>();
public long ReadWord(long uiAddr) => ReadObject<long>(uiAddr);
// Retrieve object(s) from specified RVA(s)
public U ReadMappedObject<U>(ulong uiAddr) where U : new() => ReadObject<U>(MapVATR(uiAddr));
public U[] ReadMappedArray<U>(ulong uiAddr, int count) where U : new() => ReadArray<U>(MapVATR(uiAddr), count);
// Read a file format dependent array of words (32 or 64 bits)
// The primitive mappings in Bin2Object will automatically read a uint if the file is 32-bit
public long[] ReadMappedWordArray(ulong uiAddr, int count) => ReadArray<long>(MapVATR(uiAddr), count);
public string ReadMappedNullTerminatedString(ulong uiAddr) => ReadNullTerminatedString(MapVATR(uiAddr));
// Reads a list of pointers, then reads each object pointed to
public List<U> ReadMappedObjectPointerArray<U>(ulong uiAddr, int count) where U : new() {
var pointers = ReadMappedArray<ulong>(uiAddr, count);
var array = new List<U>();
for (int i = 0; i < count; i++)
array.Add(ReadMappedObject<U>(pointers[i]));
return array;
}
}
}

View File

@@ -0,0 +1,233 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
[Flags]
public enum Elf : uint
{
// elf_header.m_dwFormat
ELFMAG = 0x464c457f, // "\177ELF"
// elf_header.e_machine
EM_386 = 0x03,
EM_ARM = 0x28,
EM_X86_64 = 0x3E,
EM_AARCH64 = 0xB7,
// elf_header.m_arch
ELFCLASS32 = 1,
ELFCLASS64 = 2,
// PHTs
PT_DYNAMIC = 2,
DT_PLTGOT = 3,
PF_X = 1,
// SHTs
SHT_SYMTAB = 2,
SHT_STRTAB = 3,
SHT_RELA = 4,
SHT_REL = 9,
SHT_DYNSYM = 11,
// dynamic sections
DT_STRTAB = 5,
DT_SYMTAB = 6,
DT_RELA = 7,
DT_RELASZ = 8,
DT_RELAENT = 9,
DT_REL = 17,
DT_RELSZ = 18,
DT_RELENT = 19,
DT_INIT_ARRAY = 25,
DT_INIT_ARRAYSZ = 27,
// relocation types
R_ARM_ABS32 = 2,
R_ARM_REL32 = 3,
R_ARM_COPY = 20,
R_AARCH64_ABS64 = 0x101,
R_AARCH64_PREL64 = 0x104,
R_AARCH64_GLOB_DAT = 0x401,
R_AARCH64_JUMP_SLOT = 0x402,
R_AARCH64_RELATIVE = 0x403,
R_386_32 = 1,
R_386_PC32 = 2,
R_386_GLOB_DAT = 6,
R_386_JMP_SLOT = 7,
R_AMD64_64 = 1
}
#pragma warning disable CS0649
internal class elf_header<TWord> where TWord : struct
{
// 0x7f followed by ELF in ascii
public uint m_dwFormat;
// 1 - 32 bit
// 2 - 64 bit
public byte m_arch;
// 1 - little endian
// 2 - big endian
public byte m_endian;
// 1 is original elf format
public byte m_version;
// set based on OS, refer to OSABI enum
public byte m_osabi;
// refer to elf documentation
public byte m_osabi_ver;
// unused
[ArrayLength(FixedSize=7)]
public byte[] e_pad;//byte[7]
// 1 - relocatable
// 2 - executable
// 3 - shared
// 4 - core
public ushort e_type;
// refer to isa enum
public ushort e_machine;
public uint e_version;
public TWord e_entry;
public TWord e_phoff;
public TWord e_shoff;
public uint e_flags;
public ushort e_ehsize;
public ushort e_phentsize;
public ushort e_phnum;
public ushort e_shentsize;
public ushort e_shnum;
public ushort e_shtrndx;
}
internal interface Ielf_phdr<TWord> where TWord : struct
{
uint p_type { get; }
TWord p_offset { get; }
TWord p_filesz { get; }
TWord p_vaddr { get; }
uint p_flags { get; }
}
internal class elf_32_phdr : Ielf_phdr<uint>
{
public uint p_type => f_p_type;
public uint p_offset => f_p_offset;
public uint p_filesz => f_p_filesz;
public uint p_vaddr => f_p_vaddr;
public uint p_flags => f_p_flags;
public uint f_p_type;
public uint f_p_offset;
public uint f_p_vaddr;
public uint p_paddr;
public uint f_p_filesz;
public uint p_memsz;
public uint f_p_flags;
public uint p_align;
}
internal class elf_64_phdr : Ielf_phdr<ulong>
{
public uint p_type => f_p_type;
public ulong p_offset => f_p_offset;
public ulong p_filesz => f_p_filesz;
public ulong p_vaddr => f_p_vaddr;
public uint p_flags => f_p_flags;
public uint f_p_type;
public uint f_p_flags;
public ulong f_p_offset;
public ulong f_p_vaddr;
public ulong p_paddr;
public ulong f_p_filesz;
public ulong p_memsz;
public ulong p_align;
}
internal class elf_shdr<TWord> where TWord : struct
{
public uint sh_name;
public uint sh_type;
public TWord sh_flags;
public TWord sh_addr;
public TWord sh_offset;
public TWord sh_size;
public uint sh_link;
public uint sh_info;
public TWord sh_addralign;
public TWord sh_entsize;
}
internal interface Ielf_sym<TWord> where TWord : struct
{
uint st_name { get; }
TWord st_value { get; }
}
internal class elf_32_sym : Ielf_sym<uint>
{
public uint st_name => f_st_name;
public uint st_value => f_st_value;
public uint f_st_name;
public uint f_st_value;
public uint st_size;
public byte st_info;
public byte st_other;
public ushort st_shndx;
}
internal class elf_64_sym : Ielf_sym<ulong>
{
public uint st_name => f_st_name;
public ulong st_value => f_st_value;
public uint f_st_name;
public byte st_info;
public byte st_other;
public ushort st_shndx;
public ulong f_st_value;
public ulong st_size;
}
internal class elf_dynamic<TWord> where TWord : struct
{
public TWord d_tag;
public TWord d_un;
}
internal class elf_rel<TWord> where TWord : struct
{
public TWord r_offset;
public TWord r_info;
}
internal class elf_rela<TWord> where TWord : struct
{
public TWord r_offset;
public TWord r_info;
public TWord r_addend;
}
#pragma warning restore CS0649
}

View File

@@ -0,0 +1,119 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
#pragma warning disable CS0649
// Structures and enums: https://opensource.apple.com/source/cctools/cctools-870/include/mach-o/loader.h
public enum MachO : uint
{
MH_MAGIC = 0xfeedface,
MH_CIGAM = 0xcefaedfe,
MH_MAGIC_64 = 0xfeedfacf,
MH_CIGAM_64 = 0xcffaedfe,
MH_EXECUTE = 0x2,
LC_SEGMENT = 0x1,
LC_SYMTAB = 0x2,
LC_DYSYMTAB = 0xb,
LC_SEGMENT_64 = 0x19,
LC_FUNCTION_STARTS = 0x26,
CPU_TYPE_X86 = 7,
CPU_TYPE_X86_64 = 0x01000000 + CPU_TYPE_X86,
CPU_TYPE_ARM = 12,
CPU_TYPE_ARM64 = 0x01000000 + CPU_TYPE_ARM
}
internal class MachOHeader<TWord> where TWord : struct
{
public uint Magic;
public uint CPUType;
public uint CPUSubType;
public uint FileType;
public uint NumCommands;
public uint SizeOfCommands;
public TWord Flags;
}
internal class MachOLoadCommand
{
public uint Command;
public uint Size;
}
internal class MachOSegmentCommand<TWord> where TWord : struct
{
// MachOLoadCommand
[String(FixedSize = 16)]
public string Name;
public TWord VirtualAddress;
public TWord VirtualSize;
public TWord ImageOffset;
public TWord ImageSize;
public uint VMMaxProt;
public uint VMInitProt;
public uint NumSections;
public uint Flags;
}
internal class MachOSection<TWord> where TWord : struct
{
[String(FixedSize = 16)]
public string Name;
[String(FixedSize = 16)]
public string SegmentName;
public TWord Address;
public TWord Size;
public uint ImageOffset;
public uint Align;
public uint ImageRelocOffset;
public int NumRelocEntries;
public uint Flags;
public uint Reserved1;
public TWord Reserved2;
}
internal class MachOLinkEditDataCommand
{
// MachOLoadCommand
public uint Offset;
public uint Size;
}
internal class MachOSymtabCommand
{
public uint SymOffset;
public uint NumSyms;
public uint StrOffset;
public uint StrSize;
}
internal class MachO_nlist<TWord> where TWord : struct
{
public uint n_strx;
public byte n_type;
public byte n_sect;
public ushort n_desc;
public TWord n_value;
}
internal class MachO_relocation_info
{
public int r_address;
public uint r_data;
public uint r_symbolnum => r_data & 0x00ffffff;
public bool r_pcrel => ((r_data >> 24) & 1) == 1;
public uint r_length => (r_data >> 25) & 3;
public bool r_extern => ((r_data >> 27) & 1) == 1;
public uint r_type => r_data >> 28;
}
}

View File

@@ -0,0 +1,148 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
// Source: https://github.com/dotnet/llilc/blob/master/include/clr/ntimage.h
public enum PE : uint
{
IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b,
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
}
#pragma warning disable CS0649
// _IMAGE_FILE_HEADER
internal class COFFHeader
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
}
// _IMAGE_OPTIONAL_HEADER
internal interface IPEOptHeader
{
PE ExpectedMagic { get; }
ushort Magic { get; }
ulong ImageBase { get; }
uint BaseOfCode { get; }
RvaEntry[] DataDirectory { get; }
}
internal class PEOptHeader32 : IPEOptHeader
{
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR32_MAGIC;
public ushort Magic => f_Magic;
public ulong ImageBase => f_ImageBase;
public uint BaseOfCode => f_BaseOfCode;
public RvaEntry[] DataDirectory => f_DataDirectory;
public ushort f_Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint f_BaseOfCode;
public uint BaseOfData;
public uint f_ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOSVersion;
public ushort MinorOSVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint Checksum;
public ushort Subsystem;
public ushort DLLCharacteristics;
public uint SizeOfStackReserve;
public uint SizeOfStackCommit;
public uint SizeOfHeapReserve;
public uint SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[ArrayLength(FieldName = "NumberOfRvaAndSizes")]
public RvaEntry[] f_DataDirectory;
}
// _IMAGE_OPTIONAL_HEADER64
internal class PEOptHeader64 : IPEOptHeader
{
public PE ExpectedMagic => PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC;
public ushort Magic => f_Magic;
public ulong ImageBase => f_ImageBase;
public uint BaseOfCode => f_BaseOfCode;
public RvaEntry[] DataDirectory => f_DataDirectory;
public ushort f_Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint f_BaseOfCode;
public ulong f_ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOSVersion;
public ushort MinorOSVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint Checksum;
public ushort Subsystem;
public ushort DLLCharacteristics;
public ulong SizeOfStackReserve;
public ulong SizeOfStackCommit;
public ulong SizeOfHeapReserve;
public ulong SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[ArrayLength(FieldName = "NumberOfRvaAndSizes")]
public RvaEntry[] f_DataDirectory;
}
internal class RvaEntry
{
public uint VirtualAddress;
public uint Size;
}
// _IMAGE_SECTION_HEADER
internal class PESection
{
[String(FixedSize=8)]
public string Name;
public uint VirtualSize; // Size in memory
public uint VirtualAddress; // Base address in memory (RVA)
public uint SizeOfRawData; // Size in file
public uint PointerToRawData; // Base address in file
public uint PointerToRelocations;
public uint PointerToLinenumbers;
public ushort NumberOfRelocations;
public ushort NumberOfLinenumbers;
public uint Characteristics;
}
#pragma warning restore CS0649
}

View File

@@ -0,0 +1,33 @@
/*
Copyright 2017 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
namespace Il2CppInspector
{
#pragma warning disable CS0649
// Structures and enums: https://cocoaintheshell.whine.fr/2009/07/universal-binary-mach-o-format/
public enum UB : uint
{
FAT_MAGIC = 0xcafebabe
}
// Big-endian
internal class FatHeader
{
public uint Magic;
public uint NumArch;
}
// Big-endian
internal class FatArch
{
public uint CPUType;
public uint CPUSubType;
public uint Offset;
public uint Size;
public uint Align;
}
}

View File

@@ -0,0 +1,176 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NoisyCowStudios.Bin2Object;
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;
protected override bool checkMagicBE(MachO magic) => magic == MachO.MH_CIGAM;
protected override MachO lc_Segment => MachO.LC_SEGMENT;
public override uint MapVATR(ulong uiAddr) {
var section = sections.First(x => uiAddr >= x.Address && uiAddr <= x.Address + x.Size);
return (uint) uiAddr - (section.Address - section.ImageOffset);
}
}
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;
protected override bool checkMagicBE(MachO magic) => magic == MachO.MH_CIGAM_64;
protected override MachO lc_Segment => MachO.LC_SEGMENT_64;
public override uint MapVATR(ulong uiAddr) {
var section = sections.First(x => uiAddr >= x.Address && uiAddr <= x.Address + x.Size);
return (uint) (uiAddr - (section.Address - section.ImageOffset));
}
}
// 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>
where TWord : struct
where TReader : FileFormatReader<TReader>
where TConvert : IWordConverter<TWord>, new()
{
private readonly TConvert conv = new TConvert();
private MachOHeader<TWord> header;
protected readonly List<MachOSection<TWord>> sections = new List<MachOSection<TWord>>();
private MachOSection<TWord> funcTab;
private MachOSymtabCommand symTab;
protected MachOReader(Stream stream) : base(stream) { }
public override string Format => "Mach-O " + (Bits == 32 ? "32-bit" : "64-bit");
public override string Arch => (MachO)header.CPUType switch
{
MachO.CPU_TYPE_ARM => "ARM",
MachO.CPU_TYPE_ARM64 => "ARM64",
MachO.CPU_TYPE_X86 => "x86",
MachO.CPU_TYPE_X86_64 => "x64",
_ => "Unsupported"
};
protected abstract bool checkMagicLE(MachO magic);
protected abstract bool checkMagicBE(MachO magic);
protected abstract MachO lc_Segment { get; }
protected override bool Init() {
// Detect endianness - default is little-endianness
MachO magic = (MachO)ReadUInt32();
if (checkMagicBE(magic))
Endianness = Endianness.Big;
if (!checkMagicBE(magic) && !checkMagicLE(magic))
return false;
header = ReadObject<MachOHeader<TWord>>(0);
// Must be executable file
if ((MachO)header.FileType != MachO.MH_EXECUTE)
return false;
// Process load commands
for (var c = 0; c < header.NumCommands; c++) {
var startPos = Position;
var loadCommand = ReadObject<MachOLoadCommand>();
switch ((MachO) loadCommand.Command) {
// Segments
case MachO cmd when cmd == lc_Segment:
var segment = ReadObject<MachOSegmentCommand<TWord>>();
if (segment.Name == "__TEXT" || segment.Name == "__DATA") {
for (int s = 0; s < segment.NumSections; s++) {
var section = ReadObject<MachOSection<TWord>>();
sections.Add(section);
if (section.Name == "__text") {
GlobalOffset = (ulong) Convert.ChangeType(section.Address, typeof(ulong)) - section.ImageOffset;
}
// Initialization (pre-main) functions
if (section.Name == "__mod_init_func") {
funcTab = section;
}
}
}
break;
// Location of static symbol table
case MachO.LC_SYMTAB:
symTab = ReadObject<MachOSymtabCommand>();
break;
case MachO.LC_DYSYMTAB:
// TODO: Implement Mach-O dynamic symbol table
break;
}
// There might be other data after the load command so always use the specified total size to step forwards
Position = startPos + loadCommand.Size;
}
// Must find __mod_init_func
if (funcTab == null)
return false;
// Process relocations
foreach (var section in sections) {
var rels = ReadArray<MachO_relocation_info>(section.ImageRelocOffset, section.NumRelocEntries);
// TODO: Implement Mach-O relocations
if (rels.Any()) {
Console.WriteLine("Mach-O file contains relocations (feature not yet implemented)");
break;
}
}
return true;
}
public override uint[] GetFunctionTable() => ReadArray<TWord>(funcTab.ImageOffset, conv.Int(funcTab.Size) / (Bits / 8)).Select(x => MapVATR(conv.ULong(x)) & 0xffff_fffe).ToArray();
public override Dictionary<string, ulong> GetSymbolTable() {
var symbols = new Dictionary<string, ulong>();
// https://opensource.apple.com/source/cctools/cctools-795/include/mach-o/nlist.h
// n_sect: https://opensource.apple.com/source/cctools/cctools-795/include/mach-o/stab.h
var symbolList = ReadArray<MachO_nlist<TWord>>(symTab.SymOffset, (int) symTab.NumSyms);
// This is a really naive implementation that ignores the values of n_type and n_sect
// which may affect the interpretation of n_value
foreach (var symbol in symbolList) {
Position = symTab.StrOffset + symbol.n_strx;
var name = (symbol.n_strx != 0) ? ReadNullTerminatedString() : "";
var value = (ulong) Convert.ChangeType(symbol.n_value, typeof(ulong));
// Ignore duplicates for now, also ignore symbols with no address
if (value != 0)
symbols.TryAdd(name, value);
}
return symbols;
}
}
}

View File

@@ -0,0 +1,125 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.IO;
using System.Linq;
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>
{
private COFFHeader coff;
private IPEOptHeader pe;
private PESection[] sections;
private uint pFuncTable;
public PEReader(Stream stream) : base(stream) {}
public override string Format => pe is PEOptHeader32 ? "PE32" : "PE32+";
public override string Arch => coff.Machine switch {
0x8664 => "x64", // IMAGE_FILE_MACHINE_AMD64
0x1C0 => "ARM", // IMAGE_FILE_MACHINE_ARM
0xAA64 => "ARM64", // IMAGE_FILE_MACHINE_ARM64
0x1C4 => "ARM", // IMAGE_FILE_MACHINE_ARMINT (Thumb-2)
0x14C => "x86", // IMAGE_FILE_MACHINE_I386
0x1C2 => "ARM", // IMAGE_FILE_MACHINE_THUMB (Thumb)
_ => "Unsupported"
};
// IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20B
// IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10B
// Could also use coff.Characteristics (IMAGE_FILE_32BIT_MACHINE) or coff.Machine
public override int Bits => (PE) pe.Magic == PE.IMAGE_NT_OPTIONAL_HDR64_MAGIC ? 64 : 32;
protected override bool Init() {
// Check for MZ signature "MZ"
if (ReadUInt16() != 0x5A4D)
return false;
// Get offset to PE header from DOS header
Position = ReadUInt32(0x3C);
// Check PE signature "PE\0\0"
if (ReadUInt32() != 0x00004550)
return false;
// Read COFF Header
coff = ReadObject<COFFHeader>();
// Ensure presence of PE Optional header
// Size will always be 0x60 (32-bit) or 0x70 (64-bit) + (0x10 ' 0x8) for 16 RVA entries @ 8 bytes each
if (!((coff.SizeOfOptionalHeader == 0xE0 ? 32 :
coff.SizeOfOptionalHeader == 0xF0 ? (int?) 64 : null) is var likelyWordSize))
return false;
// Read PE optional header
pe = likelyWordSize switch {
32 => ReadObject<PEOptHeader32>(),
64 => ReadObject<PEOptHeader64>(),
_ => null
};
// Confirm architecture magic number matches expected word size
if ((PE) pe.Magic != pe.ExpectedMagic)
return false;
// Get IAT
var IATStart = pe.DataDirectory[12].VirtualAddress;
var IATSize = pe.DataDirectory[12].Size;
// Get sections table
sections = ReadArray<PESection>(coff.NumberOfSections);
// Confirm that .rdata section begins at same place as IAT
var rData = sections.First(x => x.Name == ".rdata");
if (rData.VirtualAddress != IATStart)
return false;
// Calculate start of function pointer table
pFuncTable = rData.PointerToRawData + IATSize;
// Skip over __guard_check_icall_fptr and __guard_dispatch_icall_fptr if present, then the following zero offset
Position = pFuncTable;
if (pe is PEOptHeader32) {
while (ReadUInt32() != 0)
pFuncTable += 4;
pFuncTable += 4;
}
else {
while (ReadUInt64() != 0)
pFuncTable += 8;
pFuncTable += 8;
}
// Get base of code
GlobalOffset = pe.ImageBase + pe.BaseOfCode - sections.First(x => x.Name == ".text").PointerToRawData;
return true;
}
public override uint[] GetFunctionTable() {
Position = pFuncTable;
var addrs = new List<uint>();
ulong addr;
while ((addr = pe is PEOptHeader32? ReadUInt32() : ReadUInt64()) != 0)
addrs.Add(MapVATR(addr) & 0xfffffffc);
return addrs.ToArray();
}
public override uint MapVATR(ulong uiAddr) {
if (uiAddr == 0)
return 0;
var section = sections.First(x => uiAddr - pe.ImageBase >= x.VirtualAddress &&
uiAddr - pe.ImageBase < x.VirtualAddress + x.SizeOfRawData);
return (uint) (uiAddr - section.VirtualAddress - pe.ImageBase + section.PointerToRawData);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.IO;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
internal class UBReader : FileFormatReader<UBReader>
{
private FatHeader header;
public UBReader(Stream stream) : base(stream) { }
protected override bool Init() {
// Fat headers are always big-endian regardless of architectures
Endianness = Endianness.Big;
header = ReadObject<FatHeader>();
if ((UB) header.Magic != UB.FAT_MAGIC)
return false;
NumImages = header.NumArch;
return true;
}
public override IFileFormatReader this[uint index] {
get {
Position = 0x8 + 0x14 * index; // sizeof(FatHeader), sizeof(FatArch)
Endianness = Endianness.Big;
var arch = ReadObject<FatArch>();
Position = arch.Offset;
Endianness = Endianness.Little;
using var s = new MemoryStream(ReadBytes((int) arch.Size));
return (IFileFormatReader) MachOReader32.Load(s) ?? MachOReader64.Load(s);
}
}
}
}

View File

@@ -0,0 +1,54 @@
/*
Copyright 2019-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
namespace Il2CppInspector
{
// NOTE: What we really should have done here is add a TWord type parameter to FileFormatReader<T>
// then we could probably avoid most of this
interface IWordConverter<TWord> where TWord : struct
{
TWord Add(TWord a, TWord b);
TWord Sub(TWord a, TWord b);
TWord Div(TWord a, TWord b);
TWord Div(TWord a, int b);
TWord FromUInt(uint a);
int Int(TWord a);
long Long(TWord a);
ulong ULong(TWord a);
bool Gt(TWord a, TWord b);
uint[] UIntArray(TWord[] a);
}
internal class Convert32 : IWordConverter<uint>
{
public uint Add(uint a, uint b) => a + b;
public uint Sub(uint a, uint b) => a - b;
public uint Div(uint a, uint b) => a / b;
public uint Div(uint a, int b) => a / (uint)b;
public uint FromUInt(uint a) => a;
public int Int(uint a) => (int)a;
public long Long(uint a) => a;
public ulong ULong(uint a) => a;
public bool Gt(uint a, uint b) => a > b;
public uint[] UIntArray(uint[] a) => a;
}
internal class Convert64 : IWordConverter<ulong>
{
public ulong Add(ulong a, ulong b) => a + b;
public ulong Sub(ulong a, ulong b) => a - b;
public ulong Div(ulong a, ulong b) => a / b;
public ulong Div(ulong a, int b) => a / (uint)b;
public ulong FromUInt(uint a) => a;
public int Int(ulong a) => (int)a;
public long Long(ulong a) => (long)a;
public ulong ULong(ulong a) => a;
public bool Gt(ulong a, ulong b) => a > b;
public uint[] UIntArray(ulong[] a) => Array.ConvertAll(a, x => (uint)x);
}
}

View File

@@ -0,0 +1,258 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector
{
public abstract class Il2CppBinary
{
public IFileFormatReader Image { get; }
public Il2CppCodeRegistration CodeRegistration { get; protected set; }
public Il2CppMetadataRegistration MetadataRegistration { get; protected set; }
// Information for disassembly reverse engineering
public ulong CodeRegistrationPointer { get; private set; }
public ulong MetadataRegistrationPointer { get; private set; }
public ulong RegistrationFunctionPointer { get; private set; }
public Dictionary<string, ulong> CodeGenModulePointers { get; } = new Dictionary<string, ulong>();
// Only for <=v24.1
public ulong[] GlobalMethodPointers { get; set; }
// Only for >=v24.2
public Dictionary<Il2CppCodeGenModule, ulong[]> ModuleMethodPointers { get; set; } = new Dictionary<Il2CppCodeGenModule, ulong[]>();
// Only for >=v24.2. In earlier versions, invoker indices are stored in Il2CppMethodDefinition in the metadata file
public Dictionary<Il2CppCodeGenModule, int[]> MethodInvokerIndices { get; set; } = new Dictionary<Il2CppCodeGenModule, int[]>();
// NOTE: In versions <21 and earlier releases of v21, use FieldOffsets:
// global field index => field offset
// In versions >=22 and later releases of v21, use FieldOffsetPointers:
// type index => RVA in image where the list of field offsets for the type start (4 bytes per field)
// Negative field offsets from start of each function
public uint[] FieldOffsets { get; private set; }
// Pointers to field offsets
public long[] FieldOffsetPointers { get; private set; }
// Generated functions which call constructors on custom attributes
public ulong[] CustomAttributeGenerators { get; private set; }
// IL2CPP-generated functions which implement MethodBase.Invoke with a unique signature per invoker, defined in Il2CppInvokerTable.cpp
// One invoker specifies a return type and argument list. Multiple methods with the same signature can be invoked with the same invoker
public ulong[] MethodInvokePointers { get; private set; }
// Generic method specs for vtables
public Il2CppMethodSpec[] MethodSpecs { get; private set; }
// List of run-time concrete generic class and method signatures
public List<Il2CppGenericInst> GenericInstances { get; private set; }
// List of constructed generic method function pointers corresponding to each possible method instantiation
public Dictionary<Il2CppMethodSpec, ulong> GenericMethodPointers { get; } = new Dictionary<Il2CppMethodSpec, ulong>();
// List of invoker pointers for concrete generic methods from MethodSpecs (as above)
public Dictionary<Il2CppMethodSpec, int> GenericMethodInvokerIndices { get; } = new Dictionary<Il2CppMethodSpec, int>();
// Every type reference (TypeRef) sorted by index
public List<Il2CppType> TypeReferences { get; private set; }
// Every type reference index sorted by virtual address
public Dictionary<ulong, int> TypeReferenceIndicesByAddress { get; private set; }
// From v24.2 onwards, this structure is stored for each module (image)
// One assembly may contain multiple modules
public Dictionary<string, Il2CppCodeGenModule> Modules { get; private set; }
protected Il2CppBinary(IFileFormatReader stream) {
Image = stream;
}
protected Il2CppBinary(IFileFormatReader stream, uint codeRegistration, uint metadataRegistration) {
Image = stream;
Configure(Image, codeRegistration, metadataRegistration);
}
// Load and initialize a binary of any supported architecture
public static Il2CppBinary Load(IFileFormatReader stream, double metadataVersion) {
// Get type from image architecture
var type = Assembly.GetExecutingAssembly().GetType("Il2CppInspector.Il2CppBinary" + stream.Arch.ToUpper());
if (type == null)
throw new NotImplementedException("Unsupported architecture: " + stream.Arch);
var inst = (Il2CppBinary) Activator.CreateInstance(type, stream);
// Try to process the IL2CPP image; return the instance if succeeded, otherwise null
return inst.Initialize(metadataVersion) ? inst : null;
}
// Architecture-specific search function
protected abstract (ulong, ulong) ConsiderCode(IFileFormatReader image, uint loc);
// Check all search locations
public bool Initialize(double version, uint imageIndex = 0) {
var subImage = Image[imageIndex];
subImage.Version = version;
// Try searching the symbol table
var symbols = subImage.GetSymbolTable();
if (symbols?.Any() ?? false) {
Console.WriteLine($"Symbol table(s) found with {symbols.Count} entries");
symbols.TryGetValue("g_CodeRegistration", out var code);
symbols.TryGetValue("g_MetadataRegistration", out var metadata);
if (code == 0)
symbols.TryGetValue("_g_CodeRegistration", out code);
if (metadata == 0)
symbols.TryGetValue("_g_MetadataRegistration", out metadata);
if (code != 0 && metadata != 0) {
Console.WriteLine("Required structures acquired from symbol lookup");
Configure(subImage, code, metadata);
return true;
}
else {
Console.WriteLine("No matches in symbol table");
}
}
else if (symbols != null) {
Console.WriteLine("No symbol table present in binary file");
}
else {
Console.WriteLine("Symbol table search not implemented for this binary format");
}
// Try searching the function table
var addrs = subImage.GetFunctionTable();
Debug.WriteLine("Function table:");
Debug.WriteLine(string.Join(", ", from a in addrs select string.Format($"0x{a:X8}")));
foreach (var loc in addrs)
if (loc != 0) {
var (code, metadata) = ConsiderCode(subImage, loc);
if (code != 0) {
RegistrationFunctionPointer = loc + subImage.GlobalOffset;
Console.WriteLine("Required structures acquired from code heuristics. Initialization function: 0x{0:X16}", RegistrationFunctionPointer);
Configure(subImage, code, metadata);
return true;
}
}
Console.WriteLine("No matches via code heuristics");
return false;
}
private void Configure(IFileFormatReader image, ulong codeRegistration, ulong metadataRegistration) {
// Store locations
CodeRegistrationPointer = codeRegistration;
MetadataRegistrationPointer = metadataRegistration;
Console.WriteLine("CodeRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", image.Bits == 32 ? codeRegistration & 0xffff_ffff : codeRegistration, image.MapVATR(codeRegistration));
Console.WriteLine("MetadataRegistration struct found at 0x{0:X16} (file offset 0x{1:X8})", image.Bits == 32 ? metadataRegistration & 0xffff_ffff : metadataRegistration, image.MapVATR(metadataRegistration));
// Set width of long (convert to sizeof(int) for 32-bit files)
if (image.Bits == 32) {
image.Stream.PrimitiveMappings.Add(typeof(long), typeof(int));
image.Stream.PrimitiveMappings.Add(typeof(ulong), typeof(uint));
}
// Root structures from which we find everything else
CodeRegistration = image.ReadMappedObject<Il2CppCodeRegistration>(codeRegistration);
MetadataRegistration = image.ReadMappedObject<Il2CppMetadataRegistration>(metadataRegistration);
// The global method pointer list was deprecated in v24.2 in favour of Il2CppCodeGenModule
if (Image.Version <= 24.1)
GlobalMethodPointers = image.ReadMappedArray<ulong>(CodeRegistration.pmethodPointers, (int) CodeRegistration.methodPointersCount);
// After v24 method pointers and RGCTX data were stored in Il2CppCodeGenModules
if (Image.Version >= 24.2) {
Modules = new Dictionary<string, Il2CppCodeGenModule>();
// Array of pointers to Il2CppCodeGenModule
var codeGenModulePointers = image.ReadMappedArray<ulong>(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount);
var modules = image.ReadMappedObjectPointerArray<Il2CppCodeGenModule>(CodeRegistration.pcodeGenModules, (int) CodeRegistration.codeGenModulesCount);
foreach (var mp in modules.Zip(codeGenModulePointers, (m, p) => new { Module = m, Pointer = p })) {
var module = mp.Module;
var name = image.ReadMappedNullTerminatedString(module.moduleName);
Modules.Add(name, module);
CodeGenModulePointers.Add(name, mp.Pointer);
// Read method pointers
ModuleMethodPointers.Add(module, image.ReadMappedArray<ulong>(module.methodPointers, (int) module.methodPointerCount));
// Read method invoker pointer indices - one per method
MethodInvokerIndices.Add(module, image.ReadMappedArray<int>(module.invokerIndices, (int) module.methodPointerCount));
}
}
// Field offset data. Metadata <=21.x uses a value-type array; >=21.x uses a pointer array
// Versions from 22 onwards use an array of pointers in Binary.FieldOffsetData
bool fieldOffsetsArePointers = (image.Version >= 22);
// Some variants of 21 also use an array of pointers
if (image.Version == 21) {
// Always 4-byte values even for 64-bit builds when array is NOT pointers
var fieldTest = image.ReadMappedArray<uint>(MetadataRegistration.pfieldOffsets, 6);
// We detect this by relying on the fact Module, Object, ValueType, Attribute, _Attribute and Int32
// are always the first six defined types, and that all but Int32 have no fields
fieldOffsetsArePointers = (fieldTest[0] == 0 && fieldTest[1] == 0 && fieldTest[2] == 0 && fieldTest[3] == 0 && fieldTest[4] == 0 && fieldTest[5] > 0);
}
// All older versions use values directly in the array
if (!fieldOffsetsArePointers)
FieldOffsets = image.ReadMappedArray<uint>(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount);
else
FieldOffsetPointers = image.ReadMappedWordArray(MetadataRegistration.pfieldOffsets, (int)MetadataRegistration.fieldOffsetsCount);
// Type references (pointer array)
var typeRefPointers = image.ReadMappedArray<ulong>(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount);
TypeReferenceIndicesByAddress = typeRefPointers.Zip(Enumerable.Range(0, typeRefPointers.Length), (a, i) => new { a, i }).ToDictionary(x => x.a, x => x.i);
TypeReferences = image.ReadMappedObjectPointerArray<Il2CppType>(MetadataRegistration.ptypes, (int) MetadataRegistration.typesCount);
// Custom attribute constructors (function pointers)
CustomAttributeGenerators = image.ReadMappedArray<ulong>(CodeRegistration.customAttributeGenerators, (int) CodeRegistration.customAttributeCount);
// Method.Invoke function pointers
MethodInvokePointers = image.ReadMappedArray<ulong>(CodeRegistration.invokerPointers, (int) CodeRegistration.invokerPointersCount);
// TODO: Function pointers as shown below
// reversePInvokeWrappers
// <=22: delegateWrappersFromManagedToNative, marshalingFunctions;
// >=21 <=22: ccwMarhsalingFunctions
// >=22: unresolvedVirtualCallPointers
// >=23: interopData
// Generic type and method specs (open and closed constructed types)
MethodSpecs = image.ReadMappedArray<Il2CppMethodSpec>(MetadataRegistration.methodSpecs, (int) MetadataRegistration.methodSpecsCount);
// Concrete generic class and method signatures
GenericInstances = image.ReadMappedObjectPointerArray<Il2CppGenericInst>(MetadataRegistration.genericInsts, (int) MetadataRegistration.genericInstsCount);
// Concrete generic method pointers
var genericMethodPointers = image.ReadMappedArray<ulong>(CodeRegistration.genericMethodPointers, (int) CodeRegistration.genericMethodPointersCount);
var genericMethodTable = image.ReadMappedArray<Il2CppGenericMethodFunctionsDefinitions>(MetadataRegistration.genericMethodTable, (int) MetadataRegistration.genericMethodTableCount);
foreach (var tableEntry in genericMethodTable) {
GenericMethodPointers.Add(MethodSpecs[tableEntry.genericMethodIndex], genericMethodPointers[tableEntry.indices.methodIndex]);
GenericMethodInvokerIndices.Add(MethodSpecs[tableEntry.genericMethodIndex], tableEntry.indices.invokerIndex);
}
}
}
}

View File

@@ -0,0 +1,231 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
// From class-internals.h / il2cpp-class-internals.h
public class Il2CppCodeRegistration
{
// Moved to Il2CppCodeGenModule in v24.2
[Version(Max = 24.1)]
public ulong methodPointersCount;
[Version(Max = 24.1)]
public ulong pmethodPointers;
public ulong reversePInvokeWrapperCount; // (was renamed from delegateWrappersFromNativeToManagedCount in v22)
public ulong reversePInvokeWrappers; // (was renamed from delegateWrappersFromNativeToManaged in v22)
// Removed in metadata v23
[Version(Max = 22)]
public ulong delegateWrappersFromManagedToNativeCount;
[Version(Max = 22)]
public ulong delegateWrappersFromManagedToNative;
[Version(Max = 22)]
public ulong marshalingFunctionsCount;
[Version(Max = 22)]
public ulong marshalingFunctions;
[Version(Min = 21, Max = 22)]
public ulong ccwMarshalingFunctionsCount;
[Version(Min = 21, Max = 22)]
public ulong ccwMarshalingFunctions;
public ulong genericMethodPointersCount;
public ulong genericMethodPointers;
public ulong invokerPointersCount;
public ulong invokerPointers;
public long customAttributeCount;
public ulong customAttributeGenerators;
// Removed in metadata v23
[Version(Min = 21, Max = 22)]
public long guidCount;
[Version(Min = 21, Max = 22)]
public ulong guids; // Il2CppGuid
// Added in metadata v22
[Version(Min = 22)]
public ulong unresolvedVirtualCallCount;
[Version(Min = 22)]
public ulong unresolvedVirtualCallPointers;
// Added in metadata v23
[Version(Min = 23)]
public ulong interopDataCount;
[Version(Min = 23)]
public ulong interopData;
// Added in metadata v24.2 to replace methodPointers and methodPointersCount
[Version(Min = 24.2)]
public ulong codeGenModulesCount;
[Version(Min = 24.2)]
public ulong pcodeGenModules;
}
// Introduced in metadata v24.2 (replaces method pointers in Il2CppCodeRegistration)
public class Il2CppCodeGenModule
{
public ulong moduleName;
public ulong methodPointerCount;
public ulong methodPointers;
public ulong invokerIndices;
public ulong reversePInvokeWrapperCount;
public ulong reversePInvokeWrapperIndices;
public ulong rgctxRangesCount;
public ulong rgctxRanges;
public ulong rgctxsCount;
public ulong rgctxs;
public ulong debuggerMetadata;
}
#pragma warning disable CS0649
public class Il2CppMetadataRegistration
{
public long genericClassesCount;
public ulong genericClasses;
public long genericInstsCount;
public ulong genericInsts;
public long genericMethodTableCount;
public ulong genericMethodTable; // Il2CppGenericMethodFunctionsDefinitions
public long typesCount;
public ulong ptypes;
public long methodSpecsCount;
public ulong methodSpecs;
[Version(Max = 16)]
public long methodReferencesCount;
[Version(Max = 16)]
public ulong methodReferences;
public long fieldOffsetsCount;
public ulong pfieldOffsets; // Changed from int32_t* to int32_t** after 5.4.0f3, before 5.5.0f3
public long typeDefinitionsSizesCount;
public ulong typeDefinitionsSizes;
[Version(Min = 19)]
public ulong metadataUsagesCount;
[Version(Min = 19)]
public ulong metadataUsages;
}
#pragma warning restore CS0649
// From blob.h / il2cpp-blob.h
public enum Il2CppTypeEnum
{
IL2CPP_TYPE_END = 0x00, /* End of List */
IL2CPP_TYPE_VOID = 0x01,
IL2CPP_TYPE_BOOLEAN = 0x02,
IL2CPP_TYPE_CHAR = 0x03,
IL2CPP_TYPE_I1 = 0x04,
IL2CPP_TYPE_U1 = 0x05,
IL2CPP_TYPE_I2 = 0x06,
IL2CPP_TYPE_U2 = 0x07,
IL2CPP_TYPE_I4 = 0x08,
IL2CPP_TYPE_U4 = 0x09,
IL2CPP_TYPE_I8 = 0x0a,
IL2CPP_TYPE_U8 = 0x0b,
IL2CPP_TYPE_R4 = 0x0c,
IL2CPP_TYPE_R8 = 0x0d,
IL2CPP_TYPE_STRING = 0x0e,
IL2CPP_TYPE_PTR = 0x0f, /* arg: <type> token */
IL2CPP_TYPE_BYREF = 0x10, /* arg: <type> token */
IL2CPP_TYPE_VALUETYPE = 0x11, /* arg: <type> token */
IL2CPP_TYPE_CLASS = 0x12, /* arg: <type> token */
IL2CPP_TYPE_VAR = 0x13, /* Generic parameter in a generic type definition, represented as number (compressed unsigned integer) number */
IL2CPP_TYPE_ARRAY = 0x14, /* type, rank, boundsCount, bound1, loCount, lo1 */
IL2CPP_TYPE_GENERICINST = 0x15, /* <type> <type-arg-count> <type-1> \x{2026} <type-n> */
IL2CPP_TYPE_TYPEDBYREF = 0x16,
IL2CPP_TYPE_I = 0x18,
IL2CPP_TYPE_U = 0x19,
IL2CPP_TYPE_FNPTR = 0x1b, /* arg: full method signature */
IL2CPP_TYPE_OBJECT = 0x1c,
IL2CPP_TYPE_SZARRAY = 0x1d, /* 0-based one-dim-array */
IL2CPP_TYPE_MVAR = 0x1e, /* Generic parameter in a generic method definition, represented as number (compressed unsigned integer) */
IL2CPP_TYPE_CMOD_REQD = 0x1f, /* arg: typedef or typeref token */
IL2CPP_TYPE_CMOD_OPT = 0x20, /* optional arg: typedef or typref token */
IL2CPP_TYPE_INTERNAL = 0x21, /* CLR internal type */
IL2CPP_TYPE_MODIFIER = 0x40, /* Or with the following types */
IL2CPP_TYPE_SENTINEL = 0x41, /* Sentinel for varargs method signature */
IL2CPP_TYPE_PINNED = 0x45, /* Local var that points to pinned object */
IL2CPP_TYPE_ENUM = 0x55 /* an enumeration */
}
// From metadata.h / il2cpp-runtime-metadata.h
public class Il2CppType
{
/*
union
{
TypeDefinitionIndex klassIndex; // for VALUETYPE and CLASS
const Il2CppType* type; // for PTR and SZARRAY
Il2CppArrayType* array; // for ARRAY
GenericParameterIndex genericParameterIndex; // for VAR and MVAR
Il2CppGenericClass* generic_class; // for GENERICINST
}
*/
public ulong datapoint;
public ulong bits; // this should be private but we need it to be public for BinaryObjectReader to work
public uint attrs => (uint) bits & 0xffff; /* param attributes or field flags */
public Il2CppTypeEnum type => (Il2CppTypeEnum)((bits >> 16) & 0xff);
public uint num_mods => (uint) (bits >> 24) & 0x3f; /* max 64 modifiers follow at the end */
public bool byref => ((bits >> 30) & 1) == 1;
public bool pinned => (bits >> 31) == 1; /* valid when included in a local var signature */
}
public class Il2CppGenericClass
{
public long typeDefinitionIndex; /* the generic type definition */
public Il2CppGenericContext context; /* a context that contains the type instantiation doesn't contain any method instantiation */
public ulong cached_class; /* if present, the Il2CppClass corresponding to the instantiation. */
}
public class Il2CppGenericContext
{
/* The instantiation corresponding to the class generic parameters */
public ulong class_inst;
/* The instantiation corresponding to the method generic parameters */
public ulong method_inst;
}
public class Il2CppGenericInst
{
public ulong type_argc;
public ulong type_argv;
}
public class Il2CppArrayType
{
public ulong etype;
public byte rank;
public byte numsizes;
public byte numlobounds;
public ulong sizes;
public ulong lobounds;
}
public class Il2CppMethodSpec
{
public int methodDefinitionIndex;
public int classIndexIndex;
public int methodIndexIndex;
}
public class Il2CppGenericMethodFunctionsDefinitions
{
public int genericMethodIndex;
public Il2CppGenericMethodIndices indices;
}
public class Il2CppGenericMethodIndices
{
public int methodIndex;
public int invokerIndex;
}
}

View File

@@ -0,0 +1,244 @@
using System.Collections.Generic;
// Constants from il2cpp/tabledefs.h
namespace Il2CppInspector
{
public static class Il2CppConstants
{
/*
* Field Attributes (21.1.5).
*/
public const int FIELD_ATTRIBUTE_FIELD_ACCESS_MASK = 0x0007;
public const int FIELD_ATTRIBUTE_COMPILER_CONTROLLED = 0x0000;
public const int FIELD_ATTRIBUTE_PRIVATE = 0x0001;
public const int FIELD_ATTRIBUTE_FAM_AND_ASSEM = 0x0002;
public const int FIELD_ATTRIBUTE_ASSEMBLY = 0x0003;
public const int FIELD_ATTRIBUTE_FAMILY = 0x0004;
public const int FIELD_ATTRIBUTE_FAM_OR_ASSEM = 0x0005;
public const int FIELD_ATTRIBUTE_PUBLIC = 0x0006;
public const int FIELD_ATTRIBUTE_STATIC = 0x0010;
public const int FIELD_ATTRIBUTE_INIT_ONLY = 0x0020;
public const int FIELD_ATTRIBUTE_LITERAL = 0x0040;
public const int FIELD_ATTRIBUTE_NOT_SERIALIZED = 0x0080;
public const int FIELD_ATTRIBUTE_SPECIAL_NAME = 0x0200;
public const int FIELD_ATTRIBUTE_PINVOKE_IMPL = 0x2000;
/* For runtime use only */
public const int FIELD_ATTRIBUTE_RESERVED_MASK = 0x9500;
public const int FIELD_ATTRIBUTE_RT_SPECIAL_NAME = 0x0400;
public const int FIELD_ATTRIBUTE_HAS_FIELD_MARSHAL = 0x1000;
public const int FIELD_ATTRIBUTE_HAS_DEFAULT = 0x8000;
public const int FIELD_ATTRIBUTE_HAS_FIELD_RVA = 0x0100;
/*
* Method Attributes (22.1.9)
*/
public const int METHOD_IMPL_ATTRIBUTE_CODE_TYPE_MASK = 0x0003;
public const int METHOD_IMPL_ATTRIBUTE_IL = 0x0000;
public const int METHOD_IMPL_ATTRIBUTE_NATIVE = 0x0001;
public const int METHOD_IMPL_ATTRIBUTE_OPTIL = 0x0002;
public const int METHOD_IMPL_ATTRIBUTE_RUNTIME = 0x0003;
public const int METHOD_IMPL_ATTRIBUTE_MANAGED_MASK = 0x0004;
public const int METHOD_IMPL_ATTRIBUTE_UNMANAGED = 0x0004;
public const int METHOD_IMPL_ATTRIBUTE_MANAGED = 0x0000;
public const int METHOD_IMPL_ATTRIBUTE_FORWARD_REF = 0x0010;
public const int METHOD_IMPL_ATTRIBUTE_PRESERVE_SIG = 0x0080;
public const int METHOD_IMPL_ATTRIBUTE_INTERNAL_CALL = 0x1000;
public const int METHOD_IMPL_ATTRIBUTE_SYNCHRONIZED = 0x0020;
public const int METHOD_IMPL_ATTRIBUTE_NOINLINING = 0x0008;
public const int METHOD_IMPL_ATTRIBUTE_MAX_METHOD_IMPL_VAL = 0xffff;
public const int METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK = 0x0007;
public const int METHOD_ATTRIBUTE_COMPILER_CONTROLLED = 0x0000;
public const int METHOD_ATTRIBUTE_PRIVATE = 0x0001;
public const int METHOD_ATTRIBUTE_FAM_AND_ASSEM = 0x0002;
public const int METHOD_ATTRIBUTE_ASSEM = 0x0003;
public const int METHOD_ATTRIBUTE_FAMILY = 0x0004;
public const int METHOD_ATTRIBUTE_FAM_OR_ASSEM = 0x0005;
public const int METHOD_ATTRIBUTE_PUBLIC = 0x0006;
public const int METHOD_ATTRIBUTE_STATIC = 0x0010;
public const int METHOD_ATTRIBUTE_FINAL = 0x0020;
public const int METHOD_ATTRIBUTE_VIRTUAL = 0x0040;
public const int METHOD_ATTRIBUTE_HIDE_BY_SIG = 0x0080;
public const int METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK = 0x0100;
public const int METHOD_ATTRIBUTE_REUSE_SLOT = 0x0000;
public const int METHOD_ATTRIBUTE_NEW_SLOT = 0x0100;
public const int METHOD_ATTRIBUTE_STRICT = 0x0200;
public const int METHOD_ATTRIBUTE_ABSTRACT = 0x0400;
public const int METHOD_ATTRIBUTE_SPECIAL_NAME = 0x0800;
public const int METHOD_ATTRIBUTE_PINVOKE_IMPL = 0x2000;
public const int METHOD_ATTRIBUTE_UNMANAGED_EXPORT = 0x0008;
/*
* For runtime use only
*/
public const int METHOD_ATTRIBUTE_RESERVED_MASK = 0xd000;
public const int METHOD_ATTRIBUTE_RT_SPECIAL_NAME = 0x1000;
public const int METHOD_ATTRIBUTE_HAS_SECURITY = 0x4000;
public const int METHOD_ATTRIBUTE_REQUIRE_SEC_OBJECT = 0x8000;
/*
* Type Attributes (21.1.13).
*/
public const int TYPE_ATTRIBUTE_VISIBILITY_MASK = 0x00000007;
public const int TYPE_ATTRIBUTE_NOT_PUBLIC = 0x00000000;
public const int TYPE_ATTRIBUTE_PUBLIC = 0x00000001;
public const int TYPE_ATTRIBUTE_NESTED_PUBLIC = 0x00000002;
public const int TYPE_ATTRIBUTE_NESTED_PRIVATE = 0x00000003;
public const int TYPE_ATTRIBUTE_NESTED_FAMILY = 0x00000004;
public const int TYPE_ATTRIBUTE_NESTED_ASSEMBLY = 0x00000005;
public const int TYPE_ATTRIBUTE_NESTED_FAM_AND_ASSEM = 0x00000006;
public const int TYPE_ATTRIBUTE_NESTED_FAM_OR_ASSEM = 0x00000007;
public const int TYPE_ATTRIBUTE_LAYOUT_MASK = 0x00000018;
public const int TYPE_ATTRIBUTE_AUTO_LAYOUT = 0x00000000;
public const int TYPE_ATTRIBUTE_SEQUENTIAL_LAYOUT = 0x00000008;
public const int TYPE_ATTRIBUTE_EXPLICIT_LAYOUT = 0x00000010;
public const int TYPE_ATTRIBUTE_CLASS_SEMANTIC_MASK = 0x00000020;
public const int TYPE_ATTRIBUTE_CLASS = 0x00000000;
public const int TYPE_ATTRIBUTE_INTERFACE = 0x00000020;
public const int TYPE_ATTRIBUTE_ABSTRACT = 0x00000080;
public const int TYPE_ATTRIBUTE_SEALED = 0x00000100;
public const int TYPE_ATTRIBUTE_SPECIAL_NAME = 0x00000400;
public const int TYPE_ATTRIBUTE_IMPORT = 0x00001000;
public const int TYPE_ATTRIBUTE_SERIALIZABLE = 0x00002000;
public const int TYPE_ATTRIBUTE_STRING_FORMAT_MASK = 0x00030000;
public const int TYPE_ATTRIBUTE_ANSI_CLASS = 0x00000000;
public const int TYPE_ATTRIBUTE_UNICODE_CLASS = 0x00010000;
public const int TYPE_ATTRIBUTE_AUTO_CLASS = 0x00020000;
public const int TYPE_ATTRIBUTE_BEFORE_FIELD_INIT = 0x00100000;
public const int TYPE_ATTRIBUTE_FORWARDER = 0x00200000;
public const int TYPE_ATTRIBUTE_RESERVED_MASK = 0x00040800;
public const int TYPE_ATTRIBUTE_RT_SPECIAL_NAME = 0x00000800;
public const int TYPE_ATTRIBUTE_HAS_SECURITY = 0x00040000;
/*
* Flags for Params (22.1.12)
*/
public const int PARAM_ATTRIBUTE_IN = 0x0001;
public const int PARAM_ATTRIBUTE_OUT = 0x0002;
public const int PARAM_ATTRIBUTE_OPTIONAL = 0x0010;
public const int PARAM_ATTRIBUTE_RESERVED_MASK = 0xf000;
public const int PARAM_ATTRIBUTE_HAS_DEFAULT = 0x1000;
public const int PARAM_ATTRIBUTE_HAS_FIELD_MARSHAL = 0x2000;
public const int PARAM_ATTRIBUTE_UNUSED = 0xcfe0;
// Flags for Generic Parameters (II.23.1.7)
public const int GENERIC_PARAMETER_ATTRIBUTE_NON_VARIANT = 0x00;
public const int GENERIC_PARAMETER_ATTRIBUTE_COVARIANT = 0x01;
public const int GENERIC_PARAMETER_ATTRIBUTE_CONTRAVARIANT = 0x02;
public const int GENERIC_PARAMETER_ATTRIBUTE_VARIANCE_MASK = 0x03;
public const int GENERIC_PARAMETER_ATTRIBUTE_REFERENCE_TYPE_CONSTRAINT = 0x04;
public const int GENERIC_PARAMETER_ATTRIBUTE_NOT_NULLABLE_VALUE_TYPE_CONSTRAINT = 0x08;
public const int GENERIC_PARAMETER_ATTRIBUTE_DEFAULT_CONSTRUCTOR_CONSTRAINT = 0x10;
public const int GENERIC_PARAMETER_ATTRIBUTE_SPECIAL_CONSTRAINT_MASK = 0x1C;
/**
* 21.5 AssemblyRefs
*/
public const int ASSEMBLYREF_FULL_PUBLIC_KEY_FLAG = 0x00000001;
public const int ASSEMBLYREF_RETARGETABLE_FLAG = 0x00000100;
public const int ASSEMBLYREF_ENABLEJITCOMPILE_TRACKING_FLAG = 0x00008000;
public const int ASSEMBLYREF_DISABLEJITCOMPILE_OPTIMIZER_FLAG = 0x00004000;
// Naming conventions (follows order of Il2CppTypeEnum)
public static List<string> CSharpTypeString = new List<string>
{
"END",
"void",
"bool",
"char",
"sbyte",
"byte",
"short",
"ushort",
"int",
"uint",
"long",
"ulong",
"float",
"double",
"string",
"PTR", // Processed separately
"BYREF",
"ValueType", // Processed separately
"CLASS", // Processed separately
"T",
"Array", // Processed separately
"GENERICINST", // Processed separately
"TypedReference", // params
"None",
"IntPtr",
"UIntPtr",
"None",
"Delegate",
"object",
"SZARRAY", // Processed separately
"T",
"CMOD_REQD",
"CMOD_OPT",
"INTERNAL",
// Added in for convenience
"decimal"
};
public static List<string> FullNameTypeString = new List<string>
{
"END",
"System.Void",
"System.Boolean",
"System.Char",
"System.SByte",
"System.Byte",
"System.Int16",
"System.UInt16",
"System.Int32",
"System.UInt32",
"System.Int64",
"System.UInt64",
"System.Single",
"System.Double",
"System.String",
"PTR", // Processed separately
"BYREF",
"System.ValueType", // Processed separately
"CLASS", // Processed separately
"T",
"System.Array", // Processed separately
"GENERICINST", // Processed separately
"System.TypedReference", // params
"None",
"System.IntPtr",
"System.UIntPtr",
"None",
"System.Delegate",
"System.Object",
"SZARRAY", // Processed separately
"T",
"CMOD_REQD",
"CMOD_OPT",
"INTERNAL",
// Added in for convenience
"System.Decimal"
};
}
}

View File

@@ -0,0 +1,343 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace Il2CppInspector
{
// Il2CppInspector ties together the binary and metadata files into a congruent API surface
public class Il2CppInspector
{
public Il2CppBinary Binary { get; }
public Metadata Metadata { get; }
// All function pointers including attribute initialization functions etc. (start => end)
public Dictionary<ulong, ulong> FunctionAddresses { get; }
// Attribute indexes (>=24.1) arranged by customAttributeStart and token
public Dictionary<int, Dictionary<uint, int>> AttributeIndicesByToken { get; }
// Merged list of all metadata usage references
public List<MetadataUsage> MetadataUsages { get; }
// Shortcuts
public double Version => Metadata.Version;
public Dictionary<int, string> Strings => Metadata.Strings;
public string[] StringLiterals => Metadata.StringLiterals;
public Il2CppTypeDefinition[] TypeDefinitions => Metadata.Types;
public Il2CppAssemblyDefinition[] Assemblies => Metadata.Assemblies;
public Il2CppImageDefinition[] Images => Metadata.Images;
public Il2CppMethodDefinition[] Methods => Metadata.Methods;
public Il2CppParameterDefinition[] Params => Metadata.Params;
public Il2CppFieldDefinition[] Fields => Metadata.Fields;
public Il2CppPropertyDefinition[] Properties => Metadata.Properties;
public Il2CppEventDefinition[] Events => Metadata.Events;
public Il2CppGenericContainer[] GenericContainers => Metadata.GenericContainers;
public Il2CppGenericParameter[] GenericParameters => Metadata.GenericParameters;
public int[] GenericConstraintIndices => Metadata.GenericConstraintIndices;
public Il2CppCustomAttributeTypeRange[] AttributeTypeRanges => Metadata.AttributeTypeRanges;
public Il2CppInterfaceOffsetPair[] InterfaceOffsets => Metadata.InterfaceOffsets;
public int[] InterfaceUsageIndices => Metadata.InterfaceUsageIndices;
public int[] NestedTypeIndices => Metadata.NestedTypeIndices;
public int[] AttributeTypeIndices => Metadata.AttributeTypeIndices;
public uint[] VTableMethodIndices => Metadata.VTableMethodIndices;
public Il2CppFieldRef[] FieldRefs => Metadata.FieldRefs;
public Dictionary<int, (ulong, object)> FieldDefaultValue { get; } = new Dictionary<int, (ulong, object)>();
public Dictionary<int, (ulong, object)> ParameterDefaultValue { get; } = new Dictionary<int, (ulong, object)>();
public List<long> FieldOffsets { get; }
public List<Il2CppType> TypeReferences => Binary.TypeReferences;
public Dictionary<ulong, int> TypeReferenceIndicesByAddress => Binary.TypeReferenceIndicesByAddress;
public List<Il2CppGenericInst> GenericInstances => Binary.GenericInstances;
public Dictionary<string, Il2CppCodeGenModule> Modules => Binary.Modules;
public ulong[] CustomAttributeGenerators => Binary.CustomAttributeGenerators;
public ulong[] MethodInvokePointers => Binary.MethodInvokePointers;
public Il2CppMethodSpec[] MethodSpecs => Binary.MethodSpecs;
public Dictionary<Il2CppMethodSpec, ulong> GenericMethodPointers => Binary.GenericMethodPointers;
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;
private (ulong MetadataAddress, object Value)? getDefaultValue(int typeIndex, int dataIndex) {
// No default
if (dataIndex == -1)
return (0ul, null);
// Get pointer in binary to default value
var pValue = Metadata.Header.fieldAndParameterDefaultValueDataOffset + dataIndex;
var typeRef = TypeReferences[typeIndex];
// Default value is null
if (pValue == 0)
return (0ul, null);
object value = null;
Metadata.Position = pValue;
switch (typeRef.type) {
case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN:
value = Metadata.ReadBoolean();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_U1:
case Il2CppTypeEnum.IL2CPP_TYPE_I1:
value = Metadata.ReadByte();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_CHAR:
// UTF-8 character assumed
value = BitConverter.ToChar(Metadata.ReadBytes(2), 0);
break;
case Il2CppTypeEnum.IL2CPP_TYPE_U2:
value = Metadata.ReadUInt16();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_I2:
value = Metadata.ReadInt16();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_U4:
value = Metadata.ReadUInt32();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_I4:
value = Metadata.ReadInt32();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_U8:
value = Metadata.ReadUInt64();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_I8:
value = Metadata.ReadInt64();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_R4:
value = Metadata.ReadSingle();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_R8:
value = Metadata.ReadDouble();
break;
case Il2CppTypeEnum.IL2CPP_TYPE_STRING:
var uiLen = Metadata.ReadInt32();
value = Encoding.UTF8.GetString(Metadata.ReadBytes(uiLen));
break;
}
return ((ulong) pValue, value);
}
private List<MetadataUsage> buildMetadataUsages()
{
var usages = new Dictionary<uint, MetadataUsage>();
foreach (var metadataUsageList in Metadata.MetadataUsageLists)
{
for (var i = 0; i < metadataUsageList.count; i++)
{
var metadataUsagePair = Metadata.MetadataUsagePairs[metadataUsageList.start + i];
var encodedType = metadataUsagePair.encodedSourceIndex & 0xE0000000;
var usageType = (MetadataUsageType)(encodedType >> 29);
var sourceIndex = metadataUsagePair.encodedSourceIndex & 0x1FFFFFFF;
var destinationIndex = metadataUsagePair.destinationindex;
usages.TryAdd(destinationIndex, new MetadataUsage(usageType, (int)sourceIndex, (int)destinationIndex));
}
}
// Metadata usages (addresses)
// Unfortunately the value supplied in MetadataRegistration.matadataUsagesCount seems to be incorrect,
// so we have to calculate the correct number of usages above before reading the usage address list from the binary
var addresses = Binary.Image.ReadMappedArray<ulong>(Binary.MetadataRegistration.metadataUsages, usages.Count);
foreach (var usage in usages.Values)
usage.SetAddress(addresses[usage.DestinationIndex]);
return usages.Values.ToList();
}
public Il2CppInspector(Il2CppBinary binary, Metadata metadata) {
// Store stream representations
Binary = binary;
Metadata = metadata;
// Get all field default values
foreach (var fdv in Metadata.FieldDefaultValues)
FieldDefaultValue.Add(fdv.fieldIndex, ((ulong,object)) getDefaultValue(fdv.typeIndex, fdv.dataIndex));
// Get all parameter default values
foreach (var pdv in Metadata.ParameterDefaultValues)
ParameterDefaultValue.Add(pdv.parameterIndex, ((ulong,object)) getDefaultValue(pdv.typeIndex, pdv.dataIndex));
// Get all field offsets
if (Binary.FieldOffsets != null) {
FieldOffsets = Binary.FieldOffsets.Select(x => (long) x).ToList();
}
// Convert pointer list into fields
else {
var offsets = new Dictionary<int, long>();
for (var i = 0; i < TypeDefinitions.Length; i++) {
var def = TypeDefinitions[i];
var pFieldOffsets = Binary.FieldOffsetPointers[i];
if (pFieldOffsets != 0) {
bool available = true;
// If the target address range is not mapped in the file, assume zeroes
try {
BinaryImage.Position = BinaryImage.MapVATR((ulong) pFieldOffsets);
}
catch (InvalidOperationException) {
available = false;
}
for (var f = 0; f < def.field_count; f++)
offsets.Add(def.fieldStart + f, available? BinaryImage.ReadUInt32() : 0);
}
}
FieldOffsets = offsets.OrderBy(x => x.Key).Select(x => x.Value).ToList();
}
// Get sorted list of function pointers from all sources
var sortedFunctionPointers = (Version <= 24.1)?
Binary.GlobalMethodPointers.ToList() :
Binary.ModuleMethodPointers.SelectMany(module => module.Value).ToList();
sortedFunctionPointers.AddRange(CustomAttributeGenerators);
sortedFunctionPointers.AddRange(MethodInvokePointers);
sortedFunctionPointers.AddRange(GenericMethodPointers.Values);
sortedFunctionPointers.Sort();
sortedFunctionPointers = sortedFunctionPointers.Distinct().ToList();
// Guestimate function end addresses
FunctionAddresses = new Dictionary<ulong, ulong>(sortedFunctionPointers.Count);
for (var i = 0; i < sortedFunctionPointers.Count - 1; i++)
FunctionAddresses.Add(sortedFunctionPointers[i], sortedFunctionPointers[i + 1]);
// The last method end pointer will be incorrect but there is no way of calculating it
FunctionAddresses.Add(sortedFunctionPointers[^1], sortedFunctionPointers[^1]);
// Organize custom attribute indices
if (Version >= 24.1) {
AttributeIndicesByToken = new Dictionary<int, Dictionary<uint, int>>();
foreach (var image in Images) {
var attsByToken = new Dictionary<uint, int>();
for (int i = 0; i < image.customAttributeCount; i++) {
var index = image.customAttributeStart + i;
var token = AttributeTypeRanges[index].token;
attsByToken.Add(token, index);
}
AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken);
}
}
// Merge all metadata usage references into a single distinct list
if (Version >= 19)
MetadataUsages = buildMetadataUsages();
}
// Get a method pointer if available
public (ulong Start, ulong End)? GetMethodPointer(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) {
// Find method pointer
if (methodDef.methodIndex < 0)
return null;
ulong start = 0;
// Global method pointer array
if (Version <= 24.1) {
start = Binary.GlobalMethodPointers[methodDef.methodIndex];
}
// Per-module method pointer array uses the bottom 24 bits of the method's metadata token
// Derived from il2cpp::vm::MetadataCache::GetMethodPointer
if (Version >= 24.2) {
var method = (methodDef.token & 0xffffff);
if (method == 0)
return null;
// In the event of an exception, the method pointer is not set in the file
// This probably means it has been optimized away by the compiler, or is an unused generic method
try {
// Remove ARM Thumb marker LSB if necessary
start = Binary.ModuleMethodPointers[module][method - 1];
}
catch (IndexOutOfRangeException) {
return null;
}
}
if (start == 0)
return null;
// Consider the end of the method to be the start of the next method (or zero)
// The last method end will be wrong but there is no way to calculate it
return (start & 0xffff_ffff_ffff_fffe, FunctionAddresses[start]);
}
// Get a concrete generic method pointer if available
public (ulong Start, ulong End)? GetGenericMethodPointer(Il2CppMethodSpec spec) {
if (GenericMethodPointers.TryGetValue(spec, out var start)) {
return (start & 0xffff_ffff_ffff_fffe, FunctionAddresses[start]);
}
return null;
}
// Get a method invoker index from a method definition
public int GetInvokerIndex(Il2CppCodeGenModule module, Il2CppMethodDefinition methodDef) {
if (Version <= 24.1) {
return methodDef.invokerIndex;
}
// Version >= 24.2
var methodInModule = (methodDef.token & 0xffffff);
return Binary.MethodInvokerIndices[module][methodInModule - 1];
}
public static List<Il2CppInspector> LoadFromFile(string codeFile, string metadataFile) {
// Load the metadata file
Metadata metadata;
try {
metadata = new Metadata(new MemoryStream(File.ReadAllBytes(metadataFile)));
}
catch (Exception ex) {
Console.Error.WriteLine(ex.Message);
return null;
}
Console.WriteLine("Detected metadata version " + metadata.Version);
// Load the il2cpp code file (try all available file formats)
IFileFormatReader stream = FileFormatReader.Load(codeFile);
if (stream == null) {
Console.Error.WriteLine("Unsupported executable file format");
return null;
}
// Multi-image binaries may contain more than one Il2Cpp image
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("Architecture word size: {0}-bit", image.Bits);
Console.WriteLine("Instruction set: " + image.Arch);
Console.WriteLine("Global offset: 0x{0:X16}", image.GlobalOffset);
// Architecture-agnostic load attempt
try {
if (Il2CppBinary.Load(image, metadata.Version) is Il2CppBinary binary) {
processors.Add(new Il2CppInspector(binary, metadata));
}
else {
Console.Error.WriteLine("Could not process IL2CPP image. This may mean the binary file is packed, encrypted or obfuscated, that the file is not an IL2CPP image or that Il2CppInspector was not able to automatically find the required data.");
Console.Error.WriteLine("Please check the binary file in a disassembler to ensure that it is an unencrypted IL2CPP binary before submitting a bug report!");
}
}
// Unknown architecture
catch (NotImplementedException ex) {
Console.Error.WriteLine(ex.Message);
}
}
return processors;
}
}
}

View File

@@ -0,0 +1,184 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
public class Metadata : BinaryObjectReader
{
public Il2CppGlobalMetadataHeader Header;
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 int[] InterfaceUsageIndices { get; }
public int[] NestedTypeIndices { get; }
public int[] AttributeTypeIndices { get; }
public int[] GenericConstraintIndices { get; }
public uint[] VTableMethodIndices { get; }
public string[] StringLiterals { get; }
public Dictionary<int, string> Strings { get; } = new Dictionary<int, string>();
public List<MetadataUsage> MetadataUsages { get; } = new List<MetadataUsage>();
public Metadata(Stream stream) : base(stream)
{
// Read magic bytes
if (ReadUInt32() != 0xFAB11BAF) {
throw new InvalidOperationException("ERROR: Metadata file supplied is not valid metadata file.");
}
// Set object versioning for Bin2Object from metadata version
Version = ReadInt32();
// Rewind and read metadata header in full
Header = ReadObject<Il2CppGlobalMetadataHeader>(0);
if (Version < 16 || Version > 24)
{
throw new InvalidOperationException($"ERROR: Metadata file supplied is not a supported version ({Header.version}).");
}
// Sanity checking
// Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata always writes the metadata information in the same order it appears in the header,
// with each block always coming directly after the previous block, 4-byte aligned. We can use this to check the integrity of the data and
// detect sub-versions.
// For metadata v24, the header can either be either 0x110 (24.0, 24.1) or 0x108 (24.2) bytes long. Since 'stringLiteralOffset' is the first thing
// in the header after the sanity and version fields, and since it will always point directly to the first byte after the end of the header,
// we can use this value to determine the actual header length and therefore narrow down the metadata version to 24.0/24.1 or 24.2.
var realHeaderLength = Header.stringLiteralOffset;
if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) {
if (Version == 24.0) {
Version = 24.2;
Header = ReadObject<Il2CppGlobalMetadataHeader>(0);
}
}
if (realHeaderLength != Sizeof(typeof(Il2CppGlobalMetadataHeader))) {
throw new InvalidOperationException("ERROR: Could not verify the integrity of the metadata file or accurately identify the metadata sub-version");
}
// Load all the relevant metadata using offsets provided in the header
if (Version >= 16)
Images = ReadArray<Il2CppImageDefinition>(Header.imagesOffset, Header.imagesCount / Sizeof(typeof(Il2CppImageDefinition)));
// As an additional sanity check, all images in the metadata should have Mono.Cecil.MetadataToken == 1
// In metadata v24.1, two extra fields were added which will cause the below test to fail.
// In that case, we can then adjust the version number and reload
// Tokens were introduced in v19 - we don't bother testing earlier versions
if (Version >= 19 && Images.Any(x => x.token != 1))
if (Version == 24.0) {
Version = 24.1;
// No need to re-read the header, it's the same for both sub-versions
Images = ReadArray<Il2CppImageDefinition>(Header.imagesOffset, Header.imagesCount / Sizeof(typeof(Il2CppImageDefinition)));
if (Images.Any(x => x.token != 1))
throw new InvalidOperationException("ERROR: Could not verify the integrity of the metadata file image list");
}
Types = ReadArray<Il2CppTypeDefinition>(Header.typeDefinitionsOffset, Header.typeDefinitionsCount / Sizeof(typeof(Il2CppTypeDefinition)));
Methods = ReadArray<Il2CppMethodDefinition>(Header.methodsOffset, Header.methodsCount / Sizeof(typeof(Il2CppMethodDefinition)));
Params = ReadArray<Il2CppParameterDefinition>(Header.parametersOffset, Header.parametersCount / Sizeof(typeof(Il2CppParameterDefinition)));
Fields = ReadArray<Il2CppFieldDefinition>(Header.fieldsOffset, Header.fieldsCount / Sizeof(typeof(Il2CppFieldDefinition)));
FieldDefaultValues = ReadArray<Il2CppFieldDefaultValue>(Header.fieldDefaultValuesOffset, Header.fieldDefaultValuesCount / Sizeof(typeof(Il2CppFieldDefaultValue)));
Properties = ReadArray<Il2CppPropertyDefinition>(Header.propertiesOffset, Header.propertiesCount / Sizeof(typeof(Il2CppPropertyDefinition)));
Events = ReadArray<Il2CppEventDefinition>(Header.eventsOffset, Header.eventsCount / Sizeof(typeof(Il2CppEventDefinition)));
InterfaceUsageIndices = ReadArray<int>(Header.interfacesOffset, Header.interfacesCount / sizeof(int));
NestedTypeIndices = ReadArray<int>(Header.nestedTypesOffset, Header.nestedTypesCount / sizeof(int));
GenericContainers = ReadArray<Il2CppGenericContainer>(Header.genericContainersOffset, Header.genericContainersCount / Sizeof(typeof(Il2CppGenericContainer)));
GenericParameters = ReadArray<Il2CppGenericParameter>(Header.genericParametersOffset, Header.genericParametersCount / Sizeof(typeof(Il2CppGenericParameter)));
GenericConstraintIndices = ReadArray<int>(Header.genericParameterConstraintsOffset, Header.genericParameterConstraintsCount / sizeof(int));
InterfaceOffsets = ReadArray<Il2CppInterfaceOffsetPair>(Header.interfaceOffsetsOffset, Header.interfaceOffsetsCount / Sizeof(typeof(Il2CppInterfaceOffsetPair)));
VTableMethodIndices = ReadArray<uint>(Header.vtableMethodsOffset, Header.vtableMethodsCount / sizeof(uint));
if (Version >= 16) {
Assemblies = ReadArray<Il2CppAssemblyDefinition>(Header.assembliesOffset, Header.assembliesCount / Sizeof(typeof(Il2CppAssemblyDefinition)));
ParameterDefaultValues = ReadArray<Il2CppParameterDefaultValue>(Header.parameterDefaultValuesOffset, Header.parameterDefaultValuesCount / Sizeof(typeof(Il2CppParameterDefaultValue)));
}
if (Version >= 19) {
MetadataUsageLists = ReadArray<Il2CppMetadataUsageList>(Header.metadataUsageListsOffset, Header.metadataUsageListsCount / Sizeof(typeof(Il2CppMetadataUsageList)));
MetadataUsagePairs = ReadArray<Il2CppMetadataUsagePair>(Header.metadataUsagePairsOffset, Header.metadataUsagePairsCount / Sizeof(typeof(Il2CppMetadataUsagePair)));
FieldRefs = ReadArray<Il2CppFieldRef>(Header.fieldRefsOffset, Header.fieldRefsCount / Sizeof(typeof(Il2CppFieldRef)));
}
if (Version >= 21) {
AttributeTypeIndices = ReadArray<int>(Header.attributeTypesOffset, Header.attributeTypesCount / sizeof(int));
AttributeTypeRanges = ReadArray<Il2CppCustomAttributeTypeRange>(Header.attributesInfoOffset, Header.attributesInfoCount / Sizeof(typeof(Il2CppCustomAttributeTypeRange)));
}
// Get all metadata string literals
Position = Header.stringOffset;
while (Position < Header.stringOffset + Header.stringCount)
Strings.Add((int)Position - Header.stringOffset, ReadNullTerminatedString());
// Get all managed code string literals
var stringLiteralList = ReadArray<Il2CppStringLiteral>(Header.stringLiteralOffset, Header.stringLiteralCount / Sizeof(typeof(Il2CppStringLiteral)));
StringLiterals = new string[stringLiteralList.Length];
for (var i = 0; i < stringLiteralList.Length; i++)
StringLiterals[i] = ReadFixedLengthString(Header.stringLiteralDataOffset + stringLiteralList[i].dataIndex, stringLiteralList[i].length);
}
private int Sizeof(Type type)
{
int size = 0;
foreach (var i in type.GetTypeInfo().GetFields())
{
// Only process fields for our selected object versioning
var versionAttr = i.GetCustomAttribute<VersionAttribute>(false);
if (versionAttr != null) {
if (versionAttr.Min != -1 && versionAttr.Min > Version)
continue;
if (versionAttr.Max != -1 && versionAttr.Max < Version)
continue;
}
if (i.FieldType == typeof(int) || i.FieldType == typeof(uint))
size += 4;
else if (i.FieldType == typeof(short) || i.FieldType == typeof(ushort))
size += 2;
// Fixed-length array
else if (i.FieldType.IsArray) {
var attr = i.GetCustomAttribute<ArrayLengthAttribute>(false) ??
throw new InvalidOperationException("Array field " + i.Name + " must have ArrayLength attribute");
size += attr.FixedSize;
}
// Embedded object
else
size += Sizeof(i.FieldType);
}
return size;
}
}
}

View File

@@ -0,0 +1,448 @@
/*
Copyright 2017 Perfare - https://github.com/Perfare/Il2CppDumper
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using NoisyCowStudios.Bin2Object;
namespace Il2CppInspector
{
// Unity 4.6.1p5 - first release, no global-metadata.dat
// Unity 5.2.0f3 -> v15
// Unity 5.3.0f4 -> v16
// Unity 5.3.1f1 -> v16
// Unity 5.3.2f1 -> v19
// Unity 5.3.3f1 -> v20
// Unity 5.3.4f1 -> v20
// Unity 5.3.5f1 -> v21
// Unity 5.4.0f3 -> v21
// Unity 5.5.0f3 -> v22
// Unity 5.6.2p3 -> v23
// Unity 5.6.4f1 -> v23
// Unity 2017.2f3 -> v24
// Unity 2018.2.0f2 -> v24
// Unity 2018.3.0f2 -> v24.1
// Unity 2019.2.8f1 -> v24.2
// https://unity3d.com/get-unity/download/archive
// Metadata version is written at the end of Unity.IL2CPP.MetadataCacheWriter.WriteLibIl2CppMetadata or WriteMetadata (Unity.IL2CPP.dll)
// From il2cpp-metadata.h
#pragma warning disable CS0649
public class Il2CppGlobalMetadataHeader
{
public uint sanity;
public int version;
public int stringLiteralOffset; // string data for managed code
public int stringLiteralCount;
public int stringLiteralDataOffset;
public int stringLiteralDataCount;
public int stringOffset; // string data for metadata
public int stringCount;
public int eventsOffset; // Il2CppEventDefinition
public int eventsCount;
public int propertiesOffset; // Il2CppPropertyDefinition
public int propertiesCount;
public int methodsOffset; // Il2CppMethodDefinition
public int methodsCount;
[Version(Min = 16)]
public int parameterDefaultValuesOffset; // Il2CppParameterDefaultValue
[Version(Min = 16)]
public int parameterDefaultValuesCount;
public int fieldDefaultValuesOffset; // Il2CppFieldDefaultValue
public int fieldDefaultValuesCount;
public int fieldAndParameterDefaultValueDataOffset; // uint8_t
public int fieldAndParameterDefaultValueDataCount;
[Version(Min = 16)]
public int fieldMarshaledSizesOffset; // Il2CppFieldMarshaledSize
[Version(Min = 16)]
public int fieldMarshaledSizesCount;
public int parametersOffset; // Il2CppParameterDefinition
public int parametersCount;
public int fieldsOffset; // Il2CppFieldDefinition
public int fieldsCount;
public int genericParametersOffset; // Il2CppGenericParameter
public int genericParametersCount;
public int genericParameterConstraintsOffset; // TypeIndex
public int genericParameterConstraintsCount;
public int genericContainersOffset; // Il2CppGenericContainer
public int genericContainersCount;
public int nestedTypesOffset; // TypeDefinitionIndex
public int nestedTypesCount;
public int interfacesOffset; // TypeIndex
public int interfacesCount;
public int vtableMethodsOffset; // EncodedMethodIndex
public int vtableMethodsCount;
public int interfaceOffsetsOffset; // Il2CppInterfaceOffsetPair
public int interfaceOffsetsCount;
public int typeDefinitionsOffset; // Il2CppTypeDefinition
public int typeDefinitionsCount;
[Version(Max = 24.1)]
public int rgctxEntriesOffset; // Il2CppRGCTXDefinition
[Version(Max = 24.1)]
public int rgctxEntriesCount;
[Version(Min = 16)]
public int imagesOffset; // Il2CppImageDefinition
[Version(Min = 16)]
public int imagesCount;
[Version(Min = 16)]
public int assembliesOffset; // Il2CppAssemblyDefinition
[Version(Min = 16)]
public int assembliesCount;
[Version(Min = 19)]
public int metadataUsageListsOffset; // Il2CppMetadataUsageList
[Version(Min = 19)]
public int metadataUsageListsCount;
[Version(Min = 19)]
public int metadataUsagePairsOffset; // Il2CppMetadataUsagePair
[Version(Min = 19)]
public int metadataUsagePairsCount;
[Version(Min = 19)]
public int fieldRefsOffset; // Il2CppFieldRef
[Version(Min = 19)]
public int fieldRefsCount;
[Version(Min = 19)]
public int referencedAssembliesOffset; // int
[Version(Min = 19)]
public int referencedAssembliesCount;
[Version(Min = 21)]
public int attributesInfoOffset; // Il2CppCustomAttributeTypeRange
[Version(Min = 21)]
public int attributesInfoCount;
[Version(Min = 21)]
public int attributeTypesOffset; // TypeIndex
[Version(Min = 21)]
public int attributeTypesCount;
// Added in metadata v22
[Version(Min = 22)]
public int unresolvedVirtualCallParameterTypesOffset; // TypeIndex
[Version(Min = 22)]
public int unresolvedVirtualCallParameterTypesCount;
[Version(Min = 22)]
public int unresolvedVirtualCallParameterRangesOffset; // Il2CppRange
[Version(Min = 22)]
public int unresolvedVirtualCallParameterRangesCount;
// Added in metadata v23
[Version(Min = 23)]
public int windowsRuntimeTypeNamesOffset; // Il2CppWindowsRuntimeTypeNamePair
[Version(Min = 23)]
public int windowsRuntimeTypeNamesSize;
// Added in metadata v24
[Version(Min = 24)]
public int exportedTypeDefinitionsOffset; // TypeDefinitionIndex
[Version(Min = 24)]
public int exportedTypeDefinitionsCount;
}
public class Il2CppImageDefinition
{
public int nameIndex;
public int assemblyIndex;
public int typeStart;
public uint typeCount;
[Version(Min = 24)]
public int exportedTypeStart;
[Version(Min = 24)]
public uint exportedTypeCount;
public int entryPointIndex;
[Version(Min = 19)]
public uint token;
[Version(Min = 24.1)]
public int customAttributeStart;
[Version(Min = 24.1)]
public uint customAttributeCount;
}
#pragma warning restore CS0649
// Renamed from Il2CppAssembly somewhere after Unity 2017.2f3 up to Unity 2018.2.0f2
public class Il2CppAssemblyDefinition
{
// They moved the position of aname in v16 from the top to the bottom of the struct
public Il2CppAssemblyNameDefinition aname => aname_pre16 ?? aname_post16;
[Version(Max = 15)]
public Il2CppAssemblyNameDefinition aname_pre16;
public int imageIndex;
[Version(Min = 24.1)]
public uint token;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 20)]
public int referencedAssemblyStart;
[Version(Min = 20)]
public int referencedAssemblyCount;
[Version(Min = 16)]
public Il2CppAssemblyNameDefinition aname_post16;
}
// Renamed from Il2CppAssemblyName somewhere after Unity 2017.2f3 up to Unity 2018.2.0f2
public class Il2CppAssemblyNameDefinition
{
// They moved the position of publicKeyToken in v16 from the middle to the bottom of the struct
public byte[] publicKeyToken => publicKeyToken_post16;
public int nameIndex;
public int cultureIndex;
public int hashValueIndex;
public int publicKeyIndex;
[Version(Max = 15), ArrayLength(FixedSize = 8)]
public byte[] publicKeyToken_pre16;
public uint hash_alg;
public int hash_len;
public uint flags;
public int major;
public int minor;
public int build;
public int revision;
[Version(Min = 16), ArrayLength(FixedSize = 8)]
public byte[] publicKeyToken_post16;
}
public class Il2CppTypeDefinition
{
public int nameIndex;
public int namespaceIndex;
// Removed in metadata v24.1
[Version(Max = 24.0)]
public int customAttributeIndex;
public int byvalTypeIndex;
public int byrefTypeIndex;
public int declaringTypeIndex;
public int parentIndex;
public int elementTypeIndex; // we can probably remove this one. Only used for enums
[Version(Max = 24.1)]
public int rgctxStartIndex;
[Version(Max = 24.1)]
public int rgctxCount;
public int genericContainerIndex;
// Removed in metadata v23
[Version(Max = 22)]
public int delegateWrapperFromManagedToNativeIndex; // (was renamed to reversePInvokeWrapperIndex in v22)
[Version(Max = 22)]
public int marshalingFunctionsIndex;
[Version(Min = 21, Max = 22)]
public int ccwFunctionIndex;
[Version(Min = 21, Max = 22)]
public int guidIndex;
public uint flags;
public int fieldStart;
public int methodStart;
public int eventStart;
public int propertyStart;
public int nestedTypesStart;
public int interfacesStart;
public int vtableStart;
public int interfaceOffsetsStart;
public ushort method_count;
public ushort property_count;
public ushort field_count;
public ushort event_count;
public ushort nested_type_count;
public ushort vtable_count;
public ushort interfaces_count;
public ushort interface_offsets_count;
// bitfield to portably encode boolean values as single bits
// 01 - valuetype;
// 02 - enumtype;
// 03 - has_finalize;
// 04 - has_cctor;
// 05 - is_blittable;
// 06 - is_import; (from v22: is_import_or_windows_runtime)
// 07-10 - One of nine possible PackingSize values (0, 1, 2, 4, 8, 16, 32, 64, or 128)
public uint bitfield;
[Version(Min = 19)]
public uint token;
}
public class Il2CppMethodDefinition
{
public int nameIndex;
[Version(Min = 16)]
public int declaringType;
public int returnType;
public int parameterStart;
[Version(Max = 24.0)]
public int customAttributeIndex;
public int genericContainerIndex;
[Version(Max = 24.1)]
public int methodIndex;
[Version(Max = 24.1)]
public int invokerIndex;
[Version(Max = 24.1)]
public int reversePInvokeWrapperIndex; // (was renamed from delegateWrapperIndex in v22)
[Version(Max = 24.1)]
public int rgctxStartIndex;
[Version(Max = 24.1)]
public int rgctxCount;
public uint token;
public ushort flags;
public ushort iflags;
public ushort slot;
public ushort parameterCount;
}
public class Il2CppParameterDefinition
{
public int nameIndex;
public uint token;
[Version(Max = 24.0)]
public int customAttributeIndex;
public int typeIndex;
}
public class Il2CppParameterDefaultValue
{
public int parameterIndex;
public int typeIndex;
public int dataIndex;
}
public class Il2CppFieldDefinition
{
public int nameIndex;
public int typeIndex;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 19)]
public uint token;
}
public class Il2CppFieldDefaultValue
{
public int fieldIndex;
public int typeIndex;
public int dataIndex;
}
public class Il2CppPropertyDefinition
{
public int nameIndex;
public int get;
public int set;
public uint attrs;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 19)]
public uint token;
}
public class Il2CppEventDefinition
{
public int nameIndex;
public int typeIndex;
public int add;
public int remove;
public int raise;
[Version(Max = 24.0)]
public int customAttributeIndex;
[Version(Min = 19)]
public uint token;
}
public class Il2CppGenericContainer
{
/* index of the generic type definition or the generic method definition corresponding to this container */
public int ownerIndex; // either index into Il2CppClass metadata array or Il2CppMethodDefinition array
public int type_argc;
/* If true, we're a generic method, otherwise a generic type definition. */
public int is_method;
/* Our type parameters. */
public uint genericParameterStart; // GenericParameterIndex
}
public class Il2CppGenericParameter
{
public int ownerIndex; /* Type or method this parameter was defined in. */ // GenericContainerIndex
public int nameIndex; // StringIndex
public short constraintsStart; // GenericParameterConstraintIndex
public short constraintsCount;
public ushort num; // Generic parameter position
public ushort flags; // GenericParameterAttributes
}
public class Il2CppCustomAttributeTypeRange
{
[Version(Min = 24.1)]
public uint token;
public int start;
public int count;
}
public class Il2CppInterfaceOffsetPair
{
public int interfaceTypeIndex;
public int offset;
}
public class Il2CppMetadataUsageList
{
public uint start;
public uint count;
}
public class Il2CppMetadataUsagePair
{
public uint destinationindex;
public uint encodedSourceIndex;
}
public class Il2CppStringLiteral
{
public int length;
public int dataIndex;
}
public class Il2CppFieldRef
{
public int typeIndex;
public int fieldIndex; // local offset into type fields
}
}

View File

@@ -0,0 +1,35 @@
/*
Copyright (c) 2019-2020 Carter Bush - https://github.com/carterbush
Copyright (c) 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
namespace Il2CppInspector
{
public enum MetadataUsageType
{
TypeInfo = 1,
Type = 2,
MethodDef = 3,
FieldInfo = 4,
StringLiteral = 5,
MethodRef = 6,
}
public class MetadataUsage
{
public MetadataUsageType Type { get; }
public int SourceIndex { get; }
public int DestinationIndex { get; }
public ulong VirtualAddress { get; private set; }
public MetadataUsage(MetadataUsageType type, int sourceIndex, int destinationIndex) {
Type = type;
SourceIndex = sourceIndex;
DestinationIndex = destinationIndex;
}
public void SetAddress(ulong virtualAddress) => VirtualAddress = virtualAddress;
}
}

View File

@@ -0,0 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>Il2CppInspector.Common</AssemblyName>
<Authors>Katy Coe</Authors>
<Version>2.0</Version>
<Company>Noisy Cow Studios</Company>
<Product>Il2CppInspector Shared Library</Product>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Bin2Object\Bin2Object\Bin2Object.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,664 @@
// Copyright (c) 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
// All rights reserved
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Il2CppInspector.Properties;
using Il2CppInspector.Reflection;
using Assembly = Il2CppInspector.Reflection.Assembly;
using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData;
using MethodInfo = Il2CppInspector.Reflection.MethodInfo;
using TypeInfo = Il2CppInspector.Reflection.TypeInfo;
namespace Il2CppInspector.Outputs
{
public class CSharpCodeStubs
{
private readonly Il2CppModel model;
// Namespace prefixes whose contents should be skipped
public List<string> ExcludedNamespaces { get; set; }
// Make adjustments to ensure that the generated code compiles
public bool MustCompile { get; set; }
// Suppress binary metadata in code comments
public bool SuppressMetadata { get; set; }
private const string CGAttribute = "System.Runtime.CompilerServices.CompilerGeneratedAttribute";
private const string FBAttribute = "System.Runtime.CompilerServices.FixedBufferAttribute";
private const string ExtAttribute = "System.Runtime.CompilerServices.ExtensionAttribute";
private const string DMAttribute = "System.Reflection.DefaultMemberAttribute";
// Assembly attributes we have already emitted
private HashSet<CustomAttributeData> usedAssemblyAttributes = new HashSet<CustomAttributeData>();
private readonly object usedAssemblyAttributesLock = new object();
public CSharpCodeStubs(Il2CppModel model) => this.model = model;
public void WriteSingleFile(string outFile) => WriteSingleFile(outFile, t => t.Index);
public void WriteSingleFile<TKey>(string outFile, Func<TypeInfo, TKey> orderBy) {
usedAssemblyAttributes.Clear();
writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy));
}
public void WriteFilesByNamespace<TKey>(string outPath, Func<TypeInfo, TKey> orderBy, bool flattenHierarchy) {
usedAssemblyAttributes.Clear();
Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace), ns => {
writeFile($"{outPath}\\{(!string.IsNullOrEmpty(ns.Key) ? ns.Key : "global").Replace('.', flattenHierarchy ? '.' : '\\')}.cs",
ns.OrderBy(orderBy));
});
}
public void WriteFilesByAssembly<TKey>(string outPath, Func<TypeInfo, TKey> orderBy, bool separateAttributes) {
usedAssemblyAttributes.Clear();
Parallel.ForEach(model.Assemblies, asm => {
// Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function
if (writeFile($"{outPath}\\{asm.ShortName.Replace(".dll", "")}.cs", asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy), outputAssemblyAttributes: !separateAttributes)
&& separateAttributes) {
File.WriteAllText($"{outPath}\\AssemblyInfo_{asm.ShortName.Replace(".dll", "")}.cs", generateAssemblyInfo(new [] {asm}));
}
});
}
public void WriteFilesByClass(string outPath, bool flattenHierarchy) {
usedAssemblyAttributes.Clear();
Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => {
writeFile($"{outPath}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", ""))
.Replace('.', flattenHierarchy ? '.' : '\\') + ".cs", new[] {type});
});
}
public HashSet<Assembly> WriteFilesByClassTree(string outPath, bool separateAttributes) {
usedAssemblyAttributes.Clear();
var usedAssemblies = new HashSet<Assembly>();
// Each thread tracks its own list of used assemblies and they are merged as each thread completes
Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes),
() => new HashSet<Assembly>(),
(type, _, used) => {
if (writeFile($"{outPath}\\{type.Assembly.ShortName.Replace(".dll", "")}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", ""))
.Replace('.', '\\') + ".cs", new[] {type}, outputAssemblyAttributes: !separateAttributes))
used.Add(type.Assembly);
return used;
},
usedPartition => usedAssemblies.UnionWith(usedPartition)
);
if (separateAttributes && usedAssemblies.Any())
foreach (var asm in usedAssemblies)
File.WriteAllText($"{outPath}\\{asm.ShortName.Replace(".dll", "")}\\AssemblyInfo.cs", generateAssemblyInfo(new [] {asm}));
return usedAssemblies;
}
// Create a Visual Studio solution
public void WriteSolution(string outPath, string unityPath, string unityAssembliesPath) {
// Required settings
MustCompile = true;
// Output source files in tree format with separate assembly attributes
var assemblies = WriteFilesByClassTree(outPath, true);
// Per-project (per-assembly) solution definition and configuration
var slnProjectDefs = new StringBuilder();
var slnProjectConfigs = new StringBuilder();
foreach (var asm in assemblies) {
var guid = Guid.NewGuid();
var name = asm.ShortName.Replace(".dll", "");
var csProjFile = $"{name}\\{name}.csproj";
var def = Resources.SlnProjectDefinition
.Replace("%PROJECTGUID%", guid.ToString())
.Replace("%PROJECTNAME%", name)
.Replace("%CSPROJRELATIVEPATH%", csProjFile);
slnProjectDefs.Append(def);
var config = Resources.SlnProjectConfiguration
.Replace("%PROJECTGUID%", guid.ToString());
slnProjectConfigs.Append(config);
// Determine all the assemblies on which this assembly depends
var dependencyTypes = asm.DefinedTypes.SelectMany(t => t.GetAllTypeReferences())
.Union(asm.CustomAttributes.SelectMany(a => a.AttributeType.GetAllTypeReferences()))
.Distinct();
var dependencyAssemblies = dependencyTypes.Select(t => t.Assembly).Distinct()
.Except(new[] {asm});
// Only create project references to those assemblies actually output in our solution
dependencyAssemblies = dependencyAssemblies.Intersect(assemblies);
var referenceXml = string.Concat(dependencyAssemblies.Select(
a => $@" <ProjectReference Include=""..\{a.ShortName.Replace(".dll", "")}\{a.ShortName.Replace(".dll", "")}.csproj""/>" + "\n"
));
// Create a .csproj file using the project Guid
var csProj = Resources.CsProjTemplate
.Replace("%PROJECTGUID%", guid.ToString())
.Replace("%ASSEMBLYNAME%", name)
.Replace("%UNITYPATH%", unityPath)
.Replace("%SCRIPTASSEMBLIES%", unityAssembliesPath)
.Replace("%PROJECTREFERENCES%", referenceXml);
File.WriteAllText($"{outPath}\\{csProjFile}", csProj);
}
// Merge everything into .sln file
var sln = Resources.SlnTemplate
.Replace("%PROJECTDEFINITIONS%", slnProjectDefs.ToString())
.Replace("%PROJECTCONFIGURATIONS%", slnProjectConfigs.ToString());
var filename = Path.GetFileName(outPath);
if (filename == "")
filename = "Il2CppProject";
File.WriteAllText($"{outPath}\\{filename}.sln", sln);
}
private bool writeFile(string outFile, IEnumerable<TypeInfo> types, bool useNamespaceSyntax = true, bool outputAssemblyAttributes = true) {
var nsRefs = new HashSet<string>();
var code = new StringBuilder();
var nsContext = "";
var usedTypes = new List<TypeInfo>();
// Determine all namespace references (note: this may include some that aren't actually used due to output suppression in generateType()
// We have to do this first so that we can generate properly scoped type references in the code
foreach (var type in types) {
var refs = type.GetAllTypeReferences();
var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace);
nsRefs.UnionWith(ns);
}
// Determine assemblies used in this file
var assemblies = types.Select(t => t.Assembly).Distinct();
// Add assembly attribute namespaces to reference list
if (outputAssemblyAttributes)
nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace));
// Generate each type
foreach (var type in types) {
// Skip namespace and any children if requested
if (ExcludedNamespaces?.Any(x => x == type.Namespace || type.Namespace.StartsWith(x + ".")) ?? false)
continue;
// Don't output global::Locale if desired
if (MustCompile
&& type.Name == "Locale" && type.Namespace == string.Empty
&& type.BaseType.FullName == "System.Object"
&& type.IsClass && type.IsSealed && type.IsNotPublic && !type.ContainsGenericParameters
&& type.DeclaredMembers.Count == type.DeclaredMethods.Count
&& type.GetMethods("GetText").Length == type.DeclaredMethods.Count)
continue;
// Assembly.DefinedTypes returns nested types in the assembly by design - ignore them
if (type.IsNested)
continue;
// Get code
var text = generateType(type, nsRefs);
if (string.IsNullOrEmpty(text))
continue;
// Determine if we need to change namespace (after we have established the code block is not empty)
if (useNamespaceSyntax) {
if (type.Namespace != nsContext) {
if (!string.IsNullOrEmpty(nsContext))
code.Remove(code.Length - 1, 1).Append("}\n\n");
if (!string.IsNullOrEmpty(type.Namespace))
code.Append("namespace " + type.Namespace + "\n{\n");
nsContext = type.Namespace;
}
if (!string.IsNullOrEmpty(nsContext)) {
text = "\t" + text.Replace("\n", "\n\t");
text = text.Remove(text.Length - 1);
}
}
// Append namespace
if (!useNamespaceSyntax)
code.Append($"// Namespace: {(!string.IsNullOrEmpty(type.Namespace) ? type.Namespace : "<global namespace>")}\n");
// Append type definition
code.Append(text + "\n");
// Add to list of used types
usedTypes.Add(type);
}
// Stop if nothing to output
if (!usedTypes.Any())
return false;
// Close namespace
if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext))
code.Remove(code.Length - 1, 1).Append("}\n");
// Determine using directives (put System namespaces first)
nsRefs.Clear();
foreach (var type in usedTypes) {
var refs = type.GetAllTypeReferences();
var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace);
nsRefs.UnionWith(ns);
}
nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace));
var usings = nsRefs.OrderBy(n => (n.StartsWith("System.") || n == "System") ? "0" + n : "1" + n);
// Ensure output directory exists
if (!string.IsNullOrEmpty(Path.GetDirectoryName(outFile)))
Directory.CreateDirectory(Path.GetDirectoryName(outFile));
// Create output file
bool fileWritten = false;
do {
try {
using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8);
// Write preamble
writer.Write(@"/*
* Generated code file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty
*/
");
// Output using directives
writer.Write(string.Concat(usings.Select(n => $"using {n};\n")));
if (nsRefs.Any())
writer.Write("\n");
// Output assembly information and attributes
writer.Write(generateAssemblyInfo(assemblies, nsRefs, outputAssemblyAttributes) + "\n\n");
// Output type definitions
writer.Write(code);
fileWritten = true;
}
catch (IOException ex) {
// If we get "file is in use by another process", we are probably writing a duplicate class in another thread
// Wait a bit and try again
if ((uint) ex.HResult != 0x80070020)
throw;
System.Threading.Thread.Sleep(100);
}
} while (!fileWritten);
return true;
}
private string generateAssemblyInfo(IEnumerable<Reflection.Assembly> assemblies, IEnumerable<string> namespaces = null, bool outputAssemblyAttributes = true) {
var text = new StringBuilder();
foreach (var asm in assemblies) {
text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName} - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}\n");
// Assembly-level attributes
if (outputAssemblyAttributes)
lock (usedAssemblyAttributesLock) {
text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute)
.Except(usedAssemblyAttributes ?? new HashSet<CustomAttributeData>())
.OrderBy(a => a.AttributeType.Name)
.ToString(new Scope { Current = null, Namespaces = namespaces ?? new List<string>() }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
if (asm.CustomAttributes.Any())
text.Append("\n");
usedAssemblyAttributes.UnionWith(asm.CustomAttributes);
}
}
return text.ToString().TrimEnd();
}
private string generateType(TypeInfo type, IEnumerable<string> namespaces, string prefix = "") {
// Don't output compiler-generated types if desired
if (MustCompile && type.GetCustomAttributes(CGAttribute).Any())
return string.Empty;
var codeBlocks = new Dictionary<string, string>();
var usedMethods = new List<MethodInfo>();
var sb = new StringBuilder();
var scope = new Scope {
Current = type,
Namespaces = namespaces
};
// Fields
sb.Clear();
if (!type.IsEnum) {
foreach (var field in type.DeclaredFields) {
if (MustCompile && field.GetCustomAttributes(CGAttribute).Any())
continue;
if (field.IsNotSerialized)
sb.Append(prefix + "\t[NonSerialized]\n");
// Attributes
sb.Append(field.CustomAttributes.Where(a => a.AttributeType.FullName != FBAttribute).OrderBy(a => a.AttributeType.Name)
.ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
sb.Append(prefix + "\t");
sb.Append(field.GetModifierString());
// Fixed buffers
if (field.GetCustomAttributes(FBAttribute).Any()) {
if (!SuppressMetadata)
sb.Append($"/* {field.GetCustomAttributes(FBAttribute)[0].VirtualAddress.ToAddressString()} */ ");
sb.Append($"{field.FieldType.DeclaredFields[0].FieldType.GetScopedCSharpName(scope)} {field.CSharpSafeName}[0]"); // FixedElementField
}
// Regular fields
else
sb.Append($"{field.FieldType.GetScopedCSharpName(scope)} {field.CSharpSafeName}");
if (field.HasDefaultValue)
sb.Append($" = {field.DefaultValueString}");
sb.Append(";");
// Don't output field indices for const fields (they don't have any storage)
if (!field.IsLiteral && !SuppressMetadata)
sb.Append($" // 0x{(uint) field.Offset:X2}");
// Output metadata file offset for const fields
if (field.IsLiteral && !SuppressMetadata)
sb.Append($" // Metadata: 0x{(uint) field.DefaultValueMetadataAddress:X8}");
sb.Append("\n");
}
codeBlocks.Add("Fields", sb.ToString());
}
// Properties
sb.Clear();
foreach (var prop in type.DeclaredProperties) {
// Attributes
sb.Append(prop.CustomAttributes.OrderBy(a => a.AttributeType.Name)
.ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
// The access mask enum values go from 1 (private) to 6 (public) in order from most to least restrictive
var getAccess = (prop.GetMethod?.Attributes ?? 0) & MethodAttributes.MemberAccessMask;
var setAccess = (prop.SetMethod?.Attributes ?? 0) & MethodAttributes.MemberAccessMask;
// In case the access level of both is the same and the selected method is null, pick the other one (rare edge case)
var primary = (getAccess >= setAccess ? prop.GetMethod : prop.SetMethod) ?? prop.GetMethod ?? prop.SetMethod;
sb.Append($"{prefix}\t{primary.GetModifierString()}{prop.PropertyType.GetScopedCSharpName(scope)} ");
// Non-indexer; non-auto-properties should have a body
var needsBody = MustCompile && !type.IsInterface && !type.IsAbstract && !prop.IsAutoProperty;
var getBody = needsBody? " => default;" : ";";
var setBody = needsBody? " {}" : ";";
if ((!prop.CanRead || !prop.GetMethod.DeclaredParameters.Any()) && (!prop.CanWrite || prop.SetMethod.DeclaredParameters.Count == 1))
sb.Append($"{prop.CSharpName} {{ ");
// Indexer
else {
// Replace "Item" with "this" - preserves explicit interface implementations
sb.Append(prop.CSharpName[..^4] + "this");
sb.Append("[" + string.Join(", ", primary.DeclaredParameters.SkipLast(getAccess >= setAccess ? 0 : 1)
.Select(p => p.GetParameterString(scope, !SuppressMetadata, MustCompile))) + "] { ");
getBody = " => default;";
setBody = " {}";
}
sb.Append((prop.CanRead? prop.GetMethod.CustomAttributes.Where(a => !MustCompile || a.AttributeType.FullName != CGAttribute)
.ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: MustCompile)
+ (getAccess < setAccess? prop.GetMethod.GetAccessModifierString() : "") + $"get{getBody} " : "")
// Auto-properties must have get accessors (exclude indexers)
+ (MustCompile && !prop.CanRead && setBody == ";"? "get; " : "")
+ (prop.CanWrite? prop.SetMethod.CustomAttributes.Where(a => !MustCompile || a.AttributeType.FullName != CGAttribute)
.ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: MustCompile)
+ (setAccess < getAccess? prop.SetMethod.GetAccessModifierString() : "") + $"set{setBody} " : "") + "}");
if (!SuppressMetadata) {
if ((prop.CanRead && prop.GetMethod.VirtualAddress != null) || (prop.CanWrite && prop.SetMethod.VirtualAddress != null))
sb.Append(" // ");
sb.Append((prop.CanRead && prop.GetMethod.VirtualAddress != null ? prop.GetMethod.VirtualAddress.ToAddressString() + " " : "")
+ (prop.CanWrite && prop.SetMethod.VirtualAddress != null ? prop.SetMethod.VirtualAddress.ToAddressString() : ""));
}
sb.Append("\n");
usedMethods.Add(prop.GetMethod);
usedMethods.Add(prop.SetMethod);
}
codeBlocks.Add("Properties", sb.ToString());
// Events
sb.Clear();
foreach (var evt in type.DeclaredEvents) {
// Attributes
sb.Append(evt.CustomAttributes.OrderBy(a => a.AttributeType.Name)
.ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
string modifiers = evt.AddMethod?.GetModifierString();
sb.Append($"{prefix}\t{modifiers}event {evt.EventHandlerType.GetScopedCSharpName(scope)} {evt.CSharpSafeName}");
if (!MustCompile) {
sb.Append(" {{\n");
var m = new Dictionary<string, (ulong, ulong)?>();
if (evt.AddMethod != null) m.Add("add", evt.AddMethod.VirtualAddress);
if (evt.RemoveMethod != null) m.Add("remove", evt.RemoveMethod.VirtualAddress);
if (evt.RaiseMethod != null) m.Add("raise", evt.RaiseMethod.VirtualAddress);
sb.Append(string.Join("\n", m.Select(x => $"{prefix}\t\t{x.Key};{(SuppressMetadata? "" : " // " + x.Value.ToAddressString())}")) + "\n" + prefix + "\t}\n");
} else
sb.Append(";\n");
usedMethods.Add(evt.AddMethod);
usedMethods.Add(evt.RemoveMethod);
usedMethods.Add(evt.RaiseMethod);
}
codeBlocks.Add("Events", sb.ToString());
// Nested types
codeBlocks.Add("Nested types", string.Join("\n", type.DeclaredNestedTypes
.Select(n => generateType(n, namespaces, prefix + "\t")).Where(c => !string.IsNullOrEmpty(c))));
// Constructors
var fields = type.DeclaredFields.Where(f => !f.GetCustomAttributes(CGAttribute).Any());
sb.Clear();
// Crete a parameterless constructor for every relevant type when making code that compiles to mitigate CS1729 and CS7036
if (MustCompile && !type.IsInterface && !(type.IsAbstract && type.IsSealed) && !type.IsValueType
&& type.DeclaredConstructors.All(c => c.IsStatic || c.DeclaredParameters.Any()))
sb.Append($"{prefix}\t{(type.IsAbstract? "protected" : "public")} {type.UnmangledBaseName}() {{}} // Dummy constructor\n");
foreach (var method in type.DeclaredConstructors) {
// Attributes
sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name)
.ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
sb.Append($"{prefix}\t{method.GetModifierString()}{method.DeclaringType.UnmangledBaseName}{method.GetTypeParametersString(scope)}");
sb.Append($"({method.GetParametersString(scope, !SuppressMetadata)})");
if (MustCompile) {
// Class constructor
if (method.IsAbstract)
sb.Append(";");
else if (!type.IsValueType)
sb.Append(" {}");
// Struct constructor
else {
// Parameterized struct constructors must call the parameterless constructor to create the object
// if the object has any auto-implemented properties
if (type.DeclaredProperties.Any() && method.DeclaredParameters.Any())
sb.Append(" : this()");
// Struct construvctors must initialize all fields in the struct
if (fields.Any()) {
var paramNames = method.DeclaredParameters.Select(p => p.Name);
sb.Append(" {\n" + string.Join("\n", fields
.Where(f => !f.IsStatic && !f.IsLiteral)
.Select(f => $"{prefix}\t\t{(paramNames.Contains(f.Name) ? "this." : "")}{f.Name} = default;"))
+ $"\n{prefix}\t}}");
} else
sb.Append(" {}");
}
} else
sb.Append(";");
sb.Append((!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n");
}
codeBlocks.Add("Constructors", sb.ToString());
// Methods
// Don't re-output methods for constructors, properties, events etc.
var methods = type.DeclaredMethods.Except(usedMethods).Where(m => m.CustomAttributes.All(a => a.AttributeType.FullName != ExtAttribute));
codeBlocks.Add("Methods", string.Concat(methods.Select(m => generateMethod(m, scope, prefix))));
usedMethods.AddRange(methods);
// Extension methods
codeBlocks.Add("Extension methods", string.Concat(type.DeclaredMethods.Except(usedMethods).Select(m => generateMethod(m, scope, prefix))));
// Type declaration
sb.Clear();
if (type.IsImport)
sb.Append(prefix + "[ComImport]\n");
if (type.IsSerializable)
sb.Append(prefix + "[Serializable]\n");
// TODO: DefaultMemberAttribute should be output if it is present and the type does not have an indexer, otherwise suppressed
// See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.defaultmemberattribute?view=netframework-4.8
sb.Append(type.CustomAttributes.Where(a => a.AttributeType.FullName != DMAttribute && a.AttributeType.FullName != ExtAttribute)
.OrderBy(a => a.AttributeType.Name).ToString(scope, prefix, emitPointer: !SuppressMetadata, mustCompile: MustCompile));
// Roll-up multicast delegates to use the 'delegate' syntactic sugar
if (type.IsClass && type.IsSealed && type.BaseType?.FullName == "System.MulticastDelegate") {
sb.Append(prefix + type.GetAccessModifierString());
var del = type.GetMethod("Invoke");
// IL2CPP doesn't seem to retain return type attributes
//sb.Append(del.ReturnType.CustomAttributes.ToString(prefix, "return: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
if (del.RequiresUnsafeContext)
sb.Append("unsafe ");
sb.Append($"delegate {del.ReturnType.GetScopedCSharpName(scope)} {type.CSharpTypeDeclarationName}(");
sb.Append(del.GetParametersString(scope, !SuppressMetadata) + ");");
if (!SuppressMetadata)
sb.Append($" // TypeDefIndex: {type.Index}; {del.VirtualAddress.ToAddressString()}");
sb.Append("\n");
return sb.ToString();
}
sb.Append(prefix + type.GetModifierString());
// Inheriting from a base class or implementing an interface using a generic type which contains a nested type parameter
// inside the declared class must be referenced from outside the scope of the type being defined
var outerScope = new Scope {Current = scope.Current.DeclaringType, Namespaces = scope.Namespaces};
var @base = type.ImplementedInterfaces.Select(x => x.GetScopedCSharpName(outerScope)).ToList();
if (type.BaseType != null && type.BaseType.FullName != "System.Object" && type.BaseType.FullName != "System.ValueType" && !type.IsEnum)
@base.Insert(0, type.BaseType.GetScopedCSharpName(outerScope));
if (type.IsEnum && type.GetEnumUnderlyingType().FullName != "System.Int32") // enums derive from int by default
@base.Insert(0, type.GetEnumUnderlyingType().GetScopedCSharpName(outerScope));
var baseText = @base.Count > 0 ? " : " + string.Join(", ", @base) : string.Empty;
sb.Append($"{type.CSharpTypeDeclarationName}{baseText}");
if (!SuppressMetadata)
sb.Append($" // TypeDefIndex: {type.Index}");
sb.Append("\n");
foreach (var gp in type.GetGenericArguments()) {
var constraint = gp.GetTypeConstraintsString(scope);
if (constraint != string.Empty)
sb.Append($"{prefix}\t{constraint}\n");
}
sb.Append(prefix + "{\n");
// Enumeration
if (type.IsEnum) {
sb.Append(string.Join(",\n", type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(),
(k, v) => new { k, v }).OrderBy(x => x.v).Select(x => $"{prefix}\t{x.k} = {x.v}")) + "\n");
}
// Type definition
else
sb.Append(string.Join("\n", codeBlocks.Where(b => b.Value != string.Empty).Select(b => prefix + "\t// " + b.Key + "\n" + b.Value)));
sb.Append(prefix + "}\n");
return sb.ToString();
}
private string generateMethod(MethodInfo method, Scope scope, string prefix) {
if (MustCompile && method.GetCustomAttributes(CGAttribute).Any())
return string.Empty;
var writer = new StringBuilder();
// Attributes
writer.Append(method.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute).OrderBy(a => a.AttributeType.Name)
.ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
// IL2CPP doesn't seem to retain return type attributes
//writer.Append(method.ReturnType.CustomAttributes.ToString(prefix + "\t", "return: ", emitPointer: !SuppressMetadata));
writer.Append($"{prefix}\t{method.GetModifierString()}");
// Finalizers become destructors
if (method.Name == "Finalize" && method.IsVirtual && method.ReturnType.FullName == "System.Void" && method.IsFamily)
writer.Append("~" + method.DeclaringType.UnmangledBaseName);
// Regular method or operator overload
else if (method.Name != "op_Implicit" && method.Name != "op_Explicit")
writer.Append($"{method.ReturnParameter.GetReturnParameterString(scope)} {method.CSharpName}{method.GetTypeParametersString(scope)}");
// User-defined conversion operator
else
writer.Append($"{method.CSharpName}{method.ReturnType.GetScopedCSharpName(scope)}");
// Parameters
writer.Append("(" + method.GetParametersString(scope, !SuppressMetadata) + ")");
// Generic type constraints
foreach (var gp in method.GetGenericArguments()) {
var constraint = gp.GetTypeConstraintsString(scope);
if (constraint != string.Empty)
writer.Append($"\n{prefix}\t\t{constraint}");
}
// Body
var methodBody = MustCompile? method switch {
// Abstract method
{ IsAbstract: true } => ";",
// Extern method
{ Attributes: var a } when (a & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl => ";",
// Method with out parameters
{ DeclaredParameters: var d } when d.Any(p => p.IsOut) =>
" {\n" + string.Join("\n", d.Where(p => p.IsOut).Select(p => $"{prefix}\t\t{p.Name} = default;"))
+ (method.ReturnType.FullName != "System.Void"? $"\n{prefix}\t\treturn default;" : "")
+ $"\n{prefix}\t}}",
// No return type
{ ReturnType: var retType } when retType.FullName == "System.Void" => " {}",
// Ref return type
{ ReturnType: var retType } when retType.IsByRef => " => ref _refReturnTypeFor" + method.CSharpName + ";",
// Regular return type
_ => " => default;"
}
// Only make a method body if we are trying to compile the output
: ";";
writer.Append(methodBody + (!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n");
// Ref return type requires us to invent a field
if (MustCompile && method.ReturnType.IsByRef)
writer.Append($"{prefix}\tprivate {method.ReturnType.GetScopedCSharpName(scope)} _refReturnTypeFor{method.CSharpName}; // Dummy field\n");
return writer.ToString();
}
}
}

View File

@@ -0,0 +1,177 @@
// Copyright (c) 2019-2020 Carter Bush - https://github.com/carterbush
// Copyright (c) 2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
// All rights reserved
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Text;
using Il2CppInspector.Reflection;
namespace Il2CppInspector.Outputs
{
public class IDAPythonScript
{
private readonly Il2CppModel model;
private StreamWriter writer;
public IDAPythonScript(Il2CppModel model) => this.model = model;
public void WriteScriptToFile(string outputFile) {
using var fs = new FileStream(outputFile, FileMode.Create);
writer = new StreamWriter(fs, Encoding.UTF8);
writeLine("# Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty");
writeLine("print('Generated script file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty')");
writeSectionHeader("Preamble");
writePreamble();
writeMethods();
writeSectionHeader("Metadata Usages");
writeUsages();
writeSectionHeader("Function boundaries");
writeFunctions();
writeSectionHeader("IL2CPP Metadata");
writeMetadata();
writeLine("print('Script execution complete.')");
writer.Close();
}
private void writePreamble() {
writeLine(
@"import idaapi
def SetString(addr, comm):
name = 'StringLiteral_' + str(addr)
ret = idc.set_name(addr, name, SN_NOWARN)
idc.set_cmt(addr, comm, 1)
def SetName(addr, name):
ret = idc.set_name(addr, name, SN_NOWARN | SN_NOCHECK)
if ret == 0:
new_name = name + '_' + str(addr)
ret = idc.set_name(addr, new_name, SN_NOWARN | SN_NOCHECK)
def MakeFunction(start, end):
next_func = idc.get_next_func(start)
if next_func < end:
end = next_func
current_func = idaapi.get_func(start)
if current_func is not None and current_func.startEA == start:
ida_funcs.del_func(start)
ida_funcs.add_func(start, end)"
);
}
private void writeMethods() {
writeSectionHeader("Method definitions");
foreach (var type in model.Types) {
writeMethods(type.Name, type.DeclaredConstructors);
writeMethods(type.Name, type.DeclaredMethods);
}
writeSectionHeader("Constructed generic methods");
foreach (var method in model.GenericMethods.Values.Where(m => m.VirtualAddress.HasValue)) {
var address = method.VirtualAddress.Value.Start;
writeName(address, $"{method.DeclaringType.Name}_{method.Name}{method.GetFullTypeParametersString()}");
writeComment(address, method);
}
writeSectionHeader("Custom attributes generators");
foreach (var method in model.AttributesByIndices.Values.Where(m => m.VirtualAddress.HasValue)) {
var address = method.VirtualAddress.Value.Start;
writeName(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator");
writeComment(address, $"{method.AttributeType.Name}_CustomAttributesCacheGenerator(CustomAttributesCache *)");
}
writeSectionHeader("Method.Invoke thunks");
foreach (var method in model.MethodInvokers.Where(m => m != null)) {
var address = method.VirtualAddress.Start;
writeName(address, method.Name);
writeComment(address, method);
}
}
private void writeMethods(string typeName, IEnumerable<MethodBase> methods) {
foreach (var method in methods.Where(m => m.VirtualAddress.HasValue)) {
var address = method.VirtualAddress.Value.Start;
writeName(address, $"{typeName}_{method.Name}");
writeComment(address, method);
}
}
private void writeUsages() {
foreach (var usage in model.Package.MetadataUsages) {
var address = usage.VirtualAddress;
var name = model.GetMetadataUsageName(usage);
if (usage.Type != MetadataUsageType.StringLiteral)
writeName(address, $"{name}_{usage.Type}");
else
writeString(address, name);
if (usage.Type == MetadataUsageType.MethodDef || usage.Type == MetadataUsageType.MethodRef) {
var method = model.GetMetadataUsageMethod(usage);
writeComment(address, method);
}
else if (usage.Type != MetadataUsageType.StringLiteral) {
var type = model.GetMetadataUsageType(usage);
writeComment(address, type);
}
}
}
private void writeFunctions() {
foreach (var func in model.Package.FunctionAddresses)
if (func.Key != func.Value)
writeLine($"MakeFunction({func.Key.ToAddressString()}, {func.Value.ToAddressString()})");
}
private void writeMetadata() {
var binary = model.Package.Binary;
// TODO: In the future, add struct definitions/fields, data ranges and the entire IL2CPP metadata tree
writeName(binary.CodeRegistrationPointer, "g_CodeRegistration");
writeName(binary.MetadataRegistrationPointer, "g_MetadataRegistration");
if (model.Package.Version >= 24.2)
writeName(binary.CodeRegistration.pcodeGenModules, "g_CodeGenModules");
foreach (var ptr in binary.CodeGenModulePointers)
writeName(ptr.Value, $"g_{ptr.Key.Replace(".dll", "")}CodeGenModule");
// This will be zero if we found the structs from the symbol table
if (binary.RegistrationFunctionPointer != 0)
writeName(binary.RegistrationFunctionPointer, "__GLOBAL__sub_I_Il2CppCodeRegistration.cpp");
}
private void writeSectionHeader(string sectionName) {
writeLine("");
writeLine($"# SECTION: {sectionName}");
writeLine($"# -----------------------------");
writeLine($"print('Processing {sectionName}')");
writeLine("");
}
private void writeName(ulong address, string name) {
writeLine($"SetName({address.ToAddressString()}, r'{name.ToEscapedString()}')");
}
private void writeString(ulong address, string str) {
writeLine($"SetString({address.ToAddressString()}, r'{str.ToEscapedString()}')");
}
private void writeComment(ulong address, object comment) {
writeLine($"idc.set_cmt({address.ToAddressString()}, r'{comment.ToString().ToEscapedString()}', 1)");
}
private void writeLine(string line) => writer.WriteLine(line);
}
}

View File

@@ -0,0 +1,131 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Il2CppInspector.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Il2CppInspector.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to &lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
///&lt;Project ToolsVersion=&quot;4.0&quot; DefaultTargets=&quot;Build&quot; xmlns=&quot;http://schemas.microsoft.com/developer/msbuild/2003&quot;&gt;
/// &lt;PropertyGroup&gt;
/// &lt;LangVersion&gt;latest&lt;/LangVersion&gt;
/// &lt;/PropertyGroup&gt;
/// &lt;PropertyGroup&gt;
/// &lt;Configuration Condition=&quot; &apos;$(Configuration)&apos; == &apos;&apos; &quot;&gt;Debug&lt;/Configuration&gt;
/// &lt;Platform Condition=&quot; &apos;$(Platform)&apos; == &apos;&apos; &quot;&gt;AnyCPU&lt;/Platform&gt;
/// &lt;ProjectGuid&gt;{%PROJECTGUID%}&lt;/ProjectGuid&gt;
/// &lt;!--&lt;ProductVersion/&gt;--&gt;
/// &lt;!--&lt;SchemaVersion/&gt;--&gt;
/// &lt;OutputType&gt;Li [rest of string was truncated]&quot;;.
/// </summary>
internal static string CsProjTemplate {
get {
return ResourceManager.GetString("CsProjTemplate", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to {%PROJECTGUID%}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
/// {%PROJECTGUID%}.Debug|Any CPU.Build.0 = Debug|Any CPU
/// {%PROJECTGUID%}.Release|Any CPU.ActiveCfg = Release|Any CPU
/// {%PROJECTGUID%}.Release|Any CPU.Build.0 = Release|Any CPU
///.
/// </summary>
internal static string SlnProjectConfiguration {
get {
return ResourceManager.GetString("SlnProjectConfiguration", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Project(&quot;{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}&quot;) = &quot;%PROJECTNAME%&quot;, &quot;%CSPROJRELATIVEPATH%&quot;, &quot;{%PROJECTGUID%}&quot;
///EndProject
///.
/// </summary>
internal static string SlnProjectDefinition {
get {
return ResourceManager.GetString("SlnProjectDefinition", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Microsoft Visual Studio Solution File, Format Version 12.00
///# Visual Studio Version 16
///%PROJECTDEFINITIONS%
///Global
/// GlobalSection(SolutionConfigurationPlatforms) = preSolution
/// Debug|Any CPU = Debug|Any CPU
/// Release|Any CPU = Release|Any CPU
/// EndGlobalSection
/// GlobalSection(ProjectConfigurationPlatforms) = postSolution
///%PROJECTCONFIGURATIONS%
/// EndGlobalSection
/// GlobalSection(SolutionProperties) = preSolution
/// HideSolutionNode = FALSE
/// EndGlobalSection
///EndGlobal
///.
/// </summary>
internal static string SlnTemplate {
get {
return ResourceManager.GetString("SlnTemplate", resourceCulture);
}
}
}
}

View File

@@ -0,0 +1,774 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="CsProjTemplate" xml:space="preserve">
<value>&lt;?xml version="1.0" encoding="utf-8"?&gt;
&lt;Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"&gt;
&lt;PropertyGroup&gt;
&lt;LangVersion&gt;latest&lt;/LangVersion&gt;
&lt;/PropertyGroup&gt;
&lt;PropertyGroup&gt;
&lt;Configuration Condition=" '$(Configuration)' == '' "&gt;Debug&lt;/Configuration&gt;
&lt;Platform Condition=" '$(Platform)' == '' "&gt;AnyCPU&lt;/Platform&gt;
&lt;ProjectGuid&gt;{%PROJECTGUID%}&lt;/ProjectGuid&gt;
&lt;!--&lt;ProductVersion/&gt;--&gt;
&lt;!--&lt;SchemaVersion/&gt;--&gt;
&lt;OutputType&gt;Library&lt;/OutputType&gt;
&lt;!--&lt;NoStandardLibraries&gt;false&lt;/NoStandardLibraries&gt;--&gt;
&lt;AssemblyName&gt;%ASSEMBLYNAME%&lt;/AssemblyName&gt;
&lt;RootNamespace&gt;&lt;/RootNamespace&gt;
&lt;!--&lt;AppDesignerFolder&gt;Properties&lt;/AppDesignerFolder&gt;--&gt;
&lt;TargetFrameworkVersion&gt;v4.7.1&lt;/TargetFrameworkVersion&gt;
&lt;FileAlignment&gt;512&lt;/FileAlignment&gt;
&lt;/PropertyGroup&gt;
&lt;PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "&gt;
&lt;DebugSymbols&gt;true&lt;/DebugSymbols&gt;
&lt;DebugType&gt;full&lt;/DebugType&gt;
&lt;Optimize&gt;false&lt;/Optimize&gt;
&lt;OutputPath&gt;bin\Debug\&lt;/OutputPath&gt;
&lt;DefineConstants&gt;DEBUG;TRACE;UNITY_2019_2_8;UNITY_2019_2;UNITY_2019;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_4_OR_NEWER;UNITY_2019_1_OR_NEWER;UNITY_2019_2_OR_NEWER;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_TEXTURE_STREAMING;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;ENABLE_MANAGED_AUDIO_JOBS;INCLUDE_DYNAMIC_GI;ENABLE_MONO_BDWGC;ENABLE_SCRIPTING_GC_WBARRIERS;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;ENABLE_VIDEO;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_VSTU;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_DIRECTOR;ENABLE_LOCALIZATION;ENABLE_SPRITES;ENABLE_TERRAIN;ENABLE_TILEMAP;ENABLE_TIMELINE;ENABLE_LEGACY_INPUT_MANAGER;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER&lt;/DefineConstants&gt;
&lt;ErrorReport&gt;prompt&lt;/ErrorReport&gt;
&lt;WarningLevel&gt;4&lt;/WarningLevel&gt;
&lt;NoWarn&gt;0169&lt;/NoWarn&gt;
&lt;AllowUnsafeBlocks&gt;True&lt;/AllowUnsafeBlocks&gt;
&lt;Prefer32Bit&gt;false&lt;/Prefer32Bit&gt;
&lt;/PropertyGroup&gt;
&lt;PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "&gt;
&lt;DebugType&gt;pdbonly&lt;/DebugType&gt;
&lt;Optimize&gt;true&lt;/Optimize&gt;
&lt;OutputPath&gt;bin\Release\&lt;/OutputPath&gt;
&lt;ErrorReport&gt;prompt&lt;/ErrorReport&gt;
&lt;WarningLevel&gt;4&lt;/WarningLevel&gt;
&lt;NoWarn&gt;0169&lt;/NoWarn&gt;
&lt;AllowUnsafeBlocks&gt;True&lt;/AllowUnsafeBlocks&gt;
&lt;/PropertyGroup&gt;
&lt;PropertyGroup&gt;
&lt;NoConfig&gt;true&lt;/NoConfig&gt;
&lt;NoStdLib&gt;true&lt;/NoStdLib&gt;
&lt;AddAdditionalExplicitAssemblyReferences&gt;false&lt;/AddAdditionalExplicitAssemblyReferences&gt;
&lt;ImplicitlyExpandNETStandardFacades&gt;false&lt;/ImplicitlyExpandNETStandardFacades&gt;
&lt;ImplicitlyExpandDesignTimeFacades&gt;false&lt;/ImplicitlyExpandDesignTimeFacades&gt;
&lt;/PropertyGroup&gt;
&lt;PropertyGroup&gt;
&lt;ProjectTypeGuids&gt;{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}&lt;/ProjectTypeGuids&gt;
&lt;UnityProjectGenerator&gt;Unity/VSTU&lt;/UnityProjectGenerator&gt;
&lt;UnityProjectType&gt;Game:1&lt;/UnityProjectType&gt;
&lt;!--&lt;UnityBuildTarget&gt;StandaloneWindows:5&lt;/UnityBuildTarget&gt;--&gt;
&lt;!--&lt;UnityVersion&gt;2019.2.8f1&lt;/UnityVersion&gt;--&gt;
&lt;/PropertyGroup&gt;
&lt;ItemGroup&gt;
&lt;Compile Include="**\*.cs"/&gt;
&lt;/ItemGroup&gt;
&lt;!--&lt;ItemGroup&gt;&lt;Analyzer Include="..."/&gt;&lt;/ItemGroup&gt;--&gt;
&lt;ItemGroup&gt;
&lt;Reference Include="UnityEngine"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEditor"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEditor.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.Timeline.Editor"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.Timeline.Editor.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.VSCode.Editor"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.VSCode.Editor.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.TextMeshPro.Editor"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.TextMeshPro.Editor.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UI"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\UnityEngine.UI.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.Timeline"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.Timeline.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.CollabProxy.Editor"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.CollabProxy.Editor.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.Rider.Editor"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.Rider.Editor.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Unity.TextMeshPro"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\Unity.TextMeshPro.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEditor.UI"&gt;
&lt;HintPath&gt;%SCRIPTASSEMBLIES%\UnityEditor.UI.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.AIModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AIModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ARModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ARModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.AccessibilityModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AccessibilityModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.AndroidJNIModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.AnimationModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AnimationModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.AssetBundleModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AssetBundleModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.AudioModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AudioModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ClothModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ClothModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ClusterInputModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterInputModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ClusterRendererModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterRendererModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.CoreModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.CrashReportingModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.CrashReportingModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.DSPGraphModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.DSPGraphModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.DirectorModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.DirectorModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.FileSystemHttpModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.FileSystemHttpModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.GameCenterModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.GameCenterModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.GridModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.GridModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.HotReloadModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.HotReloadModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.IMGUIModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.IMGUIModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ImageConversionModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ImageConversionModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.InputModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.InputModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.InputLegacyModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.InputLegacyModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.JSONSerializeModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.LocalizationModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.LocalizationModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ParticleSystemModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ParticleSystemModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.PerformanceReportingModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.PerformanceReportingModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.PhysicsModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.Physics2DModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.Physics2DModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ProfilerModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ProfilerModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.ScreenCaptureModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ScreenCaptureModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.SharedInternalsModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SharedInternalsModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.SpriteMaskModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteMaskModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.SpriteShapeModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteShapeModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.StreamingModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.StreamingModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.SubstanceModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SubstanceModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.TLSModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TLSModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.TerrainModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.TerrainPhysicsModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainPhysicsModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.TextCoreModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.TextRenderingModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.TilemapModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TilemapModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UIModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UIModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UIElementsModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UIElementsModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UNETModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UNETModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UmbraModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UmbraModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityAnalyticsModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityConnectModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConnectModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityTestProtocolModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityTestProtocolModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityWebRequestModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityWebRequestAssetBundleModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAssetBundleModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityWebRequestAudioModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAudioModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityWebRequestTextureModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.UnityWebRequestWWWModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestWWWModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.VFXModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VFXModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.VRModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VRModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.VehiclesModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VehiclesModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.VideoModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VideoModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.WindModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.WindModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="UnityEngine.XRModule"&gt;
&lt;HintPath&gt;%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.XRModule.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="netstandard"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/ref/2.0.0/netstandard.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="Microsoft.Win32.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/Microsoft.Win32.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.AppContext"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.AppContext.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Collections.Concurrent"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.Concurrent.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Collections"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Collections.NonGeneric"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.NonGeneric.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Collections.Specialized"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.Specialized.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ComponentModel"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ComponentModel.EventBasedAsync"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.EventBasedAsync.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ComponentModel.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ComponentModel.TypeConverter"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.TypeConverter.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Console"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Console.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Data.Common"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Data.Common.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.Contracts"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Contracts.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.Debug"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Debug.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.FileVersionInfo"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.FileVersionInfo.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.Process"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Process.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.StackTrace"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.StackTrace.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.TextWriterTraceListener"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.TextWriterTraceListener.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.Tools"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Tools.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.TraceSource"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.TraceSource.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Diagnostics.Tracing"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Tracing.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Drawing.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Drawing.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Dynamic.Runtime"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Dynamic.Runtime.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Globalization.Calendars"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.Calendars.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Globalization"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Globalization.Extensions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.Extensions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.Compression"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Compression.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.Compression.ZipFile"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Compression.ZipFile.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.FileSystem"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.FileSystem.DriveInfo"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.DriveInfo.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.FileSystem.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.FileSystem.Watcher"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.Watcher.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.IsolatedStorage"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.IsolatedStorage.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.MemoryMappedFiles"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.MemoryMappedFiles.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.Pipes"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Pipes.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.UnmanagedMemoryStream"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.UnmanagedMemoryStream.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Linq"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Linq.Expressions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Expressions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Linq.Parallel"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Parallel.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Linq.Queryable"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Queryable.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.Http"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Http.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.NameResolution"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.NameResolution.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.pNet.NetworkInformation"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.NetworkInformation.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.Ping"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Ping.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.Requests"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Requests.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.Security"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Security.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.Sockets"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Sockets.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.WebHeaderCollection"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebHeaderCollection.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.WebSockets.Client"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebSockets.Client.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net.WebSockets"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebSockets.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ObjectModel"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ObjectModel.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Reflection"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Reflection.Extensions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.Extensions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Reflection.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Resources.Reader"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.Reader.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Resources.ResourceManager"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.ResourceManager.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Resources.Writer"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.Writer.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.CompilerServices.VisualC"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.CompilerServices.VisualC.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Extensions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Extensions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Handles"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Handles.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.InteropServices"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.InteropServices.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.InteropServices.RuntimeInformation"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.InteropServices.RuntimeInformation.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Numerics"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Numerics.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Serialization.Formatters"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Formatters.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Serialization.Json"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Json.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Serialization.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Serialization.Xml"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Xml.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Claims"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Claims.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Cryptography.Algorithms"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Algorithms.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Cryptography.Csp"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Csp.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Cryptography.Encoding"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Encoding.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Cryptography.Primitives"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Primitives.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Cryptography.X509Certificates"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.X509Certificates.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.Principal"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Principal.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Security.SecureString"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.SecureString.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Text.Encoding"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.Encoding.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Text.Encoding.Extensions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.Encoding.Extensions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Text.RegularExpressions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.RegularExpressions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading.Overlapped"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Overlapped.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading.Tasks"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Tasks.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading.Tasks.Parallel"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Tasks.Parallel.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading.Thread"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Thread.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading.ThreadPool"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.ThreadPool.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Threading.Timer"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Timer.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ValueTuple"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ValueTuple.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.ReaderWriter"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.ReaderWriter.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.XDocument"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XDocument.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.XmlDocument"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XmlDocument.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.XmlSerializer"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XmlSerializer.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.XPath"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XPath.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.XPath.XDocument"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XPath.XDocument.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Numerics.Vectors"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/Extensions/2.0.0/System.Numerics.Vectors.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.InteropServices.WindowsRuntime"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/Extensions/2.0.0/System.Runtime.InteropServices.WindowsRuntime.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="mscorlib"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/mscorlib.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ComponentModel.Composition"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.ComponentModel.Composition.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Core"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Core.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Data"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Data.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Drawing"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Drawing.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.IO.Compression.FileSystem"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.IO.Compression.FileSystem.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Net"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Net.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Numerics"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Numerics.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Runtime.Serialization"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Runtime.Serialization.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.ServiceModel.Web"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.ServiceModel.Web.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Transactions"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Transactions.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Web"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Web.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Windows"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Windows.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.Linq"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.Linq.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;Reference Include="System.Xml.Serialization"&gt;
&lt;HintPath&gt;%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.Serialization.dll&lt;/HintPath&gt;
&lt;/Reference&gt;
&lt;/ItemGroup&gt;
&lt;ItemGroup&gt;
%PROJECTREFERENCES%
&lt;/ItemGroup&gt;
&lt;!--&lt;Target Name="GenerateTargetFrameworkMonikerAttribute"/&gt;--&gt;
&lt;Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.Targets" /&gt;
&lt;/Project&gt;</value>
</data>
<data name="SlnProjectConfiguration" xml:space="preserve">
<value> {%PROJECTGUID%}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{%PROJECTGUID%}.Debug|Any CPU.Build.0 = Debug|Any CPU
{%PROJECTGUID%}.Release|Any CPU.ActiveCfg = Release|Any CPU
{%PROJECTGUID%}.Release|Any CPU.Build.0 = Release|Any CPU
</value>
</data>
<data name="SlnProjectDefinition" xml:space="preserve">
<value>Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "%PROJECTNAME%", "%CSPROJRELATIVEPATH%", "{%PROJECTGUID%}"
EndProject
</value>
</data>
<data name="SlnTemplate" xml:space="preserve">
<value>Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
%PROJECTDEFINITIONS%
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
%PROJECTCONFIGURATIONS%
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
</value>
</data>
</root>

View File

@@ -0,0 +1,83 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
namespace Il2CppInspector.Reflection {
public class Assembly
{
// IL2CPP-specific data
public Il2CppModel Model { get; }
public Il2CppImageDefinition ImageDefinition { get; }
public Il2CppAssemblyDefinition AssemblyDefinition { get; }
public Il2CppCodeGenModule ModuleDefinition { get; }
public int Index { get; }
// Custom attributes for this assembly
public IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
// Fully qualified name of the assembly
public string FullName { get; }
// Display name of the assembly
public string ShortName { get; }
// Entry point method for the assembly
public MethodInfo EntryPoint => throw new NotImplementedException();
// List of types defined in the assembly
public List<TypeInfo> DefinedTypes { get; } = new List<TypeInfo>();
// Get a type from its string name (including namespace)
public TypeInfo GetType(string typeName) => DefinedTypes.FirstOrDefault(x => x.FullName == typeName);
// Initialize from specified assembly index in package
public Assembly(Il2CppModel model, int imageIndex) {
Model = model;
ImageDefinition = Model.Package.Images[imageIndex];
AssemblyDefinition = Model.Package.Assemblies[ImageDefinition.assemblyIndex];
if (AssemblyDefinition.imageIndex != imageIndex)
throw new InvalidOperationException("Assembly/image index mismatch");
Index = ImageDefinition.assemblyIndex;
ShortName = Model.Package.Strings[ImageDefinition.nameIndex];
// Get full assembly name
var nameDef = AssemblyDefinition.aname;
var name = Model.Package.Strings[nameDef.nameIndex];
var culture = Model.Package.Strings[nameDef.cultureIndex];
if (string.IsNullOrEmpty(culture))
culture = "neutral";
var pkt = BitConverter.ToString(nameDef.publicKeyToken).Replace("-", "");
if (pkt == "0000000000000000")
pkt = "null";
var version = string.Format($"{nameDef.major}.{nameDef.minor}.{nameDef.build}.{nameDef.revision}");
FullName = string.Format($"{name}, Version={version}, Culture={culture}, PublicKeyToken={pkt.ToLower()}");
if (ImageDefinition.entryPointIndex != -1) {
// TODO: Generate EntryPoint method from entryPointIndex
}
// Find corresponding module (we'll need this for method pointers)
ModuleDefinition = Model.Package.Modules?[ShortName];
// Generate types in DefinedTypes from typeStart to typeStart+typeCount-1
for (var t = ImageDefinition.typeStart; t < ImageDefinition.typeStart + ImageDefinition.typeCount; t++) {
var type = new TypeInfo(t, this);
// Don't add empty module definitions
if (type.Name != "<Module>")
DefinedTypes.Add(type);
}
}
public override string ToString() => FullName;
}
}

View File

@@ -0,0 +1,40 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace Il2CppInspector.Reflection
{
public static class Constants
{
// All C# reserved keywords
// From: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/
public static readonly string[] Keywords = {
"abstract", "as", "base", "bool",
"break", "byte", "case", "catch",
"char", "checked", "class", "const",
"continue", "decimal", "default", "delegate",
"do", "double", "else", "enum",
"event", "explicit", "extern", "false",
"finally", "fixed", "float", "for",
"foreach", "goto", "if", "implicit",
"in", "int", "interface", "internal",
"is", "lock", "long", "namespace",
"new", "null", "object", "operator",
"out", "override", "params", "private",
"protected", "public", "readonly", "ref",
"return", "sbyte", "sealed", "short",
"sizeof", "stackalloc", "static", "string",
"struct", "switch", "this", "throw",
"true", "try", "typeof", "uint",
"ulong", "unchecked", "unsafe", "ushort",
"using", /* "using static", */ "virtual", "void",
"volatile", "while"
};
}
}

View File

@@ -0,0 +1,31 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class ConstructorInfo : MethodBase
{
// IL names of constructor and static constructor
public static readonly string ConstructorName = ".ctor";
public static readonly string TypeConstructorName = ".cctor";
public override MemberTypes MemberType => MemberTypes.Constructor;
public ConstructorInfo(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(pkg, methodIndex, declaringType) { }
public ConstructorInfo(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(model, spec, declaringType) { }
public override string ToString() => DeclaringType.Name + GetFullTypeParametersString()
+ "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")";
public override string GetSignatureString() => Name + GetFullTypeParametersString()
+ "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")";
}
}

View File

@@ -0,0 +1,75 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppInspector.Reflection
{
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?view=netframework-4.8
public class CustomAttributeData
{
// IL2CPP-specific data
public Il2CppModel Model => AttributeType.Assembly.Model;
public int Index { get; set; }
// The type of the attribute
public TypeInfo AttributeType { get; set; }
public (ulong Start, ulong End)? VirtualAddress =>
// The last one will be wrong but there is no way to calculate it
(Model.Package.CustomAttributeGenerators[Index], Model.Package.FunctionAddresses[Model.Package.CustomAttributeGenerators[Index]]);
public override string ToString() => "[" + AttributeType.FullName + "]";
// Get all the custom attributes for a given assembly, type, member or parameter
private static IEnumerable<CustomAttributeData> getCustomAttributes(Assembly asm, int customAttributeIndex) {
if (customAttributeIndex < 0)
yield break;
var pkg = asm.Model.Package;
// Attribute type ranges weren't included before v21 (customASttributeGenerators was though)
if (pkg.Version < 21)
yield break;
var range = pkg.AttributeTypeRanges[customAttributeIndex];
for (var i = range.start; i < range.start + range.count; i++) {
var typeIndex = pkg.AttributeTypeIndices[i];
if (asm.Model.AttributesByIndices.TryGetValue(i, out var attribute)) {
yield return attribute;
continue;
}
attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] };
asm.Model.AttributesByIndices.TryAdd(i, attribute);
yield return attribute;
}
}
private static readonly object gcaLock = new object();
private static IList<CustomAttributeData> getCustomAttributes(Assembly asm, uint token, int customAttributeIndex) {
// Force the generation of the collection to be thread-safe
// Convert the result into a list for thread-safe enumeration
lock (gcaLock) {
return getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList();
}
}
public static IList<CustomAttributeData> GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.AssemblyDefinition.token, asm.AssemblyDefinition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.Definition.token, evt.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.Definition.token, field.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.Definition.token, method.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.Definition.token, param.Definition.customAttributeIndex);
public static IList<CustomAttributeData> GetCustomAttributes(PropertyInfo prop)
=> prop.Definition != null ? getCustomAttributes(prop.Assembly, prop.Definition.token, prop.Definition.customAttributeIndex) : new List<CustomAttributeData>();
public static IList<CustomAttributeData> GetCustomAttributes(TypeInfo type) => getCustomAttributes(type.Assembly, type.Definition.token, type.Definition.customAttributeIndex);
}
}

View File

@@ -0,0 +1,61 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class EventInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppEventDefinition Definition { get; }
public int Index { get; }
// Information/flags about the event
public EventAttributes Attributes { get; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
// Methods for the event
public MethodInfo AddMethod { get; }
public MethodInfo RemoveMethod { get; }
public MethodInfo RaiseMethod { get; }
// Event handler delegate type
private int eventTypeReference;
public TypeInfo EventHandlerType => Assembly.Model.TypesByReferenceIndex[eventTypeReference];
// True if the event has a special name
public bool IsSpecialName => (Attributes & EventAttributes.SpecialName) == EventAttributes.SpecialName;
public override MemberTypes MemberType => MemberTypes.Event;
public EventInfo(Il2CppInspector pkg, int eventIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Events[eventIndex];
Index = eventIndex;
Name = pkg.Strings[Definition.nameIndex];
eventTypeReference = Definition.typeIndex;
var eventType = pkg.TypeReferences[eventTypeReference];
if ((eventType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME) == Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME)
Attributes |= EventAttributes.SpecialName;
// NOTE: This relies on methods being added to TypeInfo.DeclaredMethods in the same order they are defined in the Il2Cpp metadata
// add, remove and raise are method indices from the first method of the declaring type
if (Definition.add >= 0)
AddMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.add);
if (Definition.remove >= 0)
RemoveMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.remove);
if (Definition.raise >= 0)
RaiseMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.raise);
}
}
}

View File

@@ -0,0 +1,125 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppInspector.Reflection
{
public static class Extensions
{
// Convert a list of CustomAttributeData objects into C#-friendly attribute usages
public static string ToString(this IEnumerable<CustomAttributeData> attributes, Scope scope = null,
string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) {
var sb = new StringBuilder();
foreach (var cad in attributes) {
// Find a constructor that either has no parameters, or all optional parameters
var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional));
// IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile
var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : "";
var commentEnd = commentStart.Length > 0 && inline? " */" : "";
var arguments = "";
// Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592
if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") {
commentStart = "";
commentEnd = "";
arguments = "(AttributeTargets.All)";
}
var name = cad.AttributeType.GetScopedCSharpName(scope);
var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal);
if (suffix != -1)
name = name[..suffix];
sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}");
if (emitPointer)
sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}");
sb.Append(inline? " ":"\n");
}
return sb.ToString();
}
// Output a ulong as a 32 or 64-bit hexadecimal address
public static string ToAddressString(this ulong address) => address <= 0xffff_ffff
? string.Format($"0x{(uint)address:X8}")
: string.Format($"0x{address:X16}");
public static string ToAddressString(this (ulong start, ulong end)? address) => ToAddressString(address?.start ?? 0) + "-" + ToAddressString(address?.end ?? 0);
// C# string literal escape characters
// Taken from: https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/strings/#regular-and-verbatim-string-literals
private static Dictionary<char, string> escapeChars = new Dictionary<char, string> {
['\''] = @"\'",
['"'] = @"\""",
['\\'] = @"\\",
['\0'] = @"\0",
['\a'] = @"\a",
['\b'] = @"\b",
['\f'] = @"\f",
['\n'] = @"\n",
['\r'] = @"\r",
['\t'] = @"\t",
['\v'] = @"\v"
};
// Output a string in Python-friendly syntax
public static string ToEscapedString(this string str) {
// Replace standard escape characters
var s = new StringBuilder();
for (var i = 0; i < str.Length; i++)
// Standard escape characters
s.Append(escapeChars.ContainsKey(str[i]) ? escapeChars[str[i]]
// Replace everything else with UTF-16 Unicode
: str[i] < 32 || str[i] > 126 ? @"\u" + $"{(int) str[i]:X4}"
: str[i].ToString());
return s.ToString();
}
// Output a value in C#-friendly syntax
public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) {
if (value is bool)
return (bool) value ? "true" : "false";
if (value is float)
return value + "f";
if (value is string str) {
return $"\"{str.ToEscapedString()}\"";
}
if (value is char) {
var cValue = (int) (char) value;
if (cValue < 32 || cValue > 126)
return $"'\\x{cValue:x4}'";
return $"'{value}'";
}
if (type.IsEnum) {
var flags = type.GetCustomAttributes("System.FlagsAttribute").Any();
var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType<object>(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v);
var typeName = type.GetScopedCSharpName(usingScope);
// We don't know what type the enumeration or value is, so we use Object.Equals() to do content-based equality testing
if (!flags) {
// Defined enum name
if (values.FirstOrDefault(v => v.Value.Equals(value)).Key is string enumValue)
return typeName + "." + enumValue;
// Undefined enum value (return a cast)
return "(" + typeName + ") " + value;
}
// Logical OR a series of flags together
var flagValue = Convert.ToInt64(value);
var setFlags = values.Where(x => (Convert.ToInt64(x.Value) & flagValue) == Convert.ToInt64(x.Value)).Select(x => typeName + "." + x.Key);
return string.Join(" | ", setFlags);
}
// Structs and generic type parameters must use 'default' rather than 'null'
return value?.ToString() ?? (type.IsValueType || type.IsGenericParameter? "default" : "null");
}
}
}

View File

@@ -0,0 +1,150 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection {
public class FieldInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppFieldDefinition Definition { get; }
public int Index { get; }
public long Offset { get; }
public ulong DefaultValueMetadataAddress { get; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0;
public object DefaultValue { get; }
public string DefaultValueString => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType) : "";
// Information/flags about the field
public FieldAttributes Attributes { get; }
// Type of field
private readonly int fieldTypeReference;
public TypeInfo FieldType => Assembly.Model.TypesByReferenceIndex[fieldTypeReference];
// For the Is* definitions below, see:
// https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo.isfamilyandassembly?view=netframework-4.7.1#System_Reflection_FieldInfo_IsFamilyAndAssembly
// True if the field is declared as internal
public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly;
// True if the field is declared as protected
public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family;
// True if the field is declared as 'protected private' (always false)
public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem;
// True if the field is declared as protected public
public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem;
// True if the field is declared as readonly
public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly;
// True if the field is const
public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal;
// True if the field has the NonSerialized attribute
public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized;
// True if the field is extern
public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl;
// True if the field is declared a private
public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private;
// True if the field is declared as public
public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public;
// True if the field has a special name
public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) == FieldAttributes.SpecialName;
// True if the field is declared as static
public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static;
public override MemberTypes MemberType => MemberTypes.Field;
public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) :
base(declaringType) {
Definition = pkg.Fields[fieldIndex];
Index = fieldIndex;
Offset = pkg.FieldOffsets[fieldIndex];
Name = pkg.Strings[Definition.nameIndex];
fieldTypeReference = Definition.typeIndex;
var fieldType = pkg.TypeReferences[fieldTypeReference];
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_PRIVATE)
Attributes |= FieldAttributes.Private;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_PUBLIC)
Attributes |= FieldAttributes.Public;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_FAM_AND_ASSEM)
Attributes |= FieldAttributes.FamANDAssem;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_ASSEMBLY)
Attributes |= FieldAttributes.Assembly;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_FAMILY)
Attributes |= FieldAttributes.Family;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_FAM_OR_ASSEM)
Attributes |= FieldAttributes.FamORAssem;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_STATIC) == Il2CppConstants.FIELD_ATTRIBUTE_STATIC)
Attributes |= FieldAttributes.Static;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_INIT_ONLY) == Il2CppConstants.FIELD_ATTRIBUTE_INIT_ONLY)
Attributes |= FieldAttributes.InitOnly;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_LITERAL) == Il2CppConstants.FIELD_ATTRIBUTE_LITERAL)
Attributes |= FieldAttributes.Literal;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_NOT_SERIALIZED) == Il2CppConstants.FIELD_ATTRIBUTE_NOT_SERIALIZED)
Attributes |= FieldAttributes.NotSerialized;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME) == Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME)
Attributes |= FieldAttributes.SpecialName;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_PINVOKE_IMPL) == Il2CppConstants.FIELD_ATTRIBUTE_PINVOKE_IMPL)
Attributes |= FieldAttributes.PinvokeImpl;
if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_HAS_DEFAULT) != 0)
Attributes |= FieldAttributes.HasDefault;
// Default initialization value if present
if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) {
DefaultValue = value.variant;
DefaultValueMetadataAddress = value.address;
}
}
public string GetAccessModifierString() => this switch {
{ IsPrivate: true } => "private ",
{ IsPublic: true } => "public ",
{ IsFamily: true } => "protected ",
{ IsAssembly: true } => "internal ",
{ IsFamilyOrAssembly: true } => "protected internal ",
{ IsFamilyAndAssembly: true } => "private protected ",
_ => ""
};
public string GetModifierString() {
var modifiers = new StringBuilder(GetAccessModifierString());
if (FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
modifiers.Append("unsafe ");
if (IsLiteral)
modifiers.Append("const ");
// All const fields are also static by implication
else if (IsStatic)
modifiers.Append("static ");
if (IsInitOnly)
modifiers.Append("readonly ");
if (IsPinvokeImpl)
modifiers.Append("extern ");
if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any())
modifiers.Append("fixed ");
return modifiers.ToString();
}
}
}

View File

@@ -0,0 +1,260 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace Il2CppInspector.Reflection
{
public class Il2CppModel
{
public Il2CppInspector Package { get; }
public List<Assembly> Assemblies { get; } = new List<Assembly>();
// List of all types from TypeDefs ordered by their TypeDefinitionIndex
public TypeInfo[] TypesByDefinitionIndex { get; }
// List of all types from TypeRefs ordered by instanceIndex
public TypeInfo[] TypesByReferenceIndex { get; }
// List of all types from MethodSpecs (closed generic types that can be instantiated)
public Dictionary<int, TypeInfo> TypesByMethodSpecClassIndex { get; } = new Dictionary<int, TypeInfo>();
// List of all methods from MethodSpecs (closed generic methods that can be called; does not need to be in a generic class)
public Dictionary<Il2CppMethodSpec, MethodBase> GenericMethods { get; } = new Dictionary<Il2CppMethodSpec, MethodBase>();
// List of all type definitions by fully qualified name (TypeDefs only)
public Dictionary<string, TypeInfo> TypesByFullName { get; } = new Dictionary<string, TypeInfo>();
// Every type
public IEnumerable<TypeInfo> Types => new IEnumerable<TypeInfo>[] {TypesByDefinitionIndex, TypesByReferenceIndex, TypesByMethodSpecClassIndex.Values}
.SelectMany(t => t).Distinct();
// List of all methods ordered by their MethodDefinitionIndex
public MethodBase[] MethodsByDefinitionIndex { get; }
// List of all Method.Invoke functions by invoker index
public MethodInvoker[] MethodInvokers { get; }
// List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices
public ConcurrentDictionary<int, CustomAttributeData> AttributesByIndices { get; } = new ConcurrentDictionary<int, CustomAttributeData>();
// Get an assembly by its image name
public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name);
// Get a type by its fully qualified name including generic type arguments, array brackets etc.
// In other words, rather than only being able to fetch a type definition such as in Assembly.GetType(),
// this method can also find reference types, types created from TypeRefs and constructed types from MethodSpecs
public TypeInfo GetType(string fullName) => Types.FirstOrDefault(t => fullName == t.Namespace + "." + t.Name);
// Get a concrete instantiation of a generic method from its fully qualified name and type arguments
public MethodBase GetGenericMethod(string fullName, params TypeInfo[] typeArguments) =>
GenericMethods.Values.First(m => fullName == m.DeclaringType.Namespace + "." + m.DeclaringType.BaseName + "." + m.Name
&& m.GetGenericArguments().SequenceEqual(typeArguments));
// Create type model
public Il2CppModel(Il2CppInspector package) {
Package = package;
TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length];
TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count];
MethodsByDefinitionIndex = new MethodBase[package.Methods.Length];
MethodInvokers = new MethodInvoker[package.MethodInvokePointers.Length];
// Recursively create hierarchy of assemblies and types from TypeDefs
// No code that executes here can access any type through a TypeRef (ie. via TypesByReferenceIndex)
for (var image = 0; image < package.Images.Length; image++)
Assemblies.Add(new Assembly(this, image));
// Create and reference types from TypeRefs
// Note that you can't resolve any TypeRefs until all the TypeDefs have been processed
for (int typeRefIndex = 0; typeRefIndex < package.TypeReferences.Count; typeRefIndex++) {
var typeRef = Package.TypeReferences[typeRefIndex];
var referencedType = resolveTypeReference(typeRef);
TypesByReferenceIndex[typeRefIndex] = referencedType;
}
// Create types and methods from MethodSpec (which incorporates TypeSpec in IL2CPP)
foreach (var spec in Package.MethodSpecs) {
TypeInfo declaringType;
// Concrete instance of a generic class
// If the class index is not specified, we will later create a generic method in a non-generic class
if (spec.classIndexIndex != -1) {
if (!TypesByMethodSpecClassIndex.ContainsKey(spec.classIndexIndex))
TypesByMethodSpecClassIndex.Add(spec.classIndexIndex, new TypeInfo(this, spec));
declaringType = TypesByMethodSpecClassIndex[spec.classIndexIndex];
}
else
declaringType = MethodsByDefinitionIndex[spec.methodDefinitionIndex].DeclaringType;
// Concrete instance of a generic method
if (spec.methodIndexIndex != -1) {
// Method or constructor
var concreteMethod = new MethodInfo(this, spec, declaringType);
if (concreteMethod.Name == ConstructorInfo.ConstructorName || concreteMethod.Name == ConstructorInfo.TypeConstructorName)
GenericMethods.Add(spec, new ConstructorInfo(this, spec, declaringType));
else
GenericMethods.Add(spec, concreteMethod);
}
}
// Create method invokers (one per signature, in invoker index order)
foreach (var method in MethodsByDefinitionIndex) {
var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition);
if (index != -1) {
if (MethodInvokers[index] == null)
MethodInvokers[index] = new MethodInvoker(method, index);
method.Invoker = MethodInvokers[index];
}
}
// TODO: Some invokers are not initialized or missing, need to find out why
// Create method invokers sourced from generic method invoker indices
foreach (var spec in GenericMethods.Keys) {
if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) {
if (MethodInvokers[index] == null)
MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index);
GenericMethods[spec].Invoker = MethodInvokers[index];
}
}
}
// Get generic arguments from either a type or method instanceIndex from a MethodSpec
public List<TypeInfo> ResolveGenericArguments(int instanceIndex) => ResolveGenericArguments(Package.GenericInstances[instanceIndex]);
public List<TypeInfo> ResolveGenericArguments(Il2CppGenericInst inst) {
// Get list of pointers to type parameters (both unresolved and concrete)
var genericTypeArguments = Package.BinaryImage.ReadMappedWordArray(inst.type_argv, (int)inst.type_argc);
return genericTypeArguments.Select(a => GetTypeFromVirtualAddress((ulong) a)).ToList();
}
private TypeInfo resolveTypeReference(Il2CppType typeRef) {
TypeInfo underlyingType;
switch (typeRef.type) {
// Classes defined in the metadata (reference to a TypeDef)
case Il2CppTypeEnum.IL2CPP_TYPE_CLASS:
case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE:
underlyingType = TypesByDefinitionIndex[typeRef.datapoint]; // klassIndex
break;
// Constructed types
case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST:
case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY:
case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY:
case Il2CppTypeEnum.IL2CPP_TYPE_PTR:
// Generic type and generic method parameters
case Il2CppTypeEnum.IL2CPP_TYPE_VAR:
case Il2CppTypeEnum.IL2CPP_TYPE_MVAR:
underlyingType = new TypeInfo(this, typeRef);
break;
// Primitive types
default:
underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type);
break;
}
// Create a reference type if necessary
return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType;
}
// Basic primitive types are specified via a flag value
private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) {
if ((int) t >= Il2CppConstants.FullNameTypeString.Count)
return null;
var fqn = Il2CppConstants.FullNameTypeString[(int) t];
return TypesByFullName[fqn];
}
// Get a TypeRef by its virtual address
// These are always nested types from references within another TypeRef
public TypeInfo GetTypeFromVirtualAddress(ulong ptr) {
var typeRefIndex = Package.TypeReferenceIndicesByAddress[ptr];
if (TypesByReferenceIndex[typeRefIndex] != null)
return TypesByReferenceIndex[typeRefIndex];
var type = Package.TypeReferences[typeRefIndex];
var referencedType = resolveTypeReference(type);
TypesByReferenceIndex[typeRefIndex] = referencedType;
return referencedType;
}
// The attribute index is an index into AttributeTypeRanges, each of which is a start-end range index into AttributeTypeIndices, each of which is a TypeIndex
public int GetCustomAttributeIndex(Assembly asm, uint token, int customAttributeIndex) {
// Prior to v24.1, Type, Field, Parameter, Method, Event, Property, Assembly definitions had their own customAttributeIndex field
if (Package.Version <= 24.0)
return customAttributeIndex;
// From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image
if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue(token, out var index))
return -1;
return index;
}
// Get the name of a metadata typeRef
public string GetMetadataUsageName(MetadataUsage usage) {
switch (usage.Type) {
case MetadataUsageType.TypeInfo:
case MetadataUsageType.Type:
return GetMetadataUsageType(usage).Name;
case MetadataUsageType.MethodDef:
var method = GetMetadataUsageMethod(usage);
return $"{method.DeclaringType.Name}.{method.Name}";
case MetadataUsageType.FieldInfo:
var fieldRef = Package.FieldRefs[usage.SourceIndex];
var type = GetMetadataUsageType(usage);
var field = type.DeclaredFields.First(f => f.Index == type.Definition.fieldStart + fieldRef.fieldIndex);
return $"{type.Name}.{field.Name}";
case MetadataUsageType.StringLiteral:
return Package.StringLiterals[usage.SourceIndex];
case MetadataUsageType.MethodRef:
type = GetMetadataUsageType(usage);
method = GetMetadataUsageMethod(usage);
return $"{type.Name}.{method.Name}";
}
throw new NotImplementedException("Unknown metadata usage type: " + usage.Type);
}
// Get the type used in a metadata usage
public TypeInfo GetMetadataUsageType(MetadataUsage usage) => usage.Type switch {
MetadataUsageType.Type => TypesByReferenceIndex[usage.SourceIndex],
MetadataUsageType.TypeInfo => TypesByReferenceIndex[usage.SourceIndex],
MetadataUsageType.MethodDef => GetMetadataUsageMethod(usage).DeclaringType,
MetadataUsageType.FieldInfo => TypesByReferenceIndex[Package.FieldRefs[usage.SourceIndex].typeIndex],
MetadataUsageType.MethodRef => Package.MethodSpecs[usage.SourceIndex].classIndexIndex != -1?
TypesByMethodSpecClassIndex[Package.MethodSpecs[usage.SourceIndex].classIndexIndex] :
GetMetadataUsageMethod(usage).DeclaringType,
_ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type")
};
// Get the method used in a metadata usage
public MethodBase GetMetadataUsageMethod(MetadataUsage usage) => usage.Type switch {
MetadataUsageType.MethodDef => MethodsByDefinitionIndex[usage.SourceIndex],
MetadataUsageType.MethodRef => Package.MethodSpecs[usage.SourceIndex].methodIndexIndex != -1?
GenericMethods[Package.MethodSpecs[usage.SourceIndex]] :
MethodsByDefinitionIndex[Package.MethodSpecs[usage.SourceIndex].methodDefinitionIndex],
_ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type")
};
}
}

View File

@@ -0,0 +1,48 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection {
public abstract class MemberInfo
{
// Assembly that this member is defined in. Only set when MemberType == TypeInfo
public Assembly Assembly { get; protected set; }
// Custom attributes for this member
public abstract IEnumerable<CustomAttributeData> CustomAttributes { get; }
public CustomAttributeData[] GetCustomAttributes(string fullTypeName) => CustomAttributes.Where(a => a.AttributeType.FullName == fullTypeName).ToArray();
// Type that this type is declared in for nested types
protected int declaringTypeDefinitionIndex { private get; set; } = -1;
public TypeInfo DeclaringType => declaringTypeDefinitionIndex != -1? Assembly.Model.TypesByDefinitionIndex[declaringTypeDefinitionIndex] : null;
// What sort of member this is, eg. method, field etc.
public abstract MemberTypes MemberType { get; }
// Name of the member
public virtual string Name { get; protected set; }
// Name of the member with @ prepended if the name is a C# reserved keyword
public string CSharpSafeName => Constants.Keywords.Contains(Name) ? "@" + Name : Name;
// For top-level members in an assembly (ie. non-nested types)
protected MemberInfo(Assembly asm) => Assembly = asm;
// For lower level members, eg. fields, properties etc. and nested types
protected MemberInfo(TypeInfo declaringType = null) {
if (declaringType != null) {
Assembly = declaringType.Assembly;
declaringTypeDefinitionIndex = declaringType.Index;
}
}
public override string ToString() => Name;
}
}

View File

@@ -0,0 +1,280 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection
{
public abstract class MethodBase : MemberInfo
{
// IL2CPP-specific data
public Il2CppMethodDefinition Definition { get; }
public int Index { get; }
public (ulong Start, ulong End)? VirtualAddress { get; }
// Method.Invoke implementation
public MethodInvoker Invoker { get; set; }
// Information/flags about the method
public MethodAttributes Attributes { get; protected set; }
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public List<ParameterInfo> DeclaredParameters { get; } = new List<ParameterInfo>();
public bool IsAbstract => (Attributes & MethodAttributes.Abstract) == MethodAttributes.Abstract;
public bool IsAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Assembly;
public bool IsConstructor => MemberType == MemberTypes.Constructor;
public bool IsFamily => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family;
public bool IsFamilyAndAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamANDAssem;
public bool IsFamilyOrAssembly => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem;
public bool IsFinal => (Attributes & MethodAttributes.Final) == MethodAttributes.Final;
public bool IsHideBySig => (Attributes & MethodAttributes.HideBySig) == MethodAttributes.HideBySig;
public bool IsPrivate => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private;
public bool IsPublic => (Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public;
public bool IsSpecialName => (Attributes & MethodAttributes.SpecialName) == MethodAttributes.SpecialName;
public bool IsStatic => (Attributes & MethodAttributes.Static) == MethodAttributes.Static;
public bool IsVirtual => (Attributes & MethodAttributes.Virtual) == MethodAttributes.Virtual;
public virtual bool RequiresUnsafeContext => DeclaredParameters.Any(p => p.ParameterType.RequiresUnsafeContext);
// True if the method contains unresolved generic type parameters, or if it is a non-generic method in an open ganeric type
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.containsgenericparameters?view=netframework-4.8
public bool ContainsGenericParameters => DeclaringType.ContainsGenericParameters || genericArguments.Any(ga => ga.ContainsGenericParameters);
// For a generic method definition: the list of generic type parameters
// For an open generic method: a mix of generic type parameters and generic type arguments
// For a closed generic method: the list of generic type arguments
private readonly List<TypeInfo> genericArguments = new List<TypeInfo>();
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.getgenericarguments?view=netframework-4.8
public List<TypeInfo> GetGenericArguments() => genericArguments;
// This was added in .NET Core 2.1 and isn't properly documented yet
public bool IsConstructedGenericMethod => IsGenericMethod && genericArguments.All(ga => !ga.ContainsGenericParameters);
// See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.isgenericmethod?view=netframework-4.8
public bool IsGenericMethod { get; }
public bool IsGenericMethodDefinition => genericArguments.Any() && genericArguments.All(a => a.IsGenericMethodParameter);
// TODO: GetMethodBody()
public string CSharpName =>
// Operator overload or user-defined conversion operator
OperatorMethodNames.ContainsKey(Name)? "operator " + OperatorMethodNames[Name]
// Explicit interface implementation
: (IsVirtual && IsFinal && (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot && Name.IndexOf('.') != -1)?
((Func<string>)(() => {
// This is some shenanigans because IL2CPP does not use a consistent naming scheme for explicit interface implementation method names
var implementingInterface = DeclaringType.ImplementedInterfaces.FirstOrDefault(i => Name.StartsWith(i.Namespace + "." + i.CSharpName + "."))
?? DeclaringType.ImplementedInterfaces.FirstOrDefault(i => Name.StartsWith(i.Namespace + "." + i.CSharpTypeDeclarationName.Replace(" ", "") + "."));
// TODO: There are some combinations we haven't dealt with so use this test as a safety valve
if (implementingInterface == null)
return Name;
return implementingInterface.CSharpName + Name.Substring(Name.LastIndexOf('.'));
}))()
// Regular method
: Name;
// Initialize a method from a method definition (MethodDef)
protected MethodBase(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(declaringType) {
Definition = pkg.Methods[methodIndex];
Index = methodIndex;
Name = pkg.Strings[Definition.nameIndex];
// Find method pointer
VirtualAddress = pkg.GetMethodPointer(Assembly.ModuleDefinition, Definition);
// Add to global method definition list
Assembly.Model.MethodsByDefinitionIndex[Index] = this;
// Generic method definition?
if (Definition.genericContainerIndex >= 0) {
IsGenericMethod = true;
// Store the generic type parameters for later instantiation
var container = pkg.GenericContainers[Definition.genericContainerIndex];
genericArguments = pkg.GenericParameters.Skip((int)container.genericParameterStart).Take(container.type_argc).Select(p => new TypeInfo(this, p)).ToList();
}
// Set method attributes
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_PRIVATE)
Attributes |= MethodAttributes.Private;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_PUBLIC)
Attributes |= MethodAttributes.Public;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_FAM_AND_ASSEM)
Attributes |= MethodAttributes.FamANDAssem;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_ASSEM)
Attributes |= MethodAttributes.Assembly;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_FAMILY)
Attributes |= MethodAttributes.Family;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_FAM_OR_ASSEM)
Attributes |= MethodAttributes.FamORAssem;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_VIRTUAL) != 0)
Attributes |= MethodAttributes.Virtual;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_ABSTRACT) != 0)
Attributes |= MethodAttributes.Abstract;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_STATIC) != 0)
Attributes |= MethodAttributes.Static;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_FINAL) != 0)
Attributes |= MethodAttributes.Final;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_HIDE_BY_SIG) != 0)
Attributes |= MethodAttributes.HideBySig;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_VTABLE_LAYOUT_MASK) == Il2CppConstants.METHOD_ATTRIBUTE_NEW_SLOT)
Attributes |= MethodAttributes.NewSlot;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_PINVOKE_IMPL) != 0)
Attributes |= MethodAttributes.PinvokeImpl;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_SPECIAL_NAME) != 0)
Attributes |= MethodAttributes.SpecialName;
if ((Definition.flags & Il2CppConstants.METHOD_ATTRIBUTE_UNMANAGED_EXPORT) != 0)
Attributes |= MethodAttributes.UnmanagedExport;
// Add arguments
for (var p = Definition.parameterStart; p < Definition.parameterStart + Definition.parameterCount; p++)
DeclaredParameters.Add(new ParameterInfo(pkg, p, this));
}
// Initialize a method from a concrete generic method (MethodSpec)
protected MethodBase(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(declaringType) {
var methodDef = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex];
Name = methodDef.Name;
Attributes = methodDef.Attributes;
IsGenericMethod = true;
genericArguments = model.ResolveGenericArguments(spec.methodIndexIndex);
// Substitute matching generic type parameters with concrete type arguments
foreach (var p in methodDef.DeclaredParameters) {
if (!p.ParameterType.IsGenericMethodParameter)
DeclaredParameters.Add(p);
else
DeclaredParameters.Add(new ParameterInfo(model, p, genericArguments[p.ParameterType.GenericParameterPosition]));
}
VirtualAddress = model.Package.GetGenericMethodPointer(spec);
}
public string GetAccessModifierString() => this switch {
// Static constructors can not have an access level modifier
{ IsConstructor: true, IsStatic: true } => "",
// Finalizers can not have an access level modifier
{ Name: "Finalize", IsVirtual: true, IsFamily: true } => "",
// Explicit interface implementations do not have an access level modifier
{ IsVirtual: true, IsFinal: true, Attributes: var a } when (a & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot && Name.IndexOf('.') != -1 => "",
{ IsPrivate: true } => "private ",
{ IsPublic: true } => "public ",
{ IsFamily: true } => "protected ",
{ IsAssembly: true } => "internal ",
{ IsFamilyOrAssembly: true } => "protected internal ",
{ IsFamilyAndAssembly: true } => "private protected ",
_ => ""
};
public string GetModifierString() {
// Interface methods and properties have no visible modifiers (they are always declared 'public abstract')
if (DeclaringType.IsInterface)
return string.Empty;
var modifiers = new StringBuilder(GetAccessModifierString());
if (RequiresUnsafeContext)
modifiers.Append("unsafe ");
if (IsAbstract)
modifiers.Append("abstract ");
// Methods that implement interfaces are IsVirtual && IsFinal with MethodAttributes.NewSlot (don't show 'virtual sealed' for these)
if (IsFinal && (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot)
modifiers.Append("sealed override ");
// All abstract, override and sealed methods are also virtual by nature
if (IsVirtual && !IsAbstract && !IsFinal && Name != "Finalize")
modifiers.Append((Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot ? "virtual " : "override ");
if (IsStatic)
modifiers.Append("static ");
if ((Attributes & MethodAttributes.PinvokeImpl) != 0)
modifiers.Append("extern ");
// Method hiding
if ((DeclaringType.BaseType?.GetAllMethods().Any(m => m.GetSignatureString() == GetSignatureString() && m.IsHideBySig) ?? false)
&& (((Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot && !IsVirtual)
|| (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot))
modifiers.Append("new ");
if (Name == "op_Implicit")
modifiers.Append("implicit ");
if (Name == "op_Explicit")
modifiers.Append("explicit ");
// Will include a trailing space
return modifiers.ToString();
}
// Get C# syntax-friendly list of parameters
public string GetParametersString(Scope usingScope, bool emitPointer = false, bool commentAttributes = false)
=> string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(usingScope, emitPointer, commentAttributes)));
public string GetTypeParametersString(Scope usingScope) => !GetGenericArguments().Any()? "" :
"<" + string.Join(", ", GetGenericArguments().Select(p => p.GetScopedCSharpName(usingScope))) + ">";
public string GetFullTypeParametersString() => !GetGenericArguments().Any()? "" :
"[" + string.Join(",", GetGenericArguments().Select(p => p.Name)) + "]";
public abstract string GetSignatureString();
// List of operator overload metadata names
// https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/operator-overloads
public static Dictionary<string, string> OperatorMethodNames = new Dictionary<string, string> {
["op_Implicit"] = "",
["op_Explicit"] = "",
["op_Addition"] = "+",
["op_Subtraction"] = "-",
["op_Multiply"] = "*",
["op_Division"] = "/",
["op_Modulus"] = "%",
["op_ExclusiveOr"] = "^",
["op_BitwiseAnd"] = "&",
["op_BitwiseOr"] = "|",
["op_LogicalAnd"] = "&&",
["op_LogicalOr"] = "||",
["op_Assign"] = "=",
["op_LeftShift"] = "<<",
["op_RightShift"] = ">>",
["op_SignedLeftShift"] = "", // Listed as N/A in the documentation
["op_SignedRightShift"] = "", // Listed as N/A in the documentation
["op_Equality"] = "==",
["op_Inequality"] = "!=",
["op_GreaterThan"] = ">",
["op_LessThan"] = "<",
["op_GreaterThanOrEqual"] = ">=",
["op_LessThanOrEqual"] = "<=",
["op_MultiplicationAssignment"] = "*=",
["op_SubtractionAssignment"] = "-=",
["op_ExclusiveOrAssignment"] = "^=",
["op_LeftShiftAssignment"] = "<<=", // Doesn't seem to be any right shift assignment`in documentation
["op_ModulusAssignment"] = "%=",
["op_AdditionAssignment"] = "+=",
["op_BitwiseAndAssignment"] = "&=",
["op_BitwiseOrAssignment"] = "|=",
["op_Comma"] = ",",
["op_DivisionAssignment"] = "*/=",
["op_Decrement"] = "--",
["op_Increment"] = "++",
["op_UnaryNegation"] = "-",
["op_UnaryPlus"] = "+",
["op_OnesComplement"] = "~"
};
}
}

View File

@@ -0,0 +1,47 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class MethodInfo : MethodBase
{
public override MemberTypes MemberType => MemberTypes.Method;
// Info about the return parameter
public ParameterInfo ReturnParameter { get; }
// Return type of the method
private readonly int returnTypeReference;
public TypeInfo ReturnType => Assembly.Model.TypesByReferenceIndex[returnTypeReference];
public override bool RequiresUnsafeContext => base.RequiresUnsafeContext || ReturnType.RequiresUnsafeContext;
// IL2CPP doesn't seem to retain return type custom attributes
public MethodInfo(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(pkg, methodIndex, declaringType) {
// Add return parameter
returnTypeReference = Definition.returnType;
ReturnParameter = new ParameterInfo(pkg, -1, this);
}
public MethodInfo(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(model, spec, declaringType) {
var methodDef = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex];
// Add return parameter
returnTypeReference = methodDef.Definition.returnType;
ReturnParameter = ((MethodInfo) methodDef).ReturnParameter;
}
public override string ToString() => ReturnType.Name + " " + Name + GetFullTypeParametersString() + "(" + string.Join(", ",
DeclaredParameters.Select(x => x.ParameterType.IsByRef? x.ParameterType.Name.TrimEnd('&') + " ByRef" : x.ParameterType.Name)) + ")";
public override string GetSignatureString() => ReturnParameter.GetSignatureString() + " " + Name + GetFullTypeParametersString()
+ "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")";
}
}

View File

@@ -0,0 +1,64 @@
/*
Copyright 2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Il2CppInspector.Reflection
{
// Class representing a MethodBase.Invoke() method for a specific signature
// Every IL2CPP invoker has the signature:
// void* RuntimeInvoker_{RequiresObject (True/False)}{ReturnType}_{ParameterTypes}
// (Il2CppMethodPointer pointer, const RuntimeMethod* methodMetadata, void* obj, void** args)
public class MethodInvoker
{
// IL2CPP invoker index
public int Index { get; }
// Virtual address of the invoker function
public (ulong Start, ulong End) VirtualAddress { get; }
// If false, the first argument to the called function pointers must be the object instance
public bool IsStatic { get; }
// Return type
public TypeInfo ReturnType { get; }
// Argument types
public TypeInfo[] ParameterTypes { get; }
// Find the correct method invoker for a method with a specific signature
public MethodInvoker(MethodBase exampleMethod, int index) {
var model = exampleMethod.Assembly.Model;
var package = exampleMethod.Assembly.Model.Package;
Index = index;
IsStatic = exampleMethod.IsStatic;
ReturnType = exampleMethod.IsConstructor ? model.TypesByFullName["System.Void"] : mapParameterType(model, ((MethodInfo) exampleMethod).ReturnType);
ParameterTypes = exampleMethod.DeclaredParameters.Select(p => mapParameterType(model, p.ParameterType)).ToArray();
var start = package.MethodInvokePointers[Index];
VirtualAddress = (start & 0xffff_ffff_ffff_fffe, package.FunctionAddresses[start]);
}
// The invokers use Object for all reference types, and SByte for booleans
private TypeInfo mapParameterType(Il2CppModel model, TypeInfo type) => type switch {
{ IsValueType: false } => model.TypesByFullName["System.Object"],
{ FullName: "System.Boolean" } => model.TypesByFullName["System.SByte"],
_ => type
};
public string Name => $"RunTimeInvoker_{!IsStatic}{ReturnType.BaseName}_" + string.Join("_", ParameterTypes.Select(p => p.BaseName));
// Display as a C++ method signature
public string Signature => Name + "(Il2CppMethodPointer pointer, const RuntimeMethod* methodMetadata, void* obj, void** args)";
public override string ToString() => Signature;
}
}

View File

@@ -0,0 +1,133 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection
{
public class ParameterInfo
{
// IL2CPP-specific data
public Il2CppParameterDefinition Definition { get; }
public int Index { get; }
public ulong DefaultValueMetadataAddress { get; }
// Information/flags about the parameter
public ParameterAttributes Attributes { get; }
// Custom attributes for this parameter
public IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
// True if the parameter has a default value
public bool HasDefaultValue => (Attributes & ParameterAttributes.HasDefault) != 0;
// Default value for the parameter
public object DefaultValue { get; }
public bool IsIn => (Attributes & ParameterAttributes.In) != 0;
public bool IsOptional => (Attributes & ParameterAttributes.Optional) != 0;
public bool IsOut => (Attributes & ParameterAttributes.Out) != 0;
public bool IsRetval => (Attributes & ParameterAttributes.Retval) != 0;
// The method in which the parameter is defined
public MethodBase DeclaringMethod { get; }
// Name of parameter
public string Name { get; }
public string CSharpSafeName => Constants.Keywords.Contains(Name) ? "@" + Name : Name;
// Type of this parameter
private readonly int paramTypeReference;
public TypeInfo ParameterType => DeclaringMethod.Assembly.Model.TypesByReferenceIndex[paramTypeReference];
// Zero-indexed position of the parameter in parameter list
public int Position { get; }
// Create a parameter. Specify paramIndex == -1 for a return type parameter
public ParameterInfo(Il2CppInspector pkg, int paramIndex, MethodBase declaringMethod) {
Index = paramIndex;
DeclaringMethod = declaringMethod;
if (paramIndex == -1) {
Position = -1;
paramTypeReference = declaringMethod.Definition.returnType;
Attributes |= ParameterAttributes.Retval;
return;
}
Definition = pkg.Params[Index];
Name = pkg.Strings[Definition.nameIndex];
// Handle unnamed/obfuscated parameter names
if (string.IsNullOrEmpty(Name))
Name = string.Format($"param_{Index:x8}");
Position = paramIndex - declaringMethod.Definition.parameterStart;
paramTypeReference = Definition.typeIndex;
var paramType = pkg.TypeReferences[paramTypeReference];
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_HAS_DEFAULT) != 0)
Attributes |= ParameterAttributes.HasDefault;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_OPTIONAL) != 0)
Attributes |= ParameterAttributes.Optional;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_IN) != 0)
Attributes |= ParameterAttributes.In;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_OUT) != 0)
Attributes |= ParameterAttributes.Out;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_RESERVED_MASK) != 0)
Attributes |= ParameterAttributes.ReservedMask;
if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_HAS_FIELD_MARSHAL) != 0)
Attributes |= ParameterAttributes.HasFieldMarshal;
if (Position == -1)
Attributes |= ParameterAttributes.Retval;
// Default initialization value if present
if (pkg.ParameterDefaultValue.TryGetValue(paramIndex, out (ulong address, object variant) value)) {
DefaultValue = value.variant;
DefaultValueMetadataAddress = value.address;
}
}
// Create a concrete type parameter from a generic type parameter
public ParameterInfo(Il2CppModel model, ParameterInfo generic, TypeInfo concrete) {
DeclaringMethod = generic.DeclaringMethod;
Name = generic.Name;
Position = generic.Position;
Attributes = generic.Attributes;
// Search for the concrete type's TypeRef index to store as the parameter type reference index
paramTypeReference = Array.IndexOf(model.TypesByReferenceIndex, concrete);
DefaultValue = generic.DefaultValue;
DefaultValueMetadataAddress = generic.DefaultValueMetadataAddress;
}
// ref will be handled as part of the type name
public string GetModifierString() =>
(IsIn ? "in " : "")
+ (IsOut ? "out " : "")
+ (!IsIn && !IsOut && ParameterType.IsByRef ? "ref " : "");
private string getCSharpSignatureString(Scope scope) => $"{GetModifierString()}{ParameterType.GetScopedCSharpName(scope, omitRef: true)}";
public string GetSignatureString() => $"{GetModifierString()}{ParameterType.FullName}";
public string GetParameterString(Scope usingScope, bool emitPointer = false, bool compileAttributes = false) => IsRetval? null :
$"{CustomAttributes.ToString(usingScope, inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}"
+ (Position == 0 && DeclaringMethod.GetCustomAttributes("System.Runtime.CompilerServices.ExtensionAttribute").Any()? "this ":"")
+ $"{getCSharpSignatureString(usingScope)} {CSharpSafeName}"
+ (IsOptional? " = " + DefaultValue.ToCSharpValue(ParameterType, usingScope)
+ (emitPointer && !(DefaultValue is null)? $" /* Metadata: 0x{(uint) DefaultValueMetadataAddress:X8} */" : "") : "");
public string GetReturnParameterString(Scope scope) => !IsRetval? null : getCSharpSignatureString(scope);
public override string ToString() => ParameterType.Name + " " + Name;
}
}

View File

@@ -0,0 +1,72 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Il2CppInspector.Reflection {
public class PropertyInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppPropertyDefinition Definition { get; }
public int Index { get; }
public bool CanRead => GetMethod != null;
public bool CanWrite => SetMethod != null;
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public MethodInfo GetMethod { get; }
public MethodInfo SetMethod { get; }
public bool IsAutoProperty => DeclaringType.DeclaredFields.Any(f => f.Name == $"<{Name}>k__BackingField");
public override string Name { get; protected set; }
public string CSharpName {
get {
// Explicit interface implementation
if (DeclaringType.ImplementedInterfaces
.FirstOrDefault(i => CSharpSafeName.IndexOf("." + i.CSharpName, StringComparison.Ordinal) != -1) is TypeInfo @interface)
return CSharpSafeName.Substring(CSharpSafeName.IndexOf("." + @interface.CSharpName, StringComparison.Ordinal) + 1);
// Regular method
return Name;
}
}
public TypeInfo PropertyType => GetMethod?.ReturnType ?? SetMethod.DeclaredParameters[^1].ParameterType;
public override MemberTypes MemberType => MemberTypes.Property;
public PropertyInfo(Il2CppInspector pkg, int propIndex, TypeInfo declaringType) :
base(declaringType) {
Index = propIndex;
Definition = pkg.Properties[propIndex];
Name = pkg.Strings[Definition.nameIndex];
// prop.get and prop.set are method indices from the first method of the declaring type
if (Definition.get >= 0)
GetMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.get);
if (Definition.set >= 0)
SetMethod = declaringType.DeclaredMethods.First(x => x.Index == declaringType.Definition.methodStart + Definition.set);
}
// Create a property based on a get and set method
public PropertyInfo(MethodInfo getter, MethodInfo setter, TypeInfo declaringType) :
base(declaringType) {
Index = -1;
Definition = null;
Name = (getter ?? setter).Name.Replace(".get_", ".").Replace(".set_", ".");
GetMethod = getter;
SetMethod = setter;
}
}
}

View File

@@ -0,0 +1,20 @@
/*
Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System.Collections.Generic;
namespace Il2CppInspector.Reflection
{
// A code scope with which to evaluate how to output type references
public class Scope
{
// The scope we are currently in
public TypeInfo Current;
// The list of namespace using directives in the file
public IEnumerable<string> Namespaces;
}
}

View File

@@ -0,0 +1,960 @@
/*
Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com
All rights reserved.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Il2CppInspector.Reflection {
public class TypeInfo : MemberInfo
{
// IL2CPP-specific data
public Il2CppTypeDefinition Definition { get; }
public int Index { get; } = -1;
// Information/flags about the type
// Undefined if the Type represents a generic type parameter
public TypeAttributes Attributes { get; }
// Type that this type inherits from
private readonly int baseTypeReference = -1;
public TypeInfo BaseType => IsPointer? null :
baseTypeReference != -1?
Assembly.Model.TypesByReferenceIndex[baseTypeReference]
: IsArray? Assembly.Model.TypesByFullName["System.Array"]
: Namespace != "System" || BaseName != "Object" ? Assembly.Model.TypesByFullName["System.Object"]
: null;
// True if the type contains unresolved generic type parameters
public bool ContainsGenericParameters => IsGenericParameter || genericArguments.Any(ga => ga.ContainsGenericParameters);
public string BaseName => base.Name;
// Get rid of generic backticks
public string UnmangledBaseName => base.Name.IndexOf("`", StringComparison.Ordinal) == -1 ? base.Name : base.Name.Remove(base.Name.IndexOf("`", StringComparison.Ordinal));
// C# colloquial name of the type (if available)
public string CSharpName {
get {
var s = Namespace + "." + base.Name;
var i = Il2CppConstants.FullNameTypeString.IndexOf(s);
var n = (i != -1 ? Il2CppConstants.CSharpTypeString[i] : base.Name);
if (n?.IndexOf("`", StringComparison.Ordinal) != -1)
n = n?.Remove(n.IndexOf("`", StringComparison.Ordinal));
n += (GetGenericArguments().Any()? "<" + string.Join(", ", GetGenericArguments().Select(x => x.CSharpName)) + ">" : "");
if (s == "System.Nullable`1" && GetGenericArguments().Any())
n = GetGenericArguments()[0].CSharpName + "?";
if (HasElementType)
n = ElementType.CSharpName;
if ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant)
n = "out " + n;
if ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant)
n = "in " + n;
if (IsByRef)
n = "ref " + n;
return n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : "");
}
}
// C# name as it would be written in a type declaration
public string CSharpTypeDeclarationName {
get {
var ga = IsNested ? GetGenericArguments().Where(p => DeclaringType.GetGenericArguments().All(dp => dp.Name != p.Name)) : GetGenericArguments();
return (IsByRef ? "ref " : "")
+ (HasElementType
? ElementType.CSharpTypeDeclarationName
: ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant ? "in " : "")
+ ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant ? "out " : "")
+ (base.Name.IndexOf("`", StringComparison.Ordinal) == -1 ? base.Name : base.Name.Remove(base.Name.IndexOf("`", StringComparison.Ordinal)))
+ (ga.Any()? "<" + string.Join(", ", ga.Select(x => (!x.IsGenericTypeParameter ? x.Namespace + "." : "") + x.CSharpTypeDeclarationName)) + ">" : ""))
+ (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsPointer ? "*" : "");
}
}
// Custom attributes for this member
public override IEnumerable<CustomAttributeData> CustomAttributes => CustomAttributeData.GetCustomAttributes(this);
public List<ConstructorInfo> DeclaredConstructors { get; } = new List<ConstructorInfo>();
public List<EventInfo> DeclaredEvents { get; } = new List<EventInfo>();
public List<FieldInfo> DeclaredFields { get; } = new List<FieldInfo>();
public List<MemberInfo> DeclaredMembers => new IEnumerable<MemberInfo>[] {
DeclaredConstructors, DeclaredEvents, DeclaredFields, DeclaredMethods,
DeclaredNestedTypes?.ToList() ?? new List<TypeInfo>(), DeclaredProperties
}.SelectMany(m => m).ToList();
public List<MethodInfo> DeclaredMethods { get; } = new List<MethodInfo>();
private readonly int[] declaredNestedTypes;
public IEnumerable<TypeInfo> DeclaredNestedTypes => declaredNestedTypes.Select(x => Assembly.Model.TypesByDefinitionIndex[x]);
public List<PropertyInfo> DeclaredProperties { get; } = new List<PropertyInfo>();
// Get a field by its name
public FieldInfo GetField(string name) => DeclaredFields.FirstOrDefault(f => f.Name == name);
private readonly int genericConstraintIndex;
private readonly int genericConstraintCount;
// Get type constraints on a generic parameter
public TypeInfo[] GetGenericParameterConstraints() {
var types = new TypeInfo[genericConstraintCount];
for (int c = 0; c < genericConstraintCount; c++)
types[c] = Assembly.Model.TypesByReferenceIndex[Assembly.Model.Package.GenericConstraintIndices[genericConstraintIndex + c]];
return types;
}
// Get a method by its name
public MethodInfo GetMethod(string name) => DeclaredMethods.FirstOrDefault(m => m.Name == name);
// Get all methods with same name (overloads)
public MethodInfo[] GetMethods(string name) => DeclaredMethods.Where(m => m.Name == name).ToArray();
// Get methods including inherited methods
public MethodInfo[] GetAllMethods() {
var methods = new List<IEnumerable<MethodInfo>>();
// Specifically return a list in order of most derived to least derived
for (var type = this; type != null; type = type.BaseType)
methods.Add(type.DeclaredMethods);
return methods.SelectMany(m => m).ToArray();
}
// Get a property by its name
public PropertyInfo GetProperty(string name) => DeclaredProperties.FirstOrDefault(p => p.Name == name);
// Method that the type is declared in if this is a type parameter of a generic method
// TODO: Make a unit test from this: https://docs.microsoft.com/en-us/dotnet/api/system.type.declaringmethod?view=netframework-4.8
public MethodBase DeclaringMethod;
// IsGenericTypeParameter and IsGenericMethodParameter from https://github.com/dotnet/corefx/issues/23883
public bool IsGenericTypeParameter => IsGenericParameter && DeclaringMethod == null;
public bool IsGenericMethodParameter => IsGenericParameter && DeclaringMethod != null;
// Gets the type of the object encompassed or referred to by the current array, pointer or reference type
public TypeInfo ElementType { get; }
// Type name including namespace
// Fully qualified generic type names from the C# compiler use backtick and arity rather than a list of generic arguments
public string FullName =>
IsGenericParameter? null :
HasElementType && ElementType.IsGenericParameter? null :
(HasElementType? ElementType.FullName :
(DeclaringType != null? DeclaringType.FullName + "+" : Namespace + (Namespace.Length > 0? "." : ""))
+ base.Name)
+ (IsArray? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsByRef? "&" : "")
+ (IsPointer? "*" : "");
// Returns the minimally qualified type name required to refer to this type within the specified scope
private string getScopedFullName(Scope scope) {
// This is the type to be used (generic type parameters have a null FullName)
var usedType = FullName?.Replace('+', '.') ?? Name;
// This is the scope in which this type is currently being used
// If Scope.Current is null, our scope is at the assembly level
var usingScope = scope.Current?.FullName.Replace('+', '.') ?? "";
// This is the scope in which this type's definition is located
var declaringScope = DeclaringType?.FullName.Replace('+', '.') ?? Namespace;
// Are we in the same scope as the scope the type is defined in? Save ourselves a bunch of work if so
if (usingScope == declaringScope)
return base.Name;
// We're also in the same scope the type is defined in if we're looking for a nested type
// that is declared in a type we derive from
for (var b = scope.Current?.BaseType; b != null; b = b.BaseType)
if (b.FullName.Replace('+', '.') == declaringScope)
return base.Name;
// Find first difference in the declaring scope from the using scope, moving one namespace/type name at a time
var diff = 1;
usingScope += ".";
declaringScope += ".";
while (usingScope.IndexOf('.', diff) == declaringScope.IndexOf('.', diff)
&& usingScope.IndexOf('.', diff) != -1
&& usingScope.Substring(0, usingScope.IndexOf('.', diff))
== declaringScope.Substring(0, declaringScope.IndexOf('.', diff)))
diff = usingScope.IndexOf('.', diff) + 1;
usingScope = usingScope.Remove(usingScope.Length - 1);
declaringScope = declaringScope.Remove(declaringScope.Length - 1);
// This is the mutual root namespace and optionally nested types that the two scopes share
var mutualRootScope = usingScope.Substring(0, diff - 1);
// Determine if the using scope is a child of the declaring scope (always a child if declaring scope is empty)
var usingScopeIsChildOfDeclaringScope = string.IsNullOrEmpty(declaringScope) || (usingScope + ".").StartsWith(declaringScope + ".");
// Determine using directive to use
var usingDirective =
// If the scope of usage is inside the scope in which the type is declared, no additional scope is needed
// but we still need to check for ancestor conflicts below
usingScopeIsChildOfDeclaringScope? declaringScope
// Check to see if there is a namespace in our using directives which brings this type into scope
// Sort by descending order of length to search the deepest namespaces first
: scope.Namespaces.OrderByDescending(n => n.Length).FirstOrDefault(n => declaringScope == n || declaringScope.StartsWith(n + "."));
// minimallyScopedName will eventually contain the least qualified name needed to access the type
// Initially we set it as follows:
// - The non-mutual part of the declaring scope if there is a mutual root scope
// - The fully-qualified type name if there is no mutual root scope
// - The leaf name if the declaring scope and mutual root scope are the same
// The first two must be checked in this order to avoid a . at the start
// when the mutual root scope and declaring scope are both empty
var minimallyScopedName =
declaringScope == mutualRootScope? base.Name :
string.IsNullOrEmpty(mutualRootScope)? declaringScope + '.' + base.Name :
declaringScope.Substring(mutualRootScope.Length + 1) + '.' + base.Name;
// Find the outermost type name if the wanted type is a nested type (if we need it below)
string outerTypeName = "";
if (!usingScopeIsChildOfDeclaringScope)
for (var d = this; d != null; d = d.DeclaringType)
outerTypeName = d.BaseName;
// Are there any ancestor nested types or namespaces in the using scope with the same name as the wanted type's unqualified name?
// If so, the ancestor name will hide the type we are trying to reference, so we need to provide a higher-level scope
// If the using scope is a child of the declaring scope, we can try every parent scope until we find one that doesn't hide the type
// Otherwise, we just try the unqualified outer (least nested) type name to make sure it's accessible
// and revert to the fully qualified name if it's hidden
var nsAndTypeHierarchy = usingScopeIsChildOfDeclaringScope?
usingDirective.Split('.').Append(minimallyScopedName).ToArray()
: new [] {outerTypeName};
var hidden = true;
var foundTypeInAncestorScope = false;
string testTypeName = "";
for (var depth = nsAndTypeHierarchy.Length - 1; depth >= 0 && hidden; depth--) {
testTypeName = nsAndTypeHierarchy[depth] + (testTypeName.Length > 0? "." : "") + testTypeName;
hidden = false;
for (var d = scope.Current; d != null && !hidden && !foundTypeInAncestorScope; d = d.DeclaringType) {
// If neither condition is true, the wanted type is not hidden by the type we are testing
foundTypeInAncestorScope = d.FullName == FullName;
hidden = !foundTypeInAncestorScope && d.BaseName == testTypeName;
}
// We found the shortest non-hidden scope we can use
// For a child scope, use the shortest found scope
// Otherwise, we've confirmed the outer nested type name is not hidden so go ahead and use the nested type name without a namespace
if (!hidden)
minimallyScopedName = usingScopeIsChildOfDeclaringScope? testTypeName : Name.Replace('+', '.');
// If the wanted type is an unhidden ancestor, we don't need any additional scope at all
if (foundTypeInAncestorScope)
minimallyScopedName = base.Name;
}
// If there are multiple using directives that would allow the same minimally scoped name to be used,
// then the minimally scoped name is ambiguous and we can't use it
// Note that if the wanted type is an unhidden outer class relative to the using scope, this takes precedence and there can be no ambiguity
if (!foundTypeInAncestorScope) {
// Only test the outermost type name
outerTypeName = minimallyScopedName.Split('.')[0];
// Take matching type names from all namespaces in scope
var matchingNamespaces = scope.Namespaces.Where(n => Assembly.Model.TypesByFullName.ContainsKey(n + "." + outerTypeName)).ToList();
// The global namespace is in scope so take every matching type from that too
if (Assembly.Model.TypesByFullName.ContainsKey(outerTypeName))
matchingNamespaces.Add("");
// More than one possible matching namespace? If so, the type reference is ambiguous
if (matchingNamespaces.Count > 1) {
// TODO: This can be improved to cut off a new mutual root that doesn't cause ambiguity
minimallyScopedName = usedType;
}
// No matching namespaces, not hidden, no mutual root scope in the file and no using directive?
// If so, the type's namespace is completely out of scope so use the fully-qualified type name
if (matchingNamespaces.Count == 0 && !hidden && string.IsNullOrEmpty(mutualRootScope) && usingDirective == null)
minimallyScopedName = usedType;
}
return minimallyScopedName;
}
// C#-friendly type name as it should be used in the scope of a given type
public string GetScopedCSharpName(Scope usingScope = null, bool omitRef = false) {
// Unscoped name if no using scope specified
if (usingScope == null)
return CSharpName;
// Generic parameters don't have a scope
if (IsGenericParameter)
return CSharpName;
var s = Namespace + "." + base.Name;
// Built-in keyword type names do not require a scope
var i = Il2CppConstants.FullNameTypeString.IndexOf(s);
var n = i != -1 ? Il2CppConstants.CSharpTypeString[i] : getScopedFullName(usingScope);
// Unmangle generic type names
if (n?.IndexOf("`", StringComparison.Ordinal) != -1)
n = n?.Remove(n.IndexOf("`", StringComparison.Ordinal));
// Generic type parameters and type arguments
var g = string.Join(", ", getGenericTypeParameters(usingScope).Select(x => x.GetScopedCSharpName(usingScope)));
if (!string.IsNullOrEmpty(g))
n += "<" + g + ">";
// Nullable types
if (s == "System.Nullable`1" && GetGenericArguments().Any())
n = GetGenericArguments()[0].GetScopedCSharpName(usingScope) + "?";
// Arrays, pointers, references
if (HasElementType)
n = ElementType.GetScopedCSharpName(usingScope);
return (IsByRef && !omitRef? "ref " : "") + n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : "");
}
// Get the generic type parameters for a specific usage of this type based on its scope,
// or all generic type parameters if no scope specified
private IEnumerable<TypeInfo> getGenericTypeParameters(Scope scope = null) {
var ga = GetGenericArguments();
// If no scope or empty scope specified, or no type parameters, stop here
if (scope?.Current == null || !ga.Any())
return ga;
// In order to elide generic type parameters, the using scope must be a parent of the declaring scope
// Determine if the using scope is a parent of the declaring scope (always a child if using scope is empty)
var usingScopeIsParent = false;
for (var s = DeclaringType; s != null && !usingScopeIsParent; s = s.DeclaringType)
if (s == scope.Current)
usingScopeIsParent = true;
if (!usingScopeIsParent)
return ga;
// Get the generic type parameters available in the using scope
// (no need to recurse because every nested type inherits all of the generic type parameters of all of its ancestors)
var gasInScope = scope.Current.GetGenericArguments();
// Return all of the generic type parameters this type uses minus those already in scope
return ga.Where(p => gasInScope.All(pp => pp.Name != p.Name));
}
public GenericParameterAttributes GenericParameterAttributes { get; }
// Generic parameter position in list of non-concrete type parameters
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.genericparameterposition?view=netframework-4.8
private int genericParameterPosition;
public int GenericParameterPosition {
get => IsGenericParameter ? genericParameterPosition : throw new InvalidOperationException("The current type does not represent a type parameter");
private set => genericParameterPosition = value;
}
// For a generic type definition: the list of generic type parameters
// For an open generic type: a mix of generic type parameters and generic type arguments
// For a closed generic type: the list of generic type arguments
private readonly List<TypeInfo> genericArguments = new List<TypeInfo>();
public List<TypeInfo> GenericTypeParameters => IsGenericTypeDefinition ? genericArguments : new List<TypeInfo>();
public List<TypeInfo> GenericTypeArguments => !IsGenericTypeDefinition ? genericArguments : new List<TypeInfo>();
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.getgenericarguments?view=netframework-4.8
public List<TypeInfo> GetGenericArguments() => genericArguments;
// True if an array, pointer or reference, otherwise false
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.haselementtype?view=netframework-4.8
public bool HasElementType => ElementType != null;
private readonly int[] implementedInterfaceReferences;
public IEnumerable<TypeInfo> ImplementedInterfaces => implementedInterfaceReferences.Select(x => Assembly.Model.TypesByReferenceIndex[x]);
public bool IsAbstract => (Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract;
public bool IsArray { get; }
public bool IsByRef { get; }
public bool IsClass => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class;
public bool IsEnum => enumUnderlyingTypeReference != -1;
public bool IsGenericParameter { get; }
public bool IsGenericType { get; }
public bool IsGenericTypeDefinition => genericArguments.Any() && genericArguments.All(a => a.IsGenericTypeParameter);
public bool IsImport => (Attributes & TypeAttributes.Import) == TypeAttributes.Import;
public bool IsInterface => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface;
public bool IsNested => (MemberType & MemberTypes.NestedType) == MemberTypes.NestedType;
public bool IsNestedAssembly => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedAssembly;
public bool IsNestedFamANDAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamANDAssem;
public bool IsNestedFamily => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily;
public bool IsNestedFamORAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem;
public bool IsNestedPrivate => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPrivate;
public bool IsNestedPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic;
public bool IsNotPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NotPublic;
public bool IsPointer { get; }
// Primitive types table: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table (we exclude Object and String)
public bool IsPrimitive => Namespace == "System" && new[] { "Boolean", "Byte", "SByte", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64", "IntPtr", "UIntPtr", "Char", "Decimal", "Double", "Single" }.Contains(Name);
public bool IsPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public;
public bool IsSealed => (Attributes & TypeAttributes.Sealed) == TypeAttributes.Sealed;
public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable;
public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName;
public bool IsValueType => BaseType?.FullName == "System.ValueType";
// Helper function for determining if using this type as a field, parameter etc. requires that field or method to be declared as unsafe
public bool RequiresUnsafeContext => IsPointer || (HasElementType && ElementType.RequiresUnsafeContext);
// May get overridden by Il2CppType-based constructor below
public override MemberTypes MemberType { get; } = MemberTypes.TypeInfo;
private string @namespace;
public string Namespace {
get => !string.IsNullOrEmpty(@namespace) ? @namespace : DeclaringType?.Namespace ?? "";
set => @namespace = value;
}
// Number of dimensions of an array
private readonly int arrayRank;
public int GetArrayRank() => arrayRank;
public string[] GetEnumNames() => IsEnum? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.Name).ToArray() : throw new InvalidOperationException("Type is not an enumeration");
// The underlying type of an enumeration (int by default)
private readonly int enumUnderlyingTypeReference = -1;
private TypeInfo enumUnderlyingType;
public TypeInfo GetEnumUnderlyingType() {
if (!IsEnum)
return null;
enumUnderlyingType ??= Assembly.Model.TypesByReferenceIndex[enumUnderlyingTypeReference];
return enumUnderlyingType;
}
public Array GetEnumValues() => IsEnum? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.DefaultValue).ToArray() : throw new InvalidOperationException("Type is not an enumeration");
// Initialize type from TypeDef using specified index in metadata
public TypeInfo(int typeIndex, Assembly owner) : base(owner) {
var pkg = Assembly.Model.Package;
Definition = pkg.TypeDefinitions[typeIndex];
Index = typeIndex;
Namespace = pkg.Strings[Definition.namespaceIndex];
Name = pkg.Strings[Definition.nameIndex];
// Derived type?
if (Definition.parentIndex >= 0)
baseTypeReference = Definition.parentIndex;
// Nested type?
if (Definition.declaringTypeIndex >= 0) {
declaringTypeDefinitionIndex = (int) pkg.TypeReferences[Definition.declaringTypeIndex].datapoint;
MemberType |= MemberTypes.NestedType;
}
// Generic type definition?
if (Definition.genericContainerIndex >= 0) {
IsGenericType = true;
IsGenericParameter = false;
// Store the generic type parameters for later instantiation
var container = pkg.GenericContainers[Definition.genericContainerIndex];
genericArguments = pkg.GenericParameters.Skip((int) container.genericParameterStart).Take(container.type_argc).Select(p => new TypeInfo(this, p)).ToList();
}
// Add to global type definition list
Assembly.Model.TypesByDefinitionIndex[Index] = this;
Assembly.Model.TypesByFullName[FullName] = this;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_SERIALIZABLE) != 0)
Attributes |= TypeAttributes.Serializable;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_PUBLIC)
Attributes |= TypeAttributes.Public;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NOT_PUBLIC)
Attributes |= TypeAttributes.NotPublic;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_PUBLIC)
Attributes |= TypeAttributes.NestedPublic;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_PRIVATE)
Attributes |= TypeAttributes.NestedPrivate;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_ASSEMBLY)
Attributes |= TypeAttributes.NestedAssembly;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_FAMILY)
Attributes |= TypeAttributes.NestedFamily;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_FAM_AND_ASSEM)
Attributes |= TypeAttributes.NestedFamANDAssem;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_VISIBILITY_MASK) == Il2CppConstants.TYPE_ATTRIBUTE_NESTED_FAM_OR_ASSEM)
Attributes |= TypeAttributes.NestedFamORAssem;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_ABSTRACT) != 0)
Attributes |= TypeAttributes.Abstract;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_SEALED) != 0)
Attributes |= TypeAttributes.Sealed;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_SPECIAL_NAME) != 0)
Attributes |= TypeAttributes.SpecialName;
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_IMPORT) != 0)
Attributes |= TypeAttributes.Import;
// TypeAttributes.Class == 0 so we only care about setting TypeAttributes.Interface (it's a non-interface class by default)
if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_INTERFACE) != 0)
Attributes |= TypeAttributes.Interface;
// Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum)
if (((Definition.bitfield >> 1) & 1) == 1)
enumUnderlyingTypeReference = Definition.elementTypeIndex;
// Pass-by-reference type
// NOTE: This should actually always evaluate to false in the current implementation
IsByRef = Index == Definition.byrefTypeIndex;
// Add all implemented interfaces
implementedInterfaceReferences = new int[Definition.interfaces_count];
for (var i = 0; i < Definition.interfaces_count; i++)
implementedInterfaceReferences[i] = pkg.InterfaceUsageIndices[Definition.interfacesStart + i];
// Add all nested types
declaredNestedTypes = new int[Definition.nested_type_count];
for (var n = 0; n < Definition.nested_type_count; n++)
declaredNestedTypes[n] = pkg.NestedTypeIndices[Definition.nestedTypesStart + n];
// Add all fields
for (var f = Definition.fieldStart; f < Definition.fieldStart + Definition.field_count; f++)
DeclaredFields.Add(new FieldInfo(pkg, f, this));
// Add all methods
for (var m = Definition.methodStart; m < Definition.methodStart + Definition.method_count; m++) {
var method = new MethodInfo(pkg, m, this);
if (method.Name == ConstructorInfo.ConstructorName || method.Name == ConstructorInfo.TypeConstructorName)
DeclaredConstructors.Add(new ConstructorInfo(pkg, m, this));
else
DeclaredMethods.Add(method);
}
// Add all properties
for (var p = Definition.propertyStart; p < Definition.propertyStart + Definition.property_count; p++)
DeclaredProperties.Add(new PropertyInfo(pkg, p, this));
// There are rare cases when explicitly implemented interface properties
// are only given as methods in the metadata. Find these and add them as properties
var eip = DeclaredMethods.Where(m => m.Name.Contains(".get_") || m.Name.Contains(".set_"))
.Except(DeclaredProperties.Select(p => p.GetMethod))
.Except(DeclaredProperties.Select(p => p.SetMethod));
// Build a paired list of getters and setters
var pairedEip = new List<(MethodInfo get, MethodInfo set)>();
foreach (var p in eip) {
// Discern property name
var n = p.Name.Replace(".get_", ".").Replace(".set_", ".");
// Find setter with no matching getter
if (p.Name.Contains(".get_"))
if (pairedEip.FirstOrDefault(pe => pe.get == null && pe.set.Name == p.Name.Replace(".get_", ".set_")) is (MethodInfo get, MethodInfo set) method) {
pairedEip.Remove(method);
pairedEip.Add((p, method.set));
}
else
pairedEip.Add((p, null));
// Find getter with no matching setter
if (p.Name.Contains(".set_"))
if (pairedEip.FirstOrDefault(pe => pe.set == null && pe.get.Name == p.Name.Replace(".set_", ".get_")) is (MethodInfo get, MethodInfo set) method) {
pairedEip.Remove(method);
pairedEip.Add((method.get, p));
}
else
pairedEip.Add((null, p));
}
foreach (var prop in pairedEip)
DeclaredProperties.Add(new PropertyInfo(prop.get, prop.set, this));
// Add all events
for (var e = Definition.eventStart; e < Definition.eventStart + Definition.event_count; e++)
DeclaredEvents.Add(new EventInfo(pkg, e, this));
}
// Initialize type from type reference (TypeRef)
// Much of the following is adapted from il2cpp::vm::Class::FromIl2CppType
public TypeInfo(Il2CppModel model, Il2CppType pType) {
var image = model.Package.BinaryImage;
// Open and closed generic types
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) {
// TODO: Replace with array load from Il2CppMetadataRegistration.genericClasses
var generic = image.ReadMappedObject<Il2CppGenericClass>(pType.datapoint); // Il2CppGenericClass *
// We have seen one test case where the TypeRef can point to no generic instance
// This is going to leave the TypeInfo in an undefined state
if (generic.typeDefinitionIndex == 0x0000_0000_ffff_ffff)
return;
var genericTypeDef = model.TypesByDefinitionIndex[generic.typeDefinitionIndex];
Assembly = genericTypeDef.Assembly;
Namespace = genericTypeDef.Namespace;
Name = genericTypeDef.BaseName;
Attributes |= TypeAttributes.Class;
// Derived type?
if (genericTypeDef.Definition.parentIndex >= 0)
baseTypeReference = genericTypeDef.Definition.parentIndex;
// Nested type?
if (genericTypeDef.Definition.declaringTypeIndex >= 0) {
declaringTypeDefinitionIndex = (int)model.Package.TypeReferences[genericTypeDef.Definition.declaringTypeIndex].datapoint;
MemberType |= MemberTypes.NestedType;
}
IsGenericType = true;
IsGenericParameter = false;
// Get the instantiation
// TODO: Replace with array load from Il2CppMetadataRegistration.genericInsts
var genericInstance = image.ReadMappedObject<Il2CppGenericInst>(generic.context.class_inst);
if (generic.context.method_inst != 0)
throw new InvalidOperationException("Generic method instance cannot be non-null when processing a generic class instance");
// Find all the type parameters (both unresolved and concrete)
// This will cause new types to be generated with the VAR and MVAR types below
genericArguments = model.ResolveGenericArguments(genericInstance);
}
// TODO: Set DeclaringType for the two below
// Array with known dimensions and bounds
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_ARRAY) {
var descriptor = image.ReadMappedObject<Il2CppArrayType>(pType.datapoint);
ElementType = model.GetTypeFromVirtualAddress(descriptor.etype);
Assembly = ElementType.Assembly;
Namespace = ElementType.Namespace;
Name = ElementType.Name;
IsArray = true;
arrayRank = descriptor.rank;
}
// Dynamically allocated array or pointer type
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY || pType.type == Il2CppTypeEnum.IL2CPP_TYPE_PTR) {
ElementType = model.GetTypeFromVirtualAddress(pType.datapoint);
Assembly = ElementType.Assembly;
Namespace = ElementType.Namespace;
Name = ElementType.Name;
IsPointer = (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_PTR);
IsArray = !IsPointer;
// Heap arrays always have one dimension
arrayRank = 1;
}
// Generic type parameter
if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_VAR || pType.type == Il2CppTypeEnum.IL2CPP_TYPE_MVAR) {
var paramType = model.Package.GenericParameters[pType.datapoint]; // genericParameterIndex
var container = model.Package.GenericContainers[paramType.ownerIndex];
var ownerType = model.TypesByDefinitionIndex[
container.is_method == 1
? model.Package.Methods[container.ownerIndex].declaringType
: container.ownerIndex];
Assembly = ownerType.Assembly;
Namespace = "";
Name = model.Package.Strings[paramType.nameIndex];
Attributes |= TypeAttributes.Class;
// Derived type?
if (ownerType.Definition.parentIndex >= 0)
baseTypeReference = ownerType.Definition.parentIndex;
// Nested type always - sets DeclaringType used below
declaringTypeDefinitionIndex = ownerType.Index;
MemberType |= MemberTypes.NestedType;
// All generic method type parameters have a declared method
if (container.is_method == 1)
DeclaringMethod = model.MethodsByDefinitionIndex[container.ownerIndex];
// Set position in argument list
GenericParameterPosition = paramType.num;
IsGenericParameter = true;
IsGenericType = false;
}
}
// Initialize a type from a concrete generic instance (TypeSpec)
public TypeInfo(Il2CppModel model, Il2CppMethodSpec spec) {
var genericTypeDefinition = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex].DeclaringType;
// Same visibility attributes as generic type definition
Attributes = genericTypeDefinition.Attributes;
// Even though this isn't a TypeDef, we have to set this so that DeclaringType works in later references
Index = genericTypeDefinition.Index;
// Same name as generic type definition
Assembly = genericTypeDefinition.Assembly;
Namespace = genericTypeDefinition.Namespace;
Name = genericTypeDefinition.BaseName; // use BaseName to exclude the type parameters so we can supply our own
IsGenericParameter = false;
IsGenericType = true;
// Resolve type arguments
genericArguments = model.ResolveGenericArguments(spec.classIndexIndex);
/* TODO: This is a bare definition at the moment. We need to iterate over all the members of genericTypeDefinition
* and replace the matching generic type parameters with our concrete type parameters,
* as well as setting the various TypeInfo properties here
*/
}
// Initialize a type that is a generic parameter of a generic type
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8
public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) {
// Same visibility attributes as declaring type
Attributes = declaringType.Attributes;
// Same namespace as declaring type
Namespace = declaringType.Namespace;
// Special constraints
GenericParameterAttributes = (GenericParameterAttributes) param.flags;
// Type constraints
genericConstraintIndex = param.constraintsStart;
genericConstraintCount = param.constraintsCount;
// Base type of object (set by default)
// TODO: BaseType should be set to base type constraint
// TODO: ImplementedInterfaces should be set to interface types constraints
// Name of parameter
Name = Assembly.Model.Package.Strings[param.nameIndex];
// Position
GenericParameterPosition = param.num;
IsGenericParameter = true;
IsGenericType = false;
}
// Initialize a type that is a generic parameter of a generic method
public TypeInfo(MethodBase declaringMethod, Il2CppGenericParameter param) : this(declaringMethod.DeclaringType, param)
=> DeclaringMethod = declaringMethod;
// Initialize a type that is a reference to the specified type
private TypeInfo(TypeInfo underlyingType) {
ElementType = underlyingType;
IsByRef = true;
// No base type or declaring type for reference types
Assembly = ElementType.Assembly;
Definition = ElementType.Definition;
Index = ElementType.Index;
Namespace = ElementType.Namespace;
Name = ElementType.Name;
Attributes = ElementType.Attributes;
}
public TypeInfo MakeByRefType() => new TypeInfo(this);
// Get all the other types directly referenced by this type (single level depth; no recursion)
public List<TypeInfo> GetAllTypeReferences() {
var refs = new HashSet<TypeInfo>();
// Fixed attributes
if (IsImport)
refs.Add(Assembly.Model.TypesByFullName["System.Runtime.InteropServices.ComVisibleAttribute"]);
if (IsSerializable)
refs.Add(Assembly.Model.TypesByFullName["System.SerializableAttribute"]);
// Constructor, event, field, method, nested type, property attributes
var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes);
refs.UnionWith(attrs.Select(a => a.AttributeType));
// Events
refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType));
// Fields
refs.UnionWith(DeclaredFields.Select(f => f.FieldType));
// Properties (return type of getters or argument type of setters)
refs.UnionWith(DeclaredProperties.Select(p => p.PropertyType));
// Nested types
refs.UnionWith(DeclaredNestedTypes);
refs.UnionWith(DeclaredNestedTypes.SelectMany(n => n.GetAllTypeReferences()));
// Constructors
refs.UnionWith(DeclaredConstructors.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType));
// Methods (includes event add/remove/raise, property get/set methods and extension methods)
refs.UnionWith(DeclaredMethods.Select(m => m.ReturnParameter.ParameterType));
refs.UnionWith(DeclaredMethods.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType));
// Method generic type parameters and constraints
refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments()));
refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments())
.SelectMany(p => p.GetGenericParameterConstraints()));
// Type declaration attributes
refs.UnionWith(CustomAttributes.Select(a => a.AttributeType));
// Parent type
if (BaseType != null)
refs.Add(BaseType);
// Declaring type
if (DeclaringType != null)
refs.Add(DeclaringType);
// Element type
if (HasElementType)
refs.Add(ElementType);
// Enum type
if (IsEnum)
refs.Add(GetEnumUnderlyingType());
// Generic type parameters and constraints
refs.UnionWith(GetGenericArguments());
refs.UnionWith(GetGenericParameterConstraints());
// Generic type constraints of type parameters in generic type definition
refs.UnionWith(GenericTypeParameters.SelectMany(p => p.GetGenericParameterConstraints()));
// Implemented interfaces
refs.UnionWith(ImplementedInterfaces);
// Repeatedly replace arrays, pointers and references with their element types
while (refs.Any(r => r.HasElementType))
refs = refs.Select(r => r.HasElementType ? r.ElementType : r).ToHashSet();
// Type arguments in generic types that may have been a field, method parameter etc.
IEnumerable<TypeInfo> genericArguments = refs.ToList();
do {
genericArguments = genericArguments.SelectMany(r => r.GetGenericArguments());
refs.UnionWith(genericArguments);
} while (genericArguments.Any());
// Remove anonymous types
refs.RemoveWhere(r => string.IsNullOrEmpty(r.FullName));
IEnumerable<TypeInfo> refList = refs;
// Eliminated named duplicates (the HashSet removes instance duplicates)
refList = refList.GroupBy(r => r.FullName).Select(p => p.First());
// Remove System.Object
refList = refList.Where(r => r.FullName != "System.Object");
return refList.ToList();
}
// Display name of object
public override string Name => IsGenericParameter ? base.Name :
(HasElementType? ElementType.Name :
(DeclaringType != null ? DeclaringType.Name + "+" : "")
+ base.Name
+ (GetGenericArguments().Any()? "[" + string.Join(",", GetGenericArguments().Select(x => x.Namespace != Namespace? x.FullName ?? x.Name : x.Name)) + "]" : ""))
+ (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "")
+ (IsByRef ? "&" : "")
+ (IsPointer ? "*" : "");
public string GetAccessModifierString() => this switch {
{ IsPublic: true } => "public ",
{ IsNotPublic: true } => "internal ",
{ IsNestedPublic: true } => "public ",
{ IsNestedPrivate: true } => "private ",
{ IsNestedFamily: true } => "protected ",
{ IsNestedAssembly: true } => "internal ",
{ IsNestedFamORAssem: true } => "protected internal ",
{ IsNestedFamANDAssem: true } => "private protected ",
_ => throw new InvalidOperationException("Unknown type access modifier")
};
public string GetModifierString() {
var modifiers = new StringBuilder(GetAccessModifierString());
// An abstract sealed class is a static class
if (IsAbstract && IsSealed)
modifiers.Append("static ");
else {
if (IsAbstract && !IsInterface)
modifiers.Append("abstract ");
if (IsSealed && !IsValueType && !IsEnum)
modifiers.Append("sealed ");
}
if (IsInterface)
modifiers.Append("interface ");
else if (IsValueType)
modifiers.Append("struct ");
else if (IsEnum)
modifiers.Append("enum ");
else
modifiers.Append("class ");
return modifiers.ToString();
}
public string GetTypeConstraintsString(Scope scope) {
if (!IsGenericParameter)
return string.Empty;
var typeConstraints = GetGenericParameterConstraints();
if (GenericParameterAttributes == GenericParameterAttributes.None && typeConstraints.Length == 0)
return string.Empty;
// Check if we are in a nested type, and if so, exclude ourselves if we are a generic type parameter from the outer type
// All constraints are inherited automatically by all nested types so we only have to look at the immediate outer type
if (DeclaringMethod == null && DeclaringType.IsNested && DeclaringType.DeclaringType.GetGenericArguments().Any(p => p.Name == Name))
return string.Empty;
// Check if we are in an overriding method, and if so, exclude ourselves if we are a generic type parameter from the base method
// All constraints are inherited automatically by all overriding methods so we only have to look at the immediate base method
if (DeclaringMethod != null && DeclaringMethod.IsVirtual && !DeclaringMethod.IsAbstract && !DeclaringMethod.IsFinal
&& (DeclaringMethod.Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot) {
// Find nearest ancestor base method which has us as a generic type parameter
var sig = DeclaringMethod.GetSignatureString();
var method = DeclaringMethod.DeclaringType.BaseType.GetAllMethods()
.FirstOrDefault(m => m.IsHideBySig && m.IsVirtual && m.GetSignatureString() == sig && m.GetGenericArguments().Any(p => p.Name == Name));
// Stop if we are inherited from a base method
if (method != null)
return string.Empty;
}
var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.GetScopedCSharpName(scope)).ToList();
// struct or class must be the first constraint specified
if ((GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint)
constraintList.Insert(0, "struct");
if ((GenericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint)
constraintList.Insert(0, "class");
if ((GenericParameterAttributes & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint
&& !constraintList.Contains("struct"))
// new() must be the last constraint specified
constraintList.Add("new()");
// Covariance/contravariance constraints can lead to an empty constraint list
if (!constraintList.Any())
return string.Empty;
return "where " + Name + " : " + string.Join(", ", constraintList);
}
public override string ToString() => Name;
}
}