// We try decently hard to avoid creating clashing names, and also sanitize any invalid names. // 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 string[] { "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. if (ti.IsEnum) { name += "__Enum"; } return(name); }); GlobalsNamespace = CreateNamespace(); GlobalNamer = GlobalsNamespace.MakeNamer <MethodBase>((method) => $"{TypeNamer.GetName(method.DeclaringType)}_{method.Name.ToCIdentifier()}"); EnumNamer = GlobalsNamespace.MakeNamer <FieldInfo>((field) => $"{TypeNamer.GetName(field.DeclaringType)}_{field.Name.ToCIdentifier()}"); }
// Generate the C structure for a value type, such as an enum or struct private (CppComplexType valueType, CppComplexType boxedType) GenerateValueFieldStruct(TypeInfo ti) { CppComplexType valueType, boxedType; string name = TypeNamer.GetName(ti); if (ti.IsEnum) { // Enums should be represented using enum syntax // They otherwise behave like value types var namer = CreateNamespace().MakeNamer <FieldInfo>((field) => field.Name.ToCIdentifier()); var underlyingType = AsCType(ti.GetEnumUnderlyingType()); valueType = types.Enum(underlyingType, name); foreach (var field in ti.DeclaredFields) { if (field.Name != "value__") { ((CppEnumType)valueType).AddField(namer.GetName(field), field.DefaultValue); } } boxedType = GenerateObjectStruct(name + "__Boxed", ti); boxedType.AddField("value", AsCType(ti)); } else { // This structure is passed by value, so it doesn't include Il2CppObject fields. valueType = types.Struct(name); GenerateFieldList(valueType, CreateNamespace(), ti); // Also generate the boxed form of the structure which includes the Il2CppObject header. boxedType = GenerateObjectStruct(name + "__Boxed", ti); boxedType.AddField("fields", AsCType(ti)); } return(valueType, boxedType); }
// Generate the fields for the base class of all objects (Il2CppObject) // The two fields are inlined so that we can specialize the klass member for each type object private CppComplexType GenerateObjectStruct(string name, TypeInfo ti) { var type = types.Struct(name); types.AddField(type, "klass", TypeNamer.GetName(ti) + "__Class *"); types.AddField(type, "monitor", "MonitorData *"); return(type); }
/// <summary> /// Include the given type into this generator. This will add the given type and all types it depends on. /// Call GenerateRemainingTypeDeclarations to produce the actual type declarations afterwards. /// </summary> /// <param name="ti"></param> public void IncludeType(TypeInfo ti) { if (VisitedTypes.Contains(ti)) { return; } if (ti.ContainsGenericParameters) { return; } VisitedTypes.Add(ti); if (ti.IsArray) { VisitFieldStructs(ti); IncludeType(ti.ElementType); IncludeType(ti.BaseType); return; } else if (ti.HasElementType) { IncludeType(ti.ElementType); return; } else if (ti.IsEnum) { VisitFieldStructs(ti); IncludeType(ti.GetEnumUnderlyingType()); return; } // Visit all fields first, considering only value types, // so that we can get the layout correct. VisitFieldStructs(ti); if (ti.BaseType != null) { IncludeType(ti.BaseType); } TypeNamer.GetName(ti); foreach (var fi in ti.DeclaredFields) { IncludeType(fi.FieldType); } foreach (var mi in GetFilledVTable(ti)) { if (mi != null && !mi.ContainsGenericParameters) { IncludeMethod(mi); } } TodoTypeStructs.Add(ti); }
// Generate a C declaration for a method private CppFnPtrType GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) { CppType retType; if (method is MethodInfo mi) { retType = mi.ReturnType.FullName == "System.Void" ? types["void"] : AsCType(mi.ReturnType); } else { retType = types["void"]; } var paramNs = CreateNamespace(); paramNs.ReserveName("method"); var paramNamer = paramNs.MakeNamer <ParameterInfo>((pi) => pi.Name == "" ? "arg" : pi.Name.ToCIdentifier()); var paramList = new List <(string, CppType)>(); // Figure out the "this" param if (method.IsStatic) { // In older versions, static methods took a dummy this parameter if (UnityVersion.CompareTo("2018.3.0") < 0) { paramList.Add(("this", types.GetType("void *"))); } } else { if (declaringType.IsValueType) { // Methods for structs take the boxed object as the this param paramList.Add(("this", types.GetType(TypeNamer.GetName(declaringType) + "__Boxed *"))); } else { paramList.Add(("this", AsCType(declaringType))); } } foreach (var pi in method.DeclaredParameters) { paramList.Add((paramNamer.GetName(pi), AsCType(pi.ParameterType))); } paramList.Add(("method", types.GetType("MethodInfo *"))); return(new CppFnPtrType(types.WordSize, retType, paramList) { Name = name }); }
// Generate a C declaration for a method private string GenerateMethodDeclaration(MethodBase method, string name, TypeInfo declaringType) { string retType; if (method is MethodInfo mi) { retType = mi.ReturnType.FullName == "System.Void" ? "void" : AsCType(mi.ReturnType); } else { retType = "void"; } var paramNs = CreateNamespace(); paramNs.ReserveName("method"); var paramNamer = paramNs.MakeNamer <ParameterInfo>((pi) => pi.Name == "" ? "arg" : pi.Name.ToCIdentifier()); var paramList = new List <string>(); // Figure out the "this" param if (method.IsStatic) { // In older versions, static methods took a dummy this parameter if (UnityVersion.CompareTo("2018.3.0") < 0) { paramList.Add("void *this"); } } else { if (declaringType.IsValueType) { // Methods for structs take the boxed object as the this param paramList.Add($"struct {TypeNamer.GetName(declaringType)}__Boxed * this"); } else { paramList.Add($"{AsCType(declaringType)} this"); } } foreach (var pi in method.DeclaredParameters) { paramList.Add($"{AsCType(pi.ParameterType)} {paramNamer.GetName(pi)}"); } paramList.Add($"struct MethodInfo *method"); return($"{retType} {name}({string.Join(", ", paramList)})"); }
// C type declaration used to name variables of the given C# type public string AsCType(TypeInfo ti) { // IsArray case handled by TypeNamer.GetName if (ti.IsByRef || ti.IsPointer) { return($"{AsCType(ti.ElementType)} *"); } else if (ti.IsValueType) { if (ti.IsPrimitive) { switch (ti.Name) { case "Boolean": return("bool"); case "Byte": return("uint8_t"); case "SByte": return("int8_t"); case "Int16": return("int16_t"); case "UInt16": return("uint16_t"); case "Int32": return("int32_t"); case "UInt32": return("uint32_t"); case "Int64": return("int64_t"); case "UInt64": return("uint64_t"); case "IntPtr": return("void *"); case "UIntPtr": return("void *"); case "Char": return("uint16_t"); case "Double": return("double"); case "Single": return("float"); } } return($"struct {TypeNamer.GetName(ti)}"); } else if (ti.IsEnum) { return($"enum {TypeNamer.GetName(ti)}"); } return($"struct {TypeNamer.GetName(ti)} *"); }
// Generate the overall Il2CppClass-shaped structure for the given type private void GenerateTypeStruct(StringBuilder csrc, TypeInfo ti) { var name = TypeNamer.GetName(ti); GenerateVTableStruct(csrc, ti); csrc.Append($"struct {name}__StaticFields {{\n"); var namer = CreateNamespace().MakeNamer <FieldInfo>((field) => field.Name.ToCIdentifier()); foreach (var field in ti.DeclaredFields) { if (field.IsLiteral || !field.IsStatic) { continue; } csrc.Append($" {AsCType(field.FieldType)} {namer.GetName(field)};\n"); } csrc.Append($"}};\n"); /* TODO: type the rgctx_data */ if (UnityVersion.CompareTo("5.5.0") < 0) { csrc.Append( $"struct {name}__Class {{\n" + $" struct Il2CppClass_0 _0;\n" + $" struct {name}__VTable *vtable;\n" + $" Il2CppRuntimeInterfaceOffsetPair *interfaceOffsets;\n" + $" struct {name}__StaticFields *static_fields;\n" + $" const Il2CppRGCTXData *rgctx_data;\n" + $" struct Il2CppClass_1 _1;\n" + $"}};\n"); } else { csrc.Append( $"struct {name}__Class {{\n" + $" struct Il2CppClass_0 _0;\n" + $" Il2CppRuntimeInterfaceOffsetPair *interfaceOffsets;\n" + $" struct {name}__StaticFields *static_fields;\n" + $" const Il2CppRGCTXData *rgctx_data;\n" + $" struct Il2CppClass_1 _1;\n" + $" struct {name}__VTable vtable;\n" + $"}};\n"); } }
// Generate the C structure for virtual function calls in a given type (the VTable) private CppComplexType GenerateVTableStruct(TypeInfo ti) { MethodBase[] vtable; if (ti.IsInterface) { /* Interface vtables are just all of the interface methods. * You might have to type a local variable manually as an * interface vtable during an interface call, but the result * should display the correct method name (with a computed * InterfaceOffset added). */ vtable = ti.DeclaredMethods.ToArray(); } else { vtable = ti.GetVTable(); } var name = TypeNamer.GetName(ti); var namer = CreateNamespace().MakeNamer <int>((i) => vtable[i]?.Name?.ToCIdentifier() ?? "__unknown"); // Il2Cpp switched to `VirtualInvokeData *vtable` in Unity 5.3.6. // Previous versions used `MethodInfo **vtable`. // TODO: Consider adding function types. This considerably increases the script size // but can significantly help with reverse-engineering certain binaries. var vtableStruct = types.Struct(name + "__VTable"); if (UnityVersion.CompareTo("5.3.6") < 0) { for (int i = 0; i < vtable.Length; i++) { types.AddField(vtableStruct, namer.GetName(i), "MethodInfo *"); } } else { for (int i = 0; i < vtable.Length; i++) { types.AddField(vtableStruct, namer.GetName(i), "VirtualInvokeData"); } } return(vtableStruct); }
public CppType AsCType(TypeInfo ti) { // IsArray case handled by TypeNamer.GetName if (ti.IsByRef || ti.IsPointer) { return(AsCType(ti.ElementType).AsPointer(WordSize)); } if (ti.IsValueType) { if (ti.IsPrimitive && primitiveTypeMap.ContainsKey(ti.Name)) { return(types.GetType(primitiveTypeMap[ti.Name])); } return(types.GetType(TypeNamer.GetName(ti))); } if (ti.IsEnum) { return(types.GetType(TypeNamer.GetName(ti))); } return(types.GetType(TypeNamer.GetName(ti) + " *")); }
// Generate the overall Il2CppClass-shaped structure for the given type private (CppComplexType type, CppComplexType staticFields, CppComplexType vtable) GenerateTypeStruct(TypeInfo ti) { var name = TypeNamer.GetName(ti); var vtable = GenerateVTableStruct(ti); var statics = types.Struct(name + "__StaticFields"); var namer = CreateNamespace().MakeNamer <FieldInfo>((field) => field.Name.ToCIdentifier()); foreach (var field in ti.DeclaredFields) { if (field.IsLiteral || !field.IsStatic) { continue; } statics.AddField(namer.GetName(field), AsCType(field.FieldType)); } /* TODO: type the rgctx_data */ var cls = types.Struct(name + "__Class"); types.AddField(cls, "_0", "Il2CppClass_0"); if (UnityVersion.CompareTo("5.5.0") < 0) { cls.AddField("vtable", vtable.AsPointer(types.WordSize)); types.AddField(cls, "interfaceOffsets", "Il2CppRuntimeInterfaceOffsetPair *"); cls.AddField("static_fields", statics.AsPointer(types.WordSize)); types.AddField(cls, "rgctx_data", "Il2CppRGCTXData *", true); types.AddField(cls, "_1", "Il2CppClass_1"); } else { types.AddField(cls, "interfaceOffsets", "Il2CppRuntimeInterfaceOffsetPair *"); cls.AddField("static_fields", statics.AsPointer(types.WordSize)); types.AddField(cls, "rgctx_data", "Il2CppRGCTXData *", true); types.AddField(cls, "_1", "Il2CppClass_1"); cls.AddField("vtable", vtable); } return(cls, statics, vtable); }
// We try decently hard to avoid creating clashing names, and also sanitize any invalid names. // You can customize how naming works by modifying this function. private void InitializeNaming() { TypeNamespace = CreateNamespace(); TypeNamer = TypeNamespace.MakeNamer <TypeInfo>((ti) => { if (ti.IsArray) { return(TypeNamer.GetName(ti.ElementType) + "__Array"); } var name = ti.Name.ToCIdentifier(); 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. if (ti.IsEnum) { name += "__Enum"; } return(name); }); GlobalsNamespace = CreateNamespace(); GlobalNamer = GlobalsNamespace.MakeNamer <MethodBase>((method) => $"{TypeNamer.GetName(method.DeclaringType)}_{method.Name.ToCIdentifier()}"); }
// Generate the C structure for a value type, such as an enum or struct private void GenerateValueFieldStruct(StringBuilder csrc, TypeInfo ti) { string name = TypeNamer.GetName(ti); if (ti.IsEnum) { // Enums should be represented using enum syntax // They otherwise behave like value types csrc.Append($"enum {name} : {AsCType(ti.GetEnumUnderlyingType())} {{\n"); foreach (var field in ti.DeclaredFields) { if (field.Name != "value__") { csrc.Append($" {EnumNamer.GetName(field)} = {field.DefaultValue},\n"); } } csrc.Append($"}};\n"); // Use System.Enum base type as klass csrc.Append($"struct {name}__Boxed {{\n"); GenerateObjectFields(csrc, ti.BaseType); csrc.Append($" {AsCType(ti)} value;\n"); csrc.Append($"}};\n"); } else { // This structure is passed by value, so it doesn't include Il2CppObject fields. csrc.Append($"struct {name} {{\n"); GenerateFieldList(csrc, CreateNamespace(), ti); csrc.Append($"}};\n"); // Also generate the boxed form of the structure which includes the Il2CppObject header. csrc.Append($"struct {name}__Boxed {{\n"); GenerateObjectFields(csrc, ti); csrc.Append($" {AsCType(ti)} fields;\n"); csrc.Append($"}};\n"); } }
// Generate the C structure for a reference type, such as a class or array private (CppComplexType objectOrArrayType, CppComplexType fieldsType) GenerateRefFieldStruct(TypeInfo ti) { var name = TypeNamer.GetName(ti); if (ti.IsArray) { var klassType = ti.IsArray ? ti : ti.BaseType; var elementType = ti.IsArray ? AsCType(ti.ElementType) : types.GetType("void *"); var type = GenerateObjectStruct(name, klassType); types.AddField(type, "bounds", "Il2CppArrayBounds *"); types.AddField(type, "max_length", "il2cpp_array_size_t"); type.AddField("vector", elementType.AsArray(32)); return(type, null); } /* Generate a list of all base classes starting from the root */ List <TypeInfo> baseClasses = new List <TypeInfo>(); for (var bti = ti; bti != null; bti = bti.BaseType) { baseClasses.Add(bti); } baseClasses.Reverse(); var ns = CreateNamespace(); if (InheritanceStyle == CppCompilerType.MSVC) { /* MSVC style: classes directly contain their base class as the first member. * This causes all classes to be aligned to the alignment of their base class. */ TypeInfo firstNonEmpty = null; foreach (var bti in baseClasses) { if (bti.DeclaredFields.Any(field => !field.IsStatic && !field.IsLiteral)) { firstNonEmpty = bti; break; } } if (firstNonEmpty == null) { /* This struct is completely empty. Omit __Fields entirely. */ return(GenerateObjectStruct(name, ti), null); } else { CppComplexType fieldType; if (firstNonEmpty == ti) { /* All base classes are empty, so this class forms the root of a new hierarchy. * We have to be a little careful: the root-most class needs to have its alignment * set to that of Il2CppObject, but we can't explicitly include Il2CppObject * in the hierarchy because we want to customize the type of the klass parameter. */ var align = model.Package.BinaryImage.Bits == 32 ? 4 : 8; fieldType = types.Struct(name + "__Fields", align); GenerateFieldList(fieldType, ns, ti); } else { /* Include the base class fields. Alignment will be dictated by the hierarchy. */ ns.ReserveName("_"); fieldType = types.Struct(name + "__Fields"); var baseFieldType = types[TypeNamer.GetName(ti.BaseType) + "__Fields"]; fieldType.AddField("_", baseFieldType); GenerateFieldList(fieldType, ns, ti); } var type = GenerateObjectStruct(name, ti); types.AddField(type, "fields", name + "__Fields"); return(type, fieldType); } } else if (InheritanceStyle == CppCompilerType.GCC) { /* GCC style: after the base class, all fields in the hierarchy are concatenated. * This saves space (fields are "packed") but requires us to repeat fields from * base classes. */ ns.ReserveName("klass"); ns.ReserveName("monitor"); var type = GenerateObjectStruct(name, ti); foreach (var bti in baseClasses) { GenerateFieldList(type, ns, bti); } return(type, null); } throw new InvalidOperationException("Could not generate ref field struct"); }
// Generate the C structure for a reference type, such as a class or array private void GenerateRefFieldStruct(StringBuilder csrc, TypeInfo ti) { var name = TypeNamer.GetName(ti); if (ti.IsArray || ti.FullName == "System.Array") { var klassType = ti.IsArray ? ti : ti.BaseType; var elementType = ti.IsArray ? AsCType(ti.ElementType) : "void *"; csrc.Append($"struct {name} {{\n"); GenerateObjectFields(csrc, klassType); csrc.Append( $" struct Il2CppArrayBounds *bounds;\n" + $" il2cpp_array_size_t max_length;\n" + $" {elementType} vector[32];\n"); csrc.Append($"}};\n"); return; } /* Generate a list of all base classes starting from the root */ List <TypeInfo> baseClasses = new List <TypeInfo>(); for (var bti = ti; bti != null; bti = bti.BaseType) { baseClasses.Add(bti); } baseClasses.Reverse(); var ns = CreateNamespace(); if (InheritanceStyle == CppCompiler.Type.MSVC) { /* MSVC style: classes directly contain their base class as the first member. * This causes all classes to be aligned to the alignment of their base class. */ TypeInfo firstNonEmpty = null; foreach (var bti in baseClasses) { if (bti.DeclaredFields.Any(field => !field.IsStatic && !field.IsLiteral)) { firstNonEmpty = bti; break; } } if (firstNonEmpty == null) { /* This struct is completely empty. Omit __Fields entirely. */ csrc.Append($"struct {name} {{\n"); GenerateObjectFields(csrc, ti); csrc.Append($"}};\n"); } else { if (firstNonEmpty == ti) { /* All base classes are empty, so this class forms the root of a new hierarchy. * We have to be a little careful: the rootmost class needs to have its alignment * set to that of Il2CppObject, but we can't explicitly include Il2CppObject * in the hierarchy because we want to customize the type of the klass parameter. */ var align = model.Package.BinaryImage.Bits == 32 ? 4 : 8; csrc.Append($"struct __declspec(align({align})) {name}__Fields {{\n"); GenerateFieldList(csrc, ns, ti); csrc.Append($"}};\n"); } else { /* Include the base class fields. Alignment will be dictated by the hierarchy. */ ns.ReserveName("_"); csrc.Append($"struct {name}__Fields {{\n"); csrc.Append($" struct {TypeNamer.GetName(ti.BaseType)}__Fields _;\n"); GenerateFieldList(csrc, ns, ti); csrc.Append($"}};\n"); } csrc.Append($"struct {name} {{\n"); GenerateObjectFields(csrc, ti); csrc.Append($" struct {name}__Fields fields;\n"); csrc.Append($"}};\n"); } } else if (InheritanceStyle == CppCompiler.Type.GCC) { /* GCC style: after the base class, all fields in the hierarchy are concatenated. * This saves space (fields are "packed") but requires us to repeat fields from * base classes. */ ns.ReserveName("klass"); ns.ReserveName("monitor"); csrc.Append($"struct {name} {{\n"); GenerateObjectFields(csrc, ti); foreach (var bti in baseClasses) { GenerateFieldList(csrc, ns, bti); } csrc.Append($"}};\n"); } }
public void Build(CodeWriter writer) { // Create namer TypeNamer namer = new TypeNamer(); // System types with custom keywords namer.Aliases[Type.Module.TypeSystem.Boolean.FullName] = "bool"; namer.Aliases[Type.Module.TypeSystem.Byte.FullName] = "byte"; namer.Aliases[Type.Module.TypeSystem.Char.FullName] = "char"; namer.Aliases[Type.Module.TypeSystem.Double.FullName] = "double"; namer.Aliases[Type.Module.TypeSystem.Int16.FullName] = "short"; namer.Aliases[Type.Module.TypeSystem.Int32.FullName] = "int"; namer.Aliases[Type.Module.TypeSystem.Int64.FullName] = "long"; namer.Aliases[Type.Module.TypeSystem.Object.FullName] = "object"; namer.Aliases[Type.Module.TypeSystem.SByte.FullName] = "bool"; namer.Aliases[Type.Module.TypeSystem.Single.FullName] = "float"; namer.Aliases[Type.Module.TypeSystem.String.FullName] = "string"; namer.Aliases[Type.Module.TypeSystem.UInt16.FullName] = "ushort"; namer.Aliases[Type.Module.TypeSystem.UInt32.FullName] = "uint"; namer.Aliases[Type.Module.TypeSystem.UInt64.FullName] = "ulong"; namer.Aliases[Type.Module.TypeSystem.Void.FullName] = "void"; // This type namer.Aliases[this.Type.FullName] = this.Type.Name; // Information writer.WriteLine($"// Assembly: {Type.Module.Assembly.Name}"); writer.WriteLine($"// Module: {Type.Module.Name}"); writer.WriteLine($"// Type: {Type.Name}"); // Namespace writer.WriteLine($"namespace {Type.Namespace}"); writer.WriteLine("{"); writer.AddIndent(); // Modifiers writer.WriteIndent(); if (Type.IsPublic) { writer.Write("public "); } else if (Type.IsNotPublic) { writer.Write("private "); } if (Type.IsAbstract && Type.IsSealed) { writer.Write("static "); } else if (Type.IsAbstract) { writer.Write("abstract "); } else if (Type.IsSealed) { writer.Write("sealed "); } // Class name writer.Write($"class {Type.Name}"); // Inherited types List <string> inherited = new List <string>(); if (Type.BaseType != null && Type.BaseType.FullName != Type.Module.TypeSystem.Object.FullName) { inherited.Add(namer.GetName(Type.BaseType)); } inherited.AddRange(from i in Type.Interfaces select namer.GetName(i)); if (inherited.Any()) { writer.Write($" : {string.Join(", ", inherited)}"); } // Opening brace - Type writer.WriteLine(); writer.WriteLine("{"); writer.AddIndent(); // Write each method foreach (MethodDefinition method in Type.Methods) { MethodBuilder builder = new MethodBuilder(method, namer); builder.Build(writer); writer.WriteLine(); } // Closing brace - Type writer.RemoveIndent(); writer.WriteLine("}"); // Closing brace - Namespace writer.RemoveIndent(); writer.WriteLine("}"); }
// Generate the fields for the base class of all objects (Il2CppObject) // The two fields are inlined so that we can specialize the klass member for each type object private void GenerateObjectFields(StringBuilder csrc, TypeInfo ti) { csrc.Append( $" struct {TypeNamer.GetName(ti)}__Class *klass;\n" + $" struct MonitorData *monitor;\n"); }