Unity/C++: Significant re-factoring of Unity header management (see description)

Extract resource handling to UnityResource
Add API header resource helpers
Fix UnityVersion ToString() when Min == Max
Replace fixed list of Il2Cpp header reserved names with parsed names from actual selected headers (solves TypeInfo/MemberInfo problems in 5.3.0-5.3.4 (metadata v16-20))
Use CppDeclarationGenerator when initializing CppTypeCollection to ensure all Il2Cpp header symbols are reserved
Process API headers in CppTypeCollection.FromUnityHeaders
Move #define IS_32BIT handling to UnityHeaders
Update tests
This commit is contained in:
Katy Coe
2020-07-22 19:01:33 +02:00
parent 53909c539c
commit deeb8daa97
12 changed files with 216 additions and 142 deletions

View File

@@ -16,13 +16,16 @@ using Il2CppInspector.Reflection;
namespace Il2CppInspector.Cpp
{
// Class for generating C header declarations from Reflection objects (TypeInfo, etc.)
internal class CppDeclarationGenerator
public class CppDeclarationGenerator
{
private readonly AppModel appModel;
private TypeModel model => appModel.ILModel;
private CppTypeCollection types => appModel.CppTypeCollection;
// Word size (32/64-bit) for this generator
public int WordSize => appModel.WordSize;
// Version number and header file to generate structures for
public UnityVersion UnityVersion => appModel.UnityVersion;
@@ -63,7 +66,7 @@ namespace Il2CppInspector.Cpp
public CppType AsCType(TypeInfo ti) {
// IsArray case handled by TypeNamer.GetName
if (ti.IsByRef || ti.IsPointer) {
return AsCType(ti.ElementType).AsPointer(types.WordSize);
return AsCType(ti.ElementType).AsPointer(WordSize);
}
if (ti.IsValueType) {
if (ti.IsPrimitive && primitiveTypeMap.ContainsKey(ti.Name)) {
@@ -513,16 +516,10 @@ namespace Il2CppInspector.Cpp
// You can customize how naming works by modifying this function.
private void InitializeNaming() {
TypeNamespace = CreateNamespace();
// Type names that may appear in the header
foreach (var typeName in new [] { "CustomAttributesCache", "CustomAttributeTypeCache", "EventInfo", "FieldInfo", "Hash16", "MemberInfo", "MethodInfo", "MethodVariableKind", "MonitorData", "ParameterInfo", "PInvokeArguments", "PropertyInfo", "SequencePointKind", "StackFrameType", "VirtualInvokeData" }) {
TypeNamespace.ReserveName(typeName);
}
TypeNamer = TypeNamespace.MakeNamer<TypeInfo>((ti) => {
if (ti.IsArray)
return TypeNamer.GetName(ti.ElementType) + "__Array";
var name = ti.Name.ToCIdentifier();
if (name.StartsWith("Il2Cpp"))
name = "_" + name;
name = Regex.Replace(name, "__+", "_");
// Work around a dumb IDA bug: enums can't be named the same as certain "built-in" types
// like KeyCode, Position, ErrorType. This only applies to enums, not structs.

View File

@@ -535,6 +535,9 @@ namespace Il2CppInspector.Cpp
// Get all of the types in a logical group
public IEnumerable<CppType> GetTypeGroup(string groupName) => Types.Values.Where(t => t.Group == groupName);
// Get all of the typedefs in a logical group
public IEnumerable<CppType> GetTypedefGroup(string groupName) => TypedefAliases.Values.Where(t => t.Group == groupName);
// Add a type
private void Add(CppType type) {
type.Group = currentGroup;
@@ -578,22 +581,45 @@ namespace Il2CppInspector.Cpp
public CppEnumType NewDefaultEnum(string name = "") => Enum(Types["int"], name);
// Generate a populated CppTypeCollection object from a set of Unity headers
public static CppTypeCollection FromUnityVersion(UnityVersion version, int wordSize = 32)
=> FromUnityHeaders(UnityHeader.GetHeaderForVersion(version), wordSize);
// The CppDeclarationGenerator is used to ensure that the Unity header type names are not used again afterwards
// Omit this parameter only when fetching headers for inspection without a model
public static CppTypeCollection FromUnityVersion(UnityVersion version, CppDeclarationGenerator declGen = null)
=> FromUnityHeaders(UnityHeaders.UnityHeaders.GetHeadersForVersion(version), declGen);
public static CppTypeCollection FromUnityHeaders(UnityHeader header, int wordSize = 32) {
public static CppTypeCollection FromUnityHeaders(UnityHeaders.UnityHeaders header, CppDeclarationGenerator declGen = null) {
var wordSize = declGen?.WordSize ?? 64;
var cppTypes = new CppTypeCollection(wordSize);
// Process Unity headers
cppTypes.SetGroup("il2cpp");
// Add junk from config files we haven't included
cppTypes.TypedefAliases.Add("Il2CppIManagedObjectHolder", cppTypes["void"].AsPointer(wordSize));
cppTypes.TypedefAliases.Add("Il2CppIUnknown", cppTypes["void"].AsPointer(wordSize));
// Process Unity headers
var headers = header.GetHeaderText();
var headers = header.GetTypeHeaderText(wordSize);
cppTypes.AddFromDeclarationText(headers);
// Don't allow any of the header type names to be re-used; ignore primitive types
foreach (var type in cppTypes.GetTypeGroup("il2cpp"))
declGen?.TypeNamespace.ReserveName(type.Name);
foreach (var typedef in cppTypes.GetTypedefGroup("il2cpp"))
declGen?.GlobalsNamespace.ReserveName(typedef.Name);
cppTypes.SetGroup("il2cpp-api");
var apis = header.GetAPIHeaderTypedefText();
cppTypes.AddFromDeclarationText(apis);
foreach (var type in cppTypes.GetTypeGroup("il2cpp-api"))
declGen?.TypeNamespace.ReserveName(type.Name);
foreach (var typedef in cppTypes.GetTypedefGroup("il2cpp-api"))
declGen?.GlobalsNamespace.ReserveName(typedef.Name);
cppTypes.SetGroup("");
return cppTypes;
}
}

View File

@@ -1,93 +0,0 @@
/*
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
Copyright 2020 Robert Xiao - https://robertxiao.ca
All rights reserved.
*/
using System.Reflection;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
namespace Il2CppInspector.Cpp.UnityHeaders
{
// Each instance of UnityHeader represents one header file which potentially covers multiple versions of Unity.
public class UnityHeader
{
// Metadata version of this header. Multiple headers may have the same metadata version
public double MetadataVersion { get; }
// Minimum and maximum Unity version numbers corresponding to this header. Both endpoints are inclusive
public UnityVersionRange Version { get; }
// Filename for the embedded .h resource file containing the header
public string HeaderFilename { get; }
private UnityHeader(string headerFilename) {
HeaderFilename = headerFilename;
Version = UnityVersionRange.FromFilename(HeaderFilename);
MetadataVersion = double.Parse(headerFilename.Split("-")[0], NumberFormatInfo.InvariantInfo);
}
public override string ToString() => Version.ToString();
// Return the contents of this header file as a string
public string GetHeaderText() {
var str = ResourceHelper.GetText(typeof(UnityHeader).Namespace + "." + HeaderFilename);
// Versions 5.3.6-5.4.6 don't include a definition for VirtualInvokeData
if (Version.Min.CompareTo("5.3.6") >= 0 && Version.Max.CompareTo("5.4.6") <= 0) {
str = str + @"struct VirtualInvokeData
{
Il2CppMethodPointer methodPtr;
const MethodInfo* method;
} VirtualInvokeData;";
}
return str;
}
// List all header files embedded into this build of Il2CppInspector
public static IEnumerable<UnityHeader> GetAllHeaders() => ResourceHelper.GetNamesForNamespace(typeof(UnityHeader).Namespace)
.Where(s => s.EndsWith(".h"))
.Select(s => new UnityHeader(s.Substring(typeof(UnityHeader).Namespace.Length + 1)));
// List all API header files and versions embedded into this build of Il2CppInspector
public static Dictionary<string, UnityVersionRange> GetAllAPIs() {
var list = ResourceHelper.GetNamesForNamespace("Il2CppInspector.Cpp.Il2CppAPIHeaders");
return list.Select(i => new {
Key = i,
Value = UnityVersionRange.FromFilename(i),
}).ToDictionary(kv => kv.Key, kv => kv.Value);
}
// Get the header file which supports the given version of Unity
public static UnityHeader GetHeaderForVersion(string version) => GetHeaderForVersion(new UnityVersion(version));
public static UnityHeader GetHeaderForVersion(UnityVersion version) => GetAllHeaders().First(h => h.Version.Contains(version));
// Get API file resources
public static string GetAPIResourceNameForVersion(UnityVersion version) => GetAllAPIs().First(h => h.Value.Contains(version)).Key;
public static string GetAPITextForVersion(UnityVersion version) => ResourceHelper.GetText(GetAPIResourceNameForVersion(version));
// Guess which header file(s) correspond to the given metadata+binary.
// Note that this may match multiple headers due to structural changes between versions
// that are not reflected in the metadata version.
public static List<UnityHeader> GuessHeadersForModel(Reflection.TypeModel model) {
List<UnityHeader> result = new List<UnityHeader>();
foreach (var h in GetAllHeaders()) {
if (h.MetadataVersion != model.Package.BinaryImage.Version)
continue;
if (h.MetadataVersion == 21) {
/* Special version logic for metadata version 21 based on the Il2CppMetadataRegistration.fieldOffsets field */
var headerFieldOffsetsArePointers = h.Version.Min.CompareTo("5.3.7") >= 0 && h.Version.Min.CompareTo("5.4.0") != 0;
var binaryFieldOffsetsArePointers = model.Package.Binary.FieldOffsets == null;
if (headerFieldOffsetsArePointers != binaryFieldOffsetsArePointers)
continue;
}
result.Add(h);
}
return result;
}
}
}

View File

@@ -0,0 +1,152 @@
/*
Copyright 2017-2020 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
Copyright 2020 Robert Xiao - https://robertxiao.ca
All rights reserved.
*/
using System;
using System.Reflection;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Il2CppInspector.Cpp.UnityHeaders
{
// Each instance of UnityHeaders represents all of the header files needed to build for a specific range of Unity versions
// Also provides helper functions to fetch various types of resources
public class UnityHeaders
{
// Metadata version for which this group of headers are valid. Multiple headers may have the same metadata version
public double MetadataVersion { get; }
// Range of Unity versions for which this group of headers are valid
public UnityVersionRange VersionRange { get; }
// The fully qualified names of the embedded resources
private readonly UnityResource typeHeaderResource;
private readonly UnityResource apiHeaderResource;
// Initialize from a type header and an API header
private UnityHeaders(UnityResource typeHeaders, UnityResource apiHeaders) {
typeHeaderResource = typeHeaders;
apiHeaderResource = apiHeaders;
VersionRange = typeHeaders.VersionRange.Intersect(apiHeaders.VersionRange);
MetadataVersion = GetMetadataVersionFromFilename(typeHeaders.Name);
}
// Return the contents of the type header file as a string
public string GetTypeHeaderText(int WordSize) {
var str = (WordSize == 32 ? "#define IS_32BIT\n" : "") + typeHeaderResource.GetText();
// Versions 5.3.6-5.4.6 don't include a definition for VirtualInvokeData
if (VersionRange.Min.CompareTo("5.3.6") >= 0 && VersionRange.Max.CompareTo("5.4.6") <= 0) {
str = str + @"struct VirtualInvokeData
{
Il2CppMethodPointer methodPtr;
const MethodInfo* method;
} VirtualInvokeData;";
}
return str;
}
// Return the contents of the API header file as a string
public string GetAPIHeaderText() => apiHeaderResource.GetText();
// Return the contents of the API header file translated to typedefs as a string
public string GetAPIHeaderTypedefText() => GetTypedefsFromAPIHeader(GetAPIHeaderText());
public override string ToString() => VersionRange.ToString();
// Class which associates an embedded resource with a range of Unity versions
public class UnityResource
{
// The fully qualified name of the embdedded resource
public string Name { get; }
// Minimum and maximum Unity version numbers corresponding to this resource. Both endpoints are inclusive
public UnityVersionRange VersionRange { get; }
// Get the text of this resource
public string GetText() => ResourceHelper.GetText(Name);
public UnityResource(string name) {
Name = name;
VersionRange = UnityVersionRange.FromFilename(name);
}
public override string ToString() => Name + " for " + VersionRange;
}
// Static helpers
// List all type header files embedded into this build of Il2CppInspector
public static IEnumerable<UnityResource> GetAllTypeHeaders() =>
ResourceHelper.GetNamesForNamespace(typeof(UnityHeaders).Namespace)
.Where(s => s.EndsWith(".h"))
.Select(s => new UnityResource(s));
// List all API header files embedded into this build of Il2CppInspector
public static IEnumerable<UnityResource> GetAllAPIHeaders() =>
ResourceHelper.GetNamesForNamespace("Il2CppInspector.Cpp.Il2CppAPIHeaders")
.Where(s => s.EndsWith(".h"))
.Select(s => new UnityResource(s));
// Get the headers which support the given version of Unity
public static UnityHeaders GetHeadersForVersion(UnityVersion version) =>
new UnityHeaders(GetTypeHeaderForVersion(version), GetAPIHeaderForVersion(version));
public static UnityResource GetTypeHeaderForVersion(UnityVersion version) => GetAllTypeHeaders().First(r => r.VersionRange.Contains(version));
// Get the API header file which supports the given version of Unity
public static UnityResource GetAPIHeaderForVersion(UnityVersion version) => GetAllAPIHeaders().First(r => r.VersionRange.Contains(version));
// Guess which header file(s) correspond to the given metadata+binary.
// Note that this may match multiple headers due to structural changes between versions
// that are not reflected in the metadata version.
public static List<UnityHeaders> GuessHeadersForBinary(Il2CppBinary binary) {
List<UnityResource> typeHeaders = new List<UnityResource>();
foreach (var r in GetAllTypeHeaders()) {
var metadataVersion = GetMetadataVersionFromFilename(r.Name);
if (metadataVersion != binary.Image.Version)
continue;
if (metadataVersion == 21) {
/* Special version logic for metadata version 21 based on the Il2CppMetadataRegistration.fieldOffsets field */
var headerFieldOffsetsArePointers = r.VersionRange.Min.CompareTo("5.3.7") >= 0 && r.VersionRange.Min.CompareTo("5.4.0") != 0;
var binaryFieldOffsetsArePointers = binary.FieldOffsets == null;
if (headerFieldOffsetsArePointers != binaryFieldOffsetsArePointers)
continue;
}
typeHeaders.Add(r);
}
// TODO: Replace this with an implementation which searches for the correct API header
return typeHeaders.Select(t => new UnityHeaders(t, GetAPIHeaderForVersion(t.VersionRange.Min))).ToList();
}
// Convert il2cpp-api-functions.h from "DO_API(r, n, p)" to "typedef r (*n)(p)"
internal static string GetTypedefsFromAPIHeader(string text) {
var rgx = new Regex(@"^DO_API(?:_NO_RETURN)?\((.*?),(.*?),\s*\((.*?)\)\s*\);", RegexOptions.Multiline);
return rgx.Replace(text, "typedef $1 (*$2)($3);");
}
// Get a list of function names from il2cpp-api-functions.h, taking #ifs into account
private static IEnumerable<string> GetFunctionNamesFromAPIHeaderText(string text) {
var defText = GetTypedefsFromAPIHeader(text);
var defs = new CppTypeCollection(32); // word size doesn't matter
defs.AddFromDeclarationText(defText);
return defs.TypedefAliases.Keys;
}
// Get the metadata version from a type header resource name
private static double GetMetadataVersionFromFilename(string resourceName)
=> double.Parse(resourceName.Substring(typeof(UnityHeaders).Namespace.Length + 1).Split('-')[0], NumberFormatInfo.InvariantInfo);
}
}

View File

@@ -6,7 +6,6 @@
*/
using System;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
@@ -181,7 +180,7 @@ namespace Il2CppInspector.Cpp.UnityHeaders
var res = $"{Min}";
if (Max == null)
res += "+";
else if (Max != Min)
else if (!Max.Equals(Min))
res += $" - {Max}";
return res;
}