Refactor solution layout
This commit is contained in:
282
Il2CppInspector.Common/Architectures/Il2CppBinaryARM.cs
Normal file
282
Il2CppInspector.Common/Architectures/Il2CppBinaryARM.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
185
Il2CppInspector.Common/Architectures/Il2CppBinaryARM64.cs
Normal file
185
Il2CppInspector.Common/Architectures/Il2CppBinaryARM64.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
163
Il2CppInspector.Common/Architectures/Il2CppBinaryX64.cs
Normal file
163
Il2CppInspector.Common/Architectures/Il2CppBinaryX64.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
103
Il2CppInspector.Common/Architectures/Il2CppBinaryX86.cs
Normal file
103
Il2CppInspector.Common/Architectures/Il2CppBinaryX86.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
318
Il2CppInspector.Common/FileFormatReaders/ElfReader.cs
Normal file
318
Il2CppInspector.Common/FileFormatReaders/ElfReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
153
Il2CppInspector.Common/FileFormatReaders/FileFormatReader.cs
Normal file
153
Il2CppInspector.Common/FileFormatReaders/FileFormatReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
233
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/Elf.cs
Normal file
233
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/Elf.cs
Normal 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
|
||||
}
|
||||
119
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/MachO.cs
Normal file
119
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/MachO.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
148
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/PE.cs
Normal file
148
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/PE.cs
Normal 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
|
||||
}
|
||||
33
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/UB.cs
Normal file
33
Il2CppInspector.Common/FileFormatReaders/FormatLayouts/UB.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
176
Il2CppInspector.Common/FileFormatReaders/MachOReader.cs
Normal file
176
Il2CppInspector.Common/FileFormatReaders/MachOReader.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Il2CppInspector.Common/FileFormatReaders/PEReader.cs
Normal file
125
Il2CppInspector.Common/FileFormatReaders/PEReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Il2CppInspector.Common/FileFormatReaders/UBReader.cs
Normal file
46
Il2CppInspector.Common/FileFormatReaders/UBReader.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Il2CppInspector.Common/FileFormatReaders/WordConversions.cs
Normal file
54
Il2CppInspector.Common/FileFormatReaders/WordConversions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
258
Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs
Normal file
258
Il2CppInspector.Common/IL2CPP/Il2CppBinary.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
231
Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs
Normal file
231
Il2CppInspector.Common/IL2CPP/Il2CppBinaryClasses.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
244
Il2CppInspector.Common/IL2CPP/Il2CppConstants.cs
Normal file
244
Il2CppInspector.Common/IL2CPP/Il2CppConstants.cs
Normal 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"
|
||||
};
|
||||
}
|
||||
}
|
||||
343
Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs
Normal file
343
Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
184
Il2CppInspector.Common/IL2CPP/Metadata.cs
Normal file
184
Il2CppInspector.Common/IL2CPP/Metadata.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
448
Il2CppInspector.Common/IL2CPP/MetadataClasses.cs
Normal file
448
Il2CppInspector.Common/IL2CPP/MetadataClasses.cs
Normal 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
|
||||
}
|
||||
}
|
||||
35
Il2CppInspector.Common/IL2CPP/MetadataUsage.cs
Normal file
35
Il2CppInspector.Common/IL2CPP/MetadataUsage.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
35
Il2CppInspector.Common/Il2CppInspector.csproj
Normal file
35
Il2CppInspector.Common/Il2CppInspector.csproj
Normal 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>
|
||||
664
Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs
Normal file
664
Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
177
Il2CppInspector.Common/Outputs/IDAPythonScript.cs
Normal file
177
Il2CppInspector.Common/Outputs/IDAPythonScript.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
131
Il2CppInspector.Common/Properties/Resources.Designer.cs
generated
Normal file
131
Il2CppInspector.Common/Properties/Resources.Designer.cs
generated
Normal 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 <?xml version="1.0" encoding="utf-8"?>
|
||||
///<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
/// <PropertyGroup>
|
||||
/// <LangVersion>latest</LangVersion>
|
||||
/// </PropertyGroup>
|
||||
/// <PropertyGroup>
|
||||
/// <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
/// <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
/// <ProjectGuid>{%PROJECTGUID%}</ProjectGuid>
|
||||
/// <!--<ProductVersion/>-->
|
||||
/// <!--<SchemaVersion/>-->
|
||||
/// <OutputType>Li [rest of string was truncated]";.
|
||||
/// </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("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "%PROJECTNAME%", "%CSPROJRELATIVEPATH%", "{%PROJECTGUID%}"
|
||||
///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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
774
Il2CppInspector.Common/Properties/Resources.resx
Normal file
774
Il2CppInspector.Common/Properties/Resources.resx
Normal 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><?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<LangVersion>latest</LangVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{%PROJECTGUID%}</ProjectGuid>
|
||||
<!--<ProductVersion/>-->
|
||||
<!--<SchemaVersion/>-->
|
||||
<OutputType>Library</OutputType>
|
||||
<!--<NoStandardLibraries>false</NoStandardLibraries>-->
|
||||
<AssemblyName>%ASSEMBLYNAME%</AssemblyName>
|
||||
<RootNamespace></RootNamespace>
|
||||
<!--<AppDesignerFolder>Properties</AppDesignerFolder>-->
|
||||
<TargetFrameworkVersion>v4.7.1</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>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</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>0169</NoWarn>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<NoWarn>0169</NoWarn>
|
||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<NoConfig>true</NoConfig>
|
||||
<NoStdLib>true</NoStdLib>
|
||||
<AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>
|
||||
<ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>
|
||||
<ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ProjectTypeGuids>{E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<UnityProjectGenerator>Unity/VSTU</UnityProjectGenerator>
|
||||
<UnityProjectType>Game:1</UnityProjectType>
|
||||
<!--<UnityBuildTarget>StandaloneWindows:5</UnityBuildTarget>-->
|
||||
<!--<UnityVersion>2019.2.8f1</UnityVersion>-->
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="**\*.cs"/>
|
||||
</ItemGroup>
|
||||
<!--<ItemGroup><Analyzer Include="..."/></ItemGroup>-->
|
||||
<ItemGroup>
|
||||
<Reference Include="UnityEngine">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEditor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Timeline.Editor">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.Timeline.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.VSCode.Editor">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.VSCode.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.TextMeshPro.Editor">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.TextMeshPro.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UI">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\UnityEngine.UI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Timeline">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.Timeline.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.CollabProxy.Editor">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.CollabProxy.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.Rider.Editor">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.Rider.Editor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Unity.TextMeshPro">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\Unity.TextMeshPro.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEditor.UI">
|
||||
<HintPath>%SCRIPTASSEMBLIES%\UnityEditor.UI.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AIModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AIModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ARModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ARModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AccessibilityModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AccessibilityModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AndroidJNIModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AndroidJNIModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AnimationModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AnimationModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AssetBundleModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AssetBundleModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.AudioModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.AudioModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ClothModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ClothModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ClusterInputModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterInputModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ClusterRendererModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ClusterRendererModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CoreModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.CrashReportingModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.CrashReportingModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.DSPGraphModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.DSPGraphModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.DirectorModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.DirectorModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.FileSystemHttpModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.FileSystemHttpModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.GameCenterModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.GameCenterModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.GridModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.GridModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.HotReloadModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.HotReloadModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.IMGUIModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.IMGUIModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ImageConversionModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ImageConversionModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.InputModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.InputLegacyModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.JSONSerializeModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.JSONSerializeModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.LocalizationModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.LocalizationModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ParticleSystemModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ParticleSystemModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PerformanceReportingModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.PerformanceReportingModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.PhysicsModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.PhysicsModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.Physics2DModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.Physics2DModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ProfilerModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ProfilerModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.ScreenCaptureModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.ScreenCaptureModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.SharedInternalsModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SharedInternalsModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.SpriteMaskModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteMaskModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.SpriteShapeModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SpriteShapeModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.StreamingModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.StreamingModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.SubstanceModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.SubstanceModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TLSModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TLSModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TerrainModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TerrainPhysicsModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TerrainPhysicsModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextCoreModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TextCoreModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TextRenderingModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.TilemapModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.TilemapModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UIModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UIModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UIElementsModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UIElementsModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UNETModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UNETModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UmbraModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UmbraModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityAnalyticsModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityAnalyticsModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityConnectModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityConnectModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityTestProtocolModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityTestProtocolModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityWebRequestModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityWebRequestAssetBundleModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAssetBundleModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityWebRequestAudioModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestAudioModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityWebRequestTextureModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestTextureModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.UnityWebRequestWWWModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.UnityWebRequestWWWModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.VFXModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VFXModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.VRModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VRModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.VehiclesModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VehiclesModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.VideoModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.VideoModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.WindModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.WindModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="UnityEngine.XRModule">
|
||||
<HintPath>%UNITYPATH%\Editor\Data\Managed\UnityEngine\UnityEngine.XRModule.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="netstandard">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/ref/2.0.0/netstandard.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Win32.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/Microsoft.Win32.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.AppContext">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.AppContext.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections.Concurrent">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.Concurrent.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections.NonGeneric">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.NonGeneric.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Collections.Specialized">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Collections.Specialized.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.EventBasedAsync">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.EventBasedAsync.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.TypeConverter">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ComponentModel.TypeConverter.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Console">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Console.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data.Common">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Data.Common.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Contracts">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Contracts.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Debug">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Debug.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.FileVersionInfo">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.FileVersionInfo.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Process">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Process.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.StackTrace">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.StackTrace.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.TextWriterTraceListener">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.TextWriterTraceListener.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Tools">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Tools.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.TraceSource">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.TraceSource.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Diagnostics.Tracing">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Diagnostics.Tracing.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Drawing.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Dynamic.Runtime">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Dynamic.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Globalization.Calendars">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.Calendars.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Globalization">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Globalization.Extensions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Globalization.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Compression.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression.ZipFile">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Compression.ZipFile.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem.DriveInfo">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.DriveInfo.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.FileSystem.Watcher">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.FileSystem.Watcher.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.IsolatedStorage">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.IsolatedStorage.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.MemoryMappedFiles">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.MemoryMappedFiles.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Pipes">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.Pipes.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.UnmanagedMemoryStream">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.IO.UnmanagedMemoryStream.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq.Expressions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Expressions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq.Parallel">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Parallel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Linq.Queryable">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Linq.Queryable.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Http">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Http.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.NameResolution">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.NameResolution.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.pNet.NetworkInformation">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.NetworkInformation.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Ping">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Ping.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Requests">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Requests.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Security">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Security.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.Sockets">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.Sockets.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.WebHeaderCollection">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebHeaderCollection.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.WebSockets.Client">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebSockets.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net.WebSockets">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Net.WebSockets.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ObjectModel">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ObjectModel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reflection">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reflection.Extensions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Reflection.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Reflection.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Resources.Reader">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.Reader.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Resources.ResourceManager">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.ResourceManager.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Resources.Writer">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Resources.Writer.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.CompilerServices.VisualC">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.CompilerServices.VisualC.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Extensions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Handles">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Handles.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.InteropServices.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices.RuntimeInformation">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.InteropServices.RuntimeInformation.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Numerics">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Numerics.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization.Formatters">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Formatters.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization.Json">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization.Xml">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Runtime.Serialization.Xml.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Claims">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Claims.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Algorithms">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Algorithms.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Csp">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Csp.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Encoding">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Encoding.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.Primitives">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.Primitives.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Cryptography.X509Certificates">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Cryptography.X509Certificates.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.Principal">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.Principal.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Security.SecureString">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Security.SecureString.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.Encoding">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.Encoding.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.Encoding.Extensions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.Encoding.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Text.RegularExpressions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Text.RegularExpressions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Overlapped">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Overlapped.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Tasks.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Tasks.Parallel">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Tasks.Parallel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Thread">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Thread.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.ThreadPool">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.ThreadPool.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Threading.Timer">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Threading.Timer.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.ValueTuple.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.ReaderWriter">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.ReaderWriter.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.XDocument">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XDocument.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.XmlDocument">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XmlDocument.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.XmlSerializer">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XmlSerializer.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.XPath">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XPath.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.XPath.XDocument">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netstandard/System.Xml.XPath.XDocument.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics.Vectors">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/Extensions/2.0.0/System.Numerics.Vectors.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.InteropServices.WindowsRuntime">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/Extensions/2.0.0/System.Runtime.InteropServices.WindowsRuntime.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="mscorlib">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/mscorlib.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ComponentModel.Composition">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.ComponentModel.Composition.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Core">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Data">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Data.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Drawing.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.IO.Compression.FileSystem">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.IO.Compression.FileSystem.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Net">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Net.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Numerics">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Numerics.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Runtime.Serialization">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Runtime.Serialization.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ServiceModel.Web">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.ServiceModel.Web.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Transactions">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Transactions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Web.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Windows">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Windows.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Linq">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.Linq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Xml.Serialization">
|
||||
<HintPath>%UNITYPATH%/Editor/Data/NetStandard/compat/2.0.0/shims/netfx/System.Xml.Serialization.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
%PROJECTREFERENCES%
|
||||
</ItemGroup>
|
||||
|
||||
<!--<Target Name="GenerateTargetFrameworkMonikerAttribute"/>-->
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.Targets" />
|
||||
</Project></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>
|
||||
83
Il2CppInspector.Common/Reflection/Assembly.cs
Normal file
83
Il2CppInspector.Common/Reflection/Assembly.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
40
Il2CppInspector.Common/Reflection/Constants.cs
Normal file
40
Il2CppInspector.Common/Reflection/Constants.cs
Normal 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"
|
||||
};
|
||||
}
|
||||
}
|
||||
31
Il2CppInspector.Common/Reflection/ConstructorInfo.cs
Normal file
31
Il2CppInspector.Common/Reflection/ConstructorInfo.cs
Normal 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())) + ")";
|
||||
}
|
||||
}
|
||||
75
Il2CppInspector.Common/Reflection/CustomAttributeData.cs
Normal file
75
Il2CppInspector.Common/Reflection/CustomAttributeData.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
61
Il2CppInspector.Common/Reflection/EventInfo.cs
Normal file
61
Il2CppInspector.Common/Reflection/EventInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
125
Il2CppInspector.Common/Reflection/Extensions.cs
Normal file
125
Il2CppInspector.Common/Reflection/Extensions.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
150
Il2CppInspector.Common/Reflection/FieldInfo.cs
Normal file
150
Il2CppInspector.Common/Reflection/FieldInfo.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
260
Il2CppInspector.Common/Reflection/Il2CppModel.cs
Normal file
260
Il2CppInspector.Common/Reflection/Il2CppModel.cs
Normal 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")
|
||||
};
|
||||
}
|
||||
}
|
||||
48
Il2CppInspector.Common/Reflection/MemberInfo.cs
Normal file
48
Il2CppInspector.Common/Reflection/MemberInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
280
Il2CppInspector.Common/Reflection/MethodBase.cs
Normal file
280
Il2CppInspector.Common/Reflection/MethodBase.cs
Normal 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"] = "~"
|
||||
};
|
||||
}
|
||||
}
|
||||
47
Il2CppInspector.Common/Reflection/MethodInfo.cs
Normal file
47
Il2CppInspector.Common/Reflection/MethodInfo.cs
Normal 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())) + ")";
|
||||
}
|
||||
}
|
||||
64
Il2CppInspector.Common/Reflection/MethodInvoker.cs
Normal file
64
Il2CppInspector.Common/Reflection/MethodInvoker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
133
Il2CppInspector.Common/Reflection/ParameterInfo.cs
Normal file
133
Il2CppInspector.Common/Reflection/ParameterInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
72
Il2CppInspector.Common/Reflection/PropertyInfo.cs
Normal file
72
Il2CppInspector.Common/Reflection/PropertyInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Il2CppInspector.Common/Reflection/Scope.cs
Normal file
20
Il2CppInspector.Common/Reflection/Scope.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
960
Il2CppInspector.Common/Reflection/TypeInfo.cs
Normal file
960
Il2CppInspector.Common/Reflection/TypeInfo.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user