// Parse a block of C++ source code, adding any types found public void AddFromDeclarationText(string text) { using StringReader lines = new StringReader(text); var rgxForwardDecl = new Regex(@"(struct|union)\s+(\S+);"); var rgxTypedefAlias = new Regex(@"typedef\s+(?:(struct|union)\s+)?(\S+)\s+(\S+);"); var rgxTypedefFnPtr = new Regex(@"typedef\s+(?:struct\s+)?" + CppFnPtrType.Regex + ";"); var rgxDefinition = new Regex(@"^(typedef\s+)?(struct|union|enum)"); var rgxFieldFnPtr = new Regex(CppFnPtrType.Regex + @";"); var rgxField = new Regex(@"^(?:struct\s+|enum\s+)?(\S+?\s*\**)\s*((?:\S|\s*,\s*)+)(?:\s*:\s*([0-9]+))?;"); var rgxEnumValue = new Regex(@"^\s*([A-Za-z0-9_]+)(?:\s*=\s*(.+?))?,?\s*$"); var rgxIsConst = new Regex(@"\bconst\b"); var rgxStripKeywords = new Regex(@"\b(?:const|unsigned|volatile)\b"); var rgxCompressPtrs = new Regex(@"\*\s+\*"); var rgxArrayField = new Regex(@"(\S+?)\[([0-9]+)\]"); var rgxAlignment = new Regex(@"__attribute__\(\(aligned\(([0-9]+)\)\)\)\s+"); var rgxIsBitDirective = new Regex(@"#ifdef\s+IS_(32|64)BIT"); var rgxSingleLineComment = new Regex(@"/\*.*?\*/"); var currentType = new Stack <CppComplexType>(); bool falseIfBlock = false; bool inComment = false; bool inMethod = false; bool inTypedef = false; var nextEnumValue = 0ul; string rawLine, line; while ((rawLine = line = lines.ReadLine()) != null) { // Remove comments if (line.Contains("//")) { line = line.Substring(0, line.IndexOf("//", StringComparison.Ordinal)); } // End of multi-line comment? if (line.Contains("*/") && inComment) { inComment = false; line = line.Substring(line.IndexOf("*/", StringComparison.Ordinal) + 2); } if (inComment) { Debug.WriteLine($"[COMMENT ] {line}"); continue; } // Remove all single-line comments line = rgxSingleLineComment.Replace(line, ""); // Start of multi-line comment? if (line.Contains("/*") && !inComment) { inComment = true; line = line.Substring(0, line.IndexOf("/*")); } // Ignore global variables if (line.StartsWith("const ") && currentType.Count == 0) { Debug.WriteLine($"[GLOBAL ] {line}"); continue; } // Ignore methods // Note: This is a very lazy way of processing early version IL2CPP headers if (line != "}" && inMethod) { Debug.WriteLine($"[METHOD ] {line}"); continue; } if (line == "}" && inMethod) { inMethod = false; Debug.WriteLine($"[METHOD END ] {line}"); continue; } if (line.StartsWith("static inline ")) { inMethod = true; Debug.WriteLine($"[METHOD START ] {line}"); continue; } // Remove keywords we don't care about line = rgxStripKeywords.Replace(line, ""); // Remove whitespace in multiple indirections line = rgxCompressPtrs.Replace(line, "**"); // Process __attribute((aligned(x))) var alignment = 0; var alignmentMatch = rgxAlignment.Match(line); if (alignmentMatch.Success) { alignment = int.Parse(alignmentMatch.Groups[1].Captures[0].ToString()); line = rgxAlignment.Replace(line, ""); } line = line.Trim(); // Ignore blank lines if (line.Length == 0) { continue; } // Ignore defines if (line.StartsWith("#define")) { continue; } // Process #ifdefs before anything else // Doesn't handle nesting but we probably don't need to (use a Stack if we do) var ifdef = rgxIsBitDirective.Match(line); if (ifdef.Success) { var bits = int.Parse(ifdef.Groups[1].Captures[0].ToString()); if (bits != WordSize) { falseIfBlock = true; } Debug.WriteLine($"[IF ] {line}"); continue; } // Other #ifdef if (line.StartsWith("#ifdef") || line.StartsWith("#if ")) { falseIfBlock = true; Debug.WriteLine($"[IF ] {line}"); continue; } if (line.StartsWith("#ifndef")) { falseIfBlock = false; Debug.WriteLine($"[IF ] {line}"); continue; } if (line == "#else") { falseIfBlock = !falseIfBlock; Debug.WriteLine($"[ELSE ] {line}"); continue; } if (line == "#endif") { falseIfBlock = false; Debug.WriteLine($"[ENDIF ] {line}"); continue; } if (falseIfBlock) { Debug.WriteLine($"[FALSE ] {line}"); continue; } // Forward declaration // <struct|union> <external-type>; var externDecl = rgxForwardDecl.Match(line); if (externDecl.Success) { var complexType = complexTypeMap[externDecl.Groups[1].Captures[0].ToString()]; var declType = externDecl.Groups[2].Captures[0].ToString(); switch (complexType) { case ComplexValueType.Struct: Struct(declType); break; case ComplexValueType.Union: Union(declType); break; } Debug.WriteLine($"[FORWARD DECL ] {line}"); continue; } // Typedef alias // typedef <struct|union> <existing-type> <alias> var typedef = rgxTypedefAlias.Match(line); if (typedef.Success) { //var complexType = complexTypeMap[typedef.Groups[1].Captures[0].ToString()]; var existingType = typedef.Groups[2].Captures[0].ToString(); var alias = typedef.Groups[3].Captures[0].ToString(); // Sometimes we might get multiple forward declarations for the same type // Potential multiple indirection var type = GetType(existingType); // C++ allows the same typedef to be defined more than once TypedefAliases.TryAdd(alias, type); var pointers = line.Count(c => c == '*'); Debug.WriteLine($"[TYPEDEF {(pointers > 0 ? "PTR" : "VAL")} ] {line} -- Adding typedef from {type.Name} to {alias}"); continue; } // Function pointer alias // typedef <retType> (*<alias>)(<args>); typedef = rgxTypedefFnPtr.Match(line); if (typedef.Success) { var alias = typedef.Groups[2].Captures[0].ToString(); var fnPtrType = CppFnPtrType.FromSignature(this, line); fnPtrType.Group = currentGroup; TypedefAliases.Add(alias, fnPtrType); Debug.WriteLine($"[TYPEDEF FNPTR] {line} -- Adding method pointer typedef to {alias}"); continue; } // Start of struct/union/enum // [typedef] <struct|union|enum> [optional-tag-name] var definition = rgxDefinition.Match(line); if (definition.Success && line.IndexOf(";", StringComparison.Ordinal) == -1 && currentType.Count == 0) { // Must have a name if not a typedef, might have a name if it is var split = line.Split(' '); if (split[0] == "typedef") { split = split.Skip(1).ToArray(); } var name = split.Length > 1 && split[1] != "{" ? split[1] : ""; currentType.Push(complexTypeMap[split[0]] switch { ComplexValueType.Struct => Struct(name), ComplexValueType.Union => Union(name), ComplexValueType.Enum => NewDefaultEnum(name), _ => throw new InvalidOperationException("Unknown complex type") });
// Parse a block of C++ source code, adding any types found public void AddFromDeclarationText(string text) { using StringReader lines = new StringReader(text); var rgxExternDecl = new Regex(@"struct (\S+);"); var rgxTypedefForwardDecl = new Regex(@"typedef struct (\S+) (\S+);"); var rgxTypedefFnPtr = new Regex(@"typedef\s+(?:struct )?" + CppFnPtrType.Regex + ";"); var rgxTypedef = new Regex(@"typedef (\S+?)\s*\**\s*(\S+);"); var rgxFieldFnPtr = new Regex(CppFnPtrType.Regex + @";"); var rgxField = new Regex(@"^(?:struct |enum )?(\S+?)\s*\**\s*((?:\S|\s*,\s*)+)(?:\s*:\s*([0-9]+))?;"); var rgxEnumValue = new Regex(@"^\s*([A-Za-z0-9_]+)(?:\s*=\s*(.+?))?,?\s*$"); var rgxStripKeywords = new Regex(@"\b(?:const|unsigned|volatile)\b"); var rgxCompressPtrs = new Regex(@"\*\s+\*"); var rgxArrayField = new Regex(@"(\S+?)\[([0-9]+)\]"); var rgxAlignment = new Regex(@"__attribute__\(\(aligned\(([0-9]+)\)\)\)"); var rgxIsBitDirective = new Regex(@"#ifdef\s+IS_(32|64)BIT"); var rgxSingleLineComment = new Regex(@"/\*.*?\*/"); var currentType = new Stack <CppComplexType>(); bool falseIfBlock = false; bool inComment = false; bool inMethod = false; var nextEnumValue = 0ul; string line; while ((line = lines.ReadLine()) != null) { // Remove comments if (line.Contains("//")) { line = line.Substring(0, line.IndexOf("//", StringComparison.Ordinal)); } // End of multi-line comment? if (line.Contains("*/") && inComment) { inComment = false; line = line.Substring(line.IndexOf("*/", StringComparison.Ordinal) + 2); } if (inComment) { Debug.WriteLine($"[COMMENT ] {line}"); continue; } // Remove all single-line comments line = rgxSingleLineComment.Replace(line, ""); // Start of multi-line comment? if (line.Contains("/*") && !inComment) { inComment = true; line = line.Substring(0, line.IndexOf("/*")); } // Ignore global variables if (line.StartsWith("const ") && currentType.Count == 0) { Debug.WriteLine($"[GLOBAL ] {line}"); continue; } // Ignore methods // Note: This is a very lazy way of processing early version IL2CPP headers if (line != "}" && inMethod) { Debug.WriteLine($"[METHOD ] {line}"); continue; } if (line == "}" && inMethod) { inMethod = false; Debug.WriteLine($"[METHOD END ] {line}"); continue; } if (line.StartsWith("static inline ")) { inMethod = true; Debug.WriteLine($"[METHOD START ] {line}"); continue; } // Remove keywords we don't care about line = rgxStripKeywords.Replace(line, ""); // Remove whitespace in multiple indirections line = rgxCompressPtrs.Replace(line, "**"); // Process __attribute((aligned(x))) var alignment = 0; var alignmentMatch = rgxAlignment.Match(line); if (alignmentMatch.Success) { alignment = int.Parse(alignmentMatch.Groups[1].Captures[0].ToString()); line = rgxAlignment.Replace(line, ""); } line = line.Trim(); // Ignore blank lines if (line.Length == 0) { continue; } // Process #ifs before anything else // Doesn't handle nesting but we probably don't need to (use a Stack if we do) var ifdef = rgxIsBitDirective.Match(line); if (ifdef.Success) { var bits = int.Parse(ifdef.Groups[1].Captures[0].ToString()); if (bits != WordSize) { falseIfBlock = true; } Debug.WriteLine($"[IF ] {line}"); continue; } if (line == "#else") { falseIfBlock = !falseIfBlock; Debug.WriteLine($"[ELSE ] {line}"); continue; } if (line == "#endif") { falseIfBlock = false; Debug.WriteLine($"[ENDIF ] {line}"); continue; } if (falseIfBlock) { Debug.WriteLine($"[FALSE ] {line}"); continue; } // External declaration // struct <external-type>; // NOTE: Unfortunately we're not going to ever know the size of this type var externDecl = rgxExternDecl.Match(line); if (externDecl.Success) { var declType = externDecl.Groups[1].Captures[0].ToString(); Types.Add(declType, CppType.NewStruct(declType)); Debug.WriteLine($"[EXTERN DECL ] {line}"); continue; } // Forward declaration // typedef struct <struct-type> <alias> var typedef = rgxTypedefForwardDecl.Match(line); if (typedef.Success) { var alias = typedef.Groups[2].Captures[0].ToString(); var declType = typedef.Groups[1].Captures[0].ToString(); // Sometimes we might get multiple forward declarations for the same type if (!Types.ContainsKey(declType)) { Types.Add(declType, CppType.NewStruct(declType)); } // Sometimes the alias might be the same name as the type (this is usually the case) if (!Types.ContainsKey(alias)) { Types.Add(alias, Types[declType].AsAlias(alias)); } Debug.WriteLine($"[FORWARD DECL ] {line}"); continue; } // Function pointer // typedef <retType> (*<alias>)(<args>); typedef = rgxTypedefFnPtr.Match(line); if (typedef.Success) { var alias = typedef.Groups[2].Captures[0].ToString(); var fnPtrType = CppFnPtrType.FromSignature(this, line); fnPtrType.Name = alias; Types.Add(alias, fnPtrType); Debug.WriteLine($"[TYPEDEF FNPTR] {line} -- Adding method pointer typedef to {alias}"); continue; } // Alias // typedef <targetType>[*..] <alias>; typedef = rgxTypedef.Match(line); if (typedef.Success) { var alias = typedef.Groups[2].Captures[0].ToString(); var existingType = typedef.Groups[1].Captures[0].ToString(); // Potential multiple indirection var type = Types[existingType]; var pointers = line.Count(c => c == '*'); for (int i = 0; i < pointers; i++) { type = type.AsPointer(WordSize); } Types.Add(alias, type.AsAlias(alias)); Debug.WriteLine($"[TYPEDEF {(pointers > 0? "PTR":"VAL")} ] {line} -- Adding typedef from {type.Name} to {alias}"); continue; } // Start of struct // typedef struct <optional-type-name> if ((line.StartsWith("typedef struct") || line.StartsWith("struct ")) && line.IndexOf(";", StringComparison.Ordinal) == -1 && currentType.Count == 0) { currentType.Push(CppType.NewStruct()); if (line.StartsWith("struct ")) { currentType.Peek().Name = line.Split(' ')[1]; } Debug.WriteLine($"\n[STRUCT START ] {line}"); continue; } // Start of union // typedef union <optional-type-name> if (line.StartsWith("typedef union") && line.IndexOf(";", StringComparison.Ordinal) == -1) { currentType.Push(CppType.NewUnion()); Debug.WriteLine($"\n[UNION START ] {line}"); continue; } // Start of enum // typedef enum <optional-type-name> if (line.StartsWith("typedef enum") && line.IndexOf(";", StringComparison.Ordinal) == -1) { currentType.Push(NewDefaultEnum()); nextEnumValue = 0; Debug.WriteLine($"\n[ENUM START ] {line}"); continue; } // Nested complex field // struct <optional-type-name> // union <optional-type-name> var words = line.Split(' '); if ((words[0] == "union" || words[0] == "struct") && words.Length <= 2) { currentType.Push(words[0] == "struct" ? CppType.NewStruct() : CppType.NewUnion()); Debug.WriteLine($"[FIELD START ] {line}"); continue; } // End of already named struct if (line == "};" && currentType.Count == 1) { var ct = currentType.Pop(); if (!Types.ContainsKey(ct.Name)) { Types.Add(ct.Name, ct); } else { ((CppComplexType)Types[ct.Name]).Fields = ct.Fields; } Debug.WriteLine($"[STRUCT END ] {line} -- {ct.Name}\n"); continue; } // End of complex field, complex type or enum // end of [typedef] struct/union/enum if (line.StartsWith("}") && line.EndsWith(";")) { var name = line[1..^ 1].Trim();