backup of scripts before json parser changes

This commit is contained in:
EricPlayZ
2025-03-13 00:30:18 +02:00
parent edad5f4ead
commit 12de78c264
7 changed files with 294 additions and 172 deletions

View File

@ -1,48 +1,86 @@
from dataclasses import dataclass, field
from typing import Optional
from ExportClassH import Utils
from ExportClassH import Utils, HeaderGen
@dataclass(frozen=True)
class ClassName:
"""Split a potentially namespaced class name into namespace parts and class name."""
namespaces: tuple[str] = field(default_factory=tuple[str])
namespaces: tuple[str] = field(default_factory=tuple)
classes: tuple[str] = field(default_factory=tuple)
name: str = ""
namespacedName: str = ""
namespacedClassedName: str = ""
templateParams: tuple["ClassName"] = field(default_factory=tuple)
templatedName: str = ""
templatedNamespacedClassedName: str = ""
fullName: str = ""
type: str = ""
def __init__(self, fullName: str):
object.__setattr__(self, "namespaces", ())
object.__setattr__(self, "classes", ())
object.__setattr__(self, "name", "")
object.__setattr__(self, "namespacedName", "")
object.__setattr__(self, "namespacedClassedName", "")
object.__setattr__(self, "templateParams", ())
object.__setattr__(self, "templatedName", "")
object.__setattr__(self, "templatedNamespacedClassedName", "")
object.__setattr__(self, "fullName", "")
object.__setattr__(self, "type", "")
fullName = (fullName or "").strip()
fullName = fullName.strip()
if not fullName:
return
object.__setattr__(self, "fullName", fullName)
types = Utils.ExtractTypesFromString(fullName)
types = Utils.ExtractTypeTokensFromString(fullName)
if len(types) > 1:
if (types[0] in Utils.CLASS_TYPES):
object.__setattr__(self, "type", types[0])
fullName = types[1]
elif (len(types) > 2 and types[0] in Utils.FUNC_QUALIFIERS and types[1] in Utils.CLASS_TYPES):
object.__setattr__(self, "type", types[1])
fullName = types[2]
if (types[0].strip() in Utils.CLASS_TYPES):
object.__setattr__(self, "type", types[0].strip())
fullName = types[1].strip()
elif (len(types) > 2 and types[0].strip() in Utils.FUNC_QUALIFIERS and types[1].strip() in Utils.CLASS_TYPES):
object.__setattr__(self, "type", types[1].strip())
fullName = types[2].strip()
else:
return
lastClassSeparatorIndex: int = Utils.FindLastClassSeparatorOutsideTemplates(fullName)
namespacesStr: str = ""
className: str = fullName
templatedClassName: str = fullName
if lastClassSeparatorIndex != -1:
namespacesStr = fullName[:lastClassSeparatorIndex].strip()
templatedClassName = fullName[lastClassSeparatorIndex+2:].strip()
className = templatedClassName
namespaces: list[str] = list(filter(None, namespacesStr.split("::")))
classes: list[str] = []
parts = fullName.split("::")
if len(parts) == 1:
object.__setattr__(self, "name", parts[0])
object.__setattr__(self, "namespacedName", self.name)
else:
object.__setattr__(self, "namespaces", tuple(parts[:-1]))
object.__setattr__(self, "name", parts[-1])
object.__setattr__(self, "namespacedName", f"{'::'.join(self.namespaces)}::{self.name}")
parsedNamespaces: list[str] = []
for namespace in namespaces:
fullNamespace: str = "::".join(filter(None, ["::".join(parsedNamespaces), namespace]))
if HeaderGen.IsClassGenerable(ClassName(fullNamespace)):
classes = namespaces
break
parsedNamespaces.append(namespace)
namespaces.pop(0)
namespacesAndClasses: str = "::".join(filter(None, ["::".join(namespaces).strip(), "::".join(classes).strip()]))
templateParamsOpenIndex: int = templatedClassName.find('<')
templateParamsCloseIndex: int = templatedClassName.rfind('>')
templateParamsList: list[ClassName] = []
if templateParamsOpenIndex != -1 and templateParamsCloseIndex != -1:
templateParams: str = templatedClassName[templateParamsOpenIndex + 1:templateParamsCloseIndex].strip()
templateParamsStrList: list[str] = Utils.SplitByCommaOutsideTemplates(templateParams)
templateParamsList = [ClassName(templateParamStr) for templateParamStr in templateParamsStrList if templateParamStr]
className = templatedClassName[:templateParamsOpenIndex].strip()
object.__setattr__(self, "namespaces", tuple(namespaces))
object.__setattr__(self, "classes", tuple(classes))
object.__setattr__(self, "name", className)
object.__setattr__(self, "namespacedClassedName", "::".join(filter(None, [namespacesAndClasses, className])).strip())
object.__setattr__(self, "templateParams", tuple(templateParamsList))
object.__setattr__(self, "templatedName", templatedClassName)
object.__setattr__(self, "templatedNamespacedClassedName", fullName)
virtualFuncPlaceholderCounter: int = 0 # Counter for placeholder virtual functions
virtualFuncDuplicateCounter: dict[str, int] = {} # Counter for duplicate virtual functions

View File

@ -4,18 +4,25 @@ import idc
from typing import Optional
from ExportClassH import Utils, Config, RTTIAnalyzer
from ExportClassH.ClassDefs import ClassName, ParsedFunction, ParsedClassVar
# Global caches
parsedClassVarsByClass: dict[str, list[ParsedClassVar]] = {} # Cache of parsed class vars by class name
parsedVTableFuncsByClass: dict[str, list[ParsedFunction]] = {} # Cache of parsed functions by class name
parsedFuncsByClass: dict[str, list[ParsedFunction]] = {} # Cache of parsed functions by class name
allParsedFuncs: list[ParsedFunction] = []
parsedClassVarsByClass: dict[str, list[dict]] = {} # Cache of parsed class vars by class name
parsedVTableFuncsByClass: dict[str, list[dict]] = {} # Cache of parsed functions by class name
parsedFuncsByClass: dict[str, list[dict]] = {} # Cache of parsed functions by class name
allParsedFuncs: list[dict] = []
unparsedExportedSigs: list[str] = []
allClassVarsAreParsed = False # Flag to indicate if all class vars have been parsed
allFuncsAreParsed = False # Flag to indicate if all functions have been parsed
def CreateParamNamesForVTFunc(parsedFunc: ParsedFunction, skipFirstParam: bool) -> str:
def IsClassGenerable(cls: dict) -> bool:
"""
Check if a class has any parsable elements (class vars, vtable functions, regular functions).
Returns True if the class is generable, False if it should be treated as a namespace.
"""
(parsedVars, parsedVTFuncs, parsedFuncs) = GetAllParsedClassVarsAndFuncs(cls)
return len(parsedVars) > 0 or len(parsedVTFuncs) > 0 or len(parsedFuncs) > 0
def CreateParamNamesForVTFunc(parsedFunc: dict, skipFirstParam: bool) -> str:
paramsList: list[str] = [param.fullName for param in parsedFunc.params if param.fullName]
if len(paramsList) == 1 and paramsList[0] == "void":
return "void"
@ -45,19 +52,19 @@ def GetClassTypeFromParsedSigs(targetClass: ClassName, allParsedElements: tuple[
# Check class vars first
for parsedClassVar in parsedClassVars:
if (parsedClassVar.varType and
parsedClassVar.varType.namespacedName == targetClass.namespacedName and
parsedClassVar.varType.namespacedClassedName == targetClass.namespacedClassedName and
parsedClassVar.varType.type):
return parsedClassVar.varType.type
# Check vtable functions next
for parsedVTFunc in parsedVtFuncs:
if (parsedVTFunc.returnType and
parsedVTFunc.returnType.namespacedName == targetClass.namespacedName and
parsedVTFunc.returnType.namespacedClassedName == targetClass.namespacedClassedName and
parsedVTFunc.returnType.type):
return parsedVTFunc.returnType.type
# Check all parsed functions last
for parsedFunc in allParsedFuncs:
if (parsedFunc.returnType and
parsedFunc.returnType.namespacedName == targetClass.namespacedName and
parsedFunc.returnType.namespacedClassedName == targetClass.namespacedClassedName and
parsedFunc.returnType.type):
return parsedFunc.returnType.type
@ -86,7 +93,7 @@ def GetDemangledExportedSigs() -> list[str]:
sigs_set.add(demangledExportedSig)
return list(sigs_set)
def GetParsedClassVars(targetClass: Optional[ClassName] = None) -> list[ParsedClassVar]:
def GetParsedClassVars(targetClass: dict = {}) -> list[ParsedClassVar]:
"""
Collect and parse all class var signatures from the IDA database.
If target_class is provided, only return class vars for that class.
@ -126,7 +133,7 @@ def GetParsedClassVars(targetClass: Optional[ClassName] = None) -> list[ParsedCl
print(f"Failed parsing class var sig: \"{sig}\"")
continue
parsedClassVarsByClass.setdefault(parsedVar.className.namespacedName, []).append(parsedVar)
parsedClassVarsByClass.setdefault(parsedVar.className.namespacedClassedName, []).append(parsedVar)
allClassVarsAreParsed = True
@ -146,7 +153,7 @@ def GetParsedClassVars(targetClass: Optional[ClassName] = None) -> list[ParsedCl
if targetClass is None:
return [var for vars_list in parsedClassVarsByClass.values() for var in vars_list]
else:
return parsedClassVarsByClass.get(targetClass.namespacedName, [])
return parsedClassVarsByClass.get(targetClass.namespacedClassedName, [])
def GetParsedVTableFuncs(targetClass: ClassName) -> list[ParsedFunction]:
"""
@ -157,27 +164,27 @@ def GetParsedVTableFuncs(targetClass: ClassName) -> list[ParsedFunction]:
global parsedVTableFuncsByClass
if targetClass not in parsedVTableFuncsByClass:
parsedVTableFuncsByClass[targetClass.namespacedName] = []
parsedVTableFuncsByClass[targetClass.namespacedClassedName] = []
for (demangledFuncSig, rawType) in RTTIAnalyzer.GetDemangledVTableFuncSigs(targetClass):
if rawType:
parsedFunc: ParsedFunction = ParsedFunction(rawType, True)
if parsedFunc.returnType:
newParamTypes: str = CreateParamNamesForVTFunc(parsedFunc, True) if parsedFunc.params else ""
demangledFuncSig = f"{'DUPLICATE_FUNC ' if demangledFuncSig.startswith('DUPLICATE_FUNC') else ''}IDA_GEN_PARSED virtual {parsedFunc.returnType.namespacedName} {demangledFuncSig.removeprefix('DUPLICATE_FUNC').strip()}({newParamTypes})"
demangledFuncSig = f"{'DUPLICATE_FUNC ' if demangledFuncSig.startswith('DUPLICATE_FUNC') else ''}IDA_GEN_PARSED virtual {parsedFunc.returnType.namespacedClassedName} {demangledFuncSig.removeprefix('DUPLICATE_FUNC').strip()}({newParamTypes})"
elif demangledFuncSig.startswith("DUPLICATE_FUNC"):
parsedFunc: ParsedFunction = ParsedFunction(demangledFuncSig.removeprefix("DUPLICATE_FUNC").strip(), True)
if parsedFunc.returnType:
newParamTypes: str = CreateParamNamesForVTFunc(parsedFunc, False) if parsedFunc.params else ""
demangledFuncSig = f"DUPLICATE_FUNC {parsedFunc.returnType.namespacedName} {parsedFunc.funcName}({newParamTypes})"
demangledFuncSig = f"DUPLICATE_FUNC {parsedFunc.returnType.namespacedClassedName} {parsedFunc.funcName}({newParamTypes})"
parsedFunc: ParsedFunction = ParsedFunction(demangledFuncSig, True)
if not parsedFunc.className:
object.__setattr__(parsedFunc, "className", targetClass)
parsedVTableFuncsByClass[targetClass.namespacedName].append(parsedFunc)
parsedVTableFuncsByClass[targetClass.namespacedClassedName].append(parsedFunc)
return parsedVTableFuncsByClass.get(targetClass.namespacedName, [])
return parsedVTableFuncsByClass.get(targetClass.namespacedClassedName, [])
def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFunction]:
"""
@ -209,7 +216,7 @@ def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFuncti
if not parsedFunc.type or not parsedFunc.className:
print(f"Failed parsing func sig: \"{demangledFuncSig}\"")
continue
parsedFuncsByClass.setdefault(parsedFunc.className.namespacedName, []).append(parsedFunc)
parsedFuncsByClass.setdefault(parsedFunc.className.namespacedClassedName, []).append(parsedFunc)
allFuncsAreParsed = True
try:
os.makedirs(Config.CACHE_OUTPUT_PATH, exist_ok=True)
@ -225,23 +232,23 @@ def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFuncti
if targetClass is None:
return [pf for funcList in parsedFuncsByClass.values() for pf in funcList]
else:
return parsedFuncsByClass.get(targetClass.namespacedName, [])
return parsedFuncsByClass.get(targetClass.namespacedClassedName, [])
def GetAllParsedClassVarsAndFuncs(targetClass: ClassName) -> tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]:
def GetAllParsedClassVarsAndFuncs(cls: dict) -> tuple[list[dict], list[dict], list[dict]]:
global allParsedFuncs
parsedVTableClassFuncs: list[ParsedFunction] = GetParsedVTableFuncs(targetClass)
parsedVTableClassFuncs: list[dict] = GetParsedVTableFuncs(cls)
if not parsedVTableClassFuncs:
print(f"No matching VTable function signatures were found for {targetClass.namespacedName}.")
print(f"No matching VTable function signatures were found for {cls.namespacedClassedName}.")
parsedClassFuncs: list[ParsedFunction] = GetParsedFuncs(targetClass)
parsedClassFuncs: list[dict] = GetParsedFuncs(cls)
if not parsedClassFuncs:
print(f"No matching function signatures were found for {targetClass.namespacedName}.")
print(f"No matching function signatures were found for {cls.namespacedClassedName}.")
allParsedFuncs = GetParsedFuncs()
parsedClassVars: list[ParsedClassVar] = GetParsedClassVars(targetClass)
parsedClassVars: list[dict] = GetParsedClassVars(cls)
if not parsedClassVars:
print(f"No matching class var signatures were found for {targetClass.namespacedName}.")
print(f"No matching class var signatures were found for {cls.namespacedClassedName}.")
# Get non-vtable methods
vTableFuncsSet: set[str] = {pf.fullFuncSig for pf in parsedVTableClassFuncs}
@ -250,11 +257,9 @@ def GetAllParsedClassVarsAndFuncs(targetClass: ClassName) -> tuple[list[ParsedCl
if parsedFunc.fullFuncSig not in vTableFuncsSet
]
allParsedElements = (parsedClassVars, parsedVTableClassFuncs, finalParsedClassFuncs)
allParsedElements = parsedClassVars, parsedVTableClassFuncs, finalParsedClassFuncs
# Get and set class type if available
classType: str = GetClassTypeFromParsedSigs(targetClass, allParsedElements)
if classType:
object.__setattr__(targetClass, "type", classType)
# Set class type if available
cls["type"] = GetClassTypeFromParsedSigs(cls, allParsedElements)
return allParsedElements

View File

@ -3,49 +3,14 @@ import os
from ExportClassH import Utils, Config, ClassParser
from ExportClassH.ClassDefs import ClassName, ParsedFunction, ParsedClassVar
def IsClassGenerable(className: ClassName) -> bool:
"""
Check if a class has any parsable elements (class vars, vtable functions, regular functions).
Returns True if the class is generable, False if it should be treated as a namespace.
"""
parsedVars = ClassParser.GetParsedClassVars(className)
parsedVtFuncs = ClassParser.GetParsedVTableFuncs(className) if className else []
parsedFuncs = ClassParser.GetParsedFuncs(className)
return len(parsedVars) > 0 or len(parsedVtFuncs) > 0 or len(parsedFuncs) > 0
def IdentifyClassHierarchy(targetClass: ClassName) -> list[tuple[ClassName, bool]]:
"""
Given a class name with namespace/nested parts, identify which parts are classes
and which are namespaces.
Returns a list of tuples (ClassName, isClass) for each part of the hierarchy.
"""
if not targetClass.namespaces:
return [(targetClass, IsClassGenerable(targetClass))]
hierarchy = []
# Check each part of the namespace to see if it's a class
currentNamespace = []
for part in targetClass.namespaces:
currentNamespace.append(part)
partClass = ClassName("::".join(currentNamespace))
isClass = IsClassGenerable(partClass)
hierarchy.append((partClass, isClass))
# Add the target class at the end
hierarchy.append((targetClass, IsClassGenerable(targetClass)))
return hierarchy
processedClasses: set[str] = set()
currentAccess: str = "public"
def GenerateClassVarCode(classVar: ParsedClassVar) -> str:
def GenerateClassVarCode(classVar: ParsedClassVar) -> list[str]:
"""Generate code for a single class variable."""
global currentAccess
access: str = f"{classVar.access}:\n\t" if classVar.access else "\t"
access: str = f"{classVar.access}:" if classVar.access else ""
if currentAccess == classVar.access:
access = "\t"
else:
@ -61,15 +26,21 @@ def GenerateClassVarCode(classVar: ParsedClassVar) -> str:
varType: str = ""
classVarSig: str = f"{varType}{classVar.varName}"
return f"{access}{classVarSig};"
classVarLines: list[str] = []
if access:
classVarLines.append(access)
if classVarSig:
classVarLines.append(f"\t{classVarSig};")
return classVarLines
def GenerateClassFuncCode(func: ParsedFunction, vtFuncIndex: int = 0) -> str:
def GenerateClassFuncCode(func: ParsedFunction, vtFuncIndex: int = 0) -> list[str]:
"""Generate code for a single class method."""
global currentAccess
access: str = f"{func.access}:\n\t" if func.access else "\t"
access: str = f"{func.access}:" if func.access else ""
if currentAccess == func.access:
access = "\t"
access = ""
else:
currentAccess = func.access
@ -106,7 +77,13 @@ def GenerateClassFuncCode(func: ParsedFunction, vtFuncIndex: int = 0) -> str:
targetParams = ", " + targetParams if targetParams else ""
funcSig: str = f"{returnType}{func.funcName}({params}){const}{stripped_vfunc}" if func.type != "basic_vfunc" else f"VIRTUAL_CALL({vtFuncIndex}, {returnType}, {func.funcName}, ({params}){targetParams})"
return f"{access}{funcSig};"
classFuncLines: list[str] = []
if access:
classFuncLines.append(access)
if funcSig:
classFuncLines.append(f"\t{funcSig};")
return classFuncLines
def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> list[str]:
"""
@ -129,7 +106,7 @@ def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[Par
for classVar in parsedVars:
if not firstVarOrFuncAccess:
firstVarOrFuncAccess = classVar.access
contentLines.append(GenerateClassVarCode(classVar))
contentLines.extend(GenerateClassVarCode(classVar))
# Add newline between sections if both exist
if parsedVars and (parsedVtFuncs or parsedFuncs):
@ -139,7 +116,7 @@ def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[Par
for index, vTableFunc in enumerate(parsedVtFuncs):
if not firstVarOrFuncAccess:
firstVarOrFuncAccess = vTableFunc.access
contentLines.append(GenerateClassFuncCode(vTableFunc, index))
contentLines.extend(GenerateClassFuncCode(vTableFunc, index))
# Add newline between sections if both exist
if parsedVtFuncs and parsedFuncs:
@ -149,7 +126,7 @@ def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[Par
for func in parsedFuncs:
if not firstVarOrFuncAccess:
firstVarOrFuncAccess = func.access
contentLines.append(GenerateClassFuncCode(func))
contentLines.extend(GenerateClassFuncCode(func))
contentLines.append("#pragma endregion")
@ -159,36 +136,37 @@ def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[Par
return contentLines
def GenerateClassDefinition(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> list[str]:
def GenerateClassDefinition(targetClass: ClassName, forwardDeclare: bool = False) -> list[str]:
"""Generate a class definition from a list of methods."""
parsedVars, parsedVtFuncs, parsedFuncs = allParsedElements
if not parsedVars and not parsedVtFuncs and not parsedFuncs:
return []
classContent: list[str] = GenerateClassContent(allParsedElements)
classLines: list[str] = []
if classContent:
classLines.extend(classContent)
classDefLines: list[str] = [f"{targetClass.type} {targetClass.name}{' {' if not forwardDeclare else ';'}"]
if not forwardDeclare:
if targetClass.type == "class":
classDefLines.append("public:")
classDefLines.append("};")
return classDefLines
classLines.insert(0, f"{targetClass.type if targetClass.type else 'class'} {targetClass.name}")
if classContent:
classLines[0] = f"{classLines[0]} {{"
if targetClass.type == "struct":
classLines[1] = "public:"
classLines.append("};")
else:
classLines[0] = f"{classLines[0]};"
return classLines
def GenerateHeaderCode(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> list[str]:
def GenerateHeaderCode(targetClass: ClassName) -> list[str]:
"""Generate header code for a standard class (not nested)."""
classDefinition = GenerateClassDefinition(targetClass, allParsedElements)
if not classDefinition:
return []
# Reset processed classes for this generation
global processedClasses, forwardDeclarations
processedClasses = set()
forwardDeclarations = set()
# Wrap in namespace blocks if needed
# Mark target class as processed to avoid self-dependency issues
processedClasses.add(targetClass.namespacedClassedName)
# Get all parsed elements for the target class
allParsedElements = ClassParser.GetAllParsedClassVarsAndFuncs(targetClass)
# Generate the target class definition
classContent = GenerateClassContent(allParsedElements)
classDefinition = GenerateClassDefinition(targetClass)
fullClassDefinition = classDefinition
if (classContent):
fullClassDefinition = fullClassDefinition[:len(classDefinition) - 1] + classContent + fullClassDefinition[len(classDefinition) - 1:]
# Wrap target class in namespace blocks if needed
if targetClass.namespaces:
namespaceCode = []
indentLevel = ""
@ -197,19 +175,30 @@ def GenerateHeaderCode(targetClass: ClassName, allParsedElements: tuple[list[Par
for namespace in targetClass.namespaces:
namespaceCode.append(f"{indentLevel}namespace {namespace} {{")
indentLevel += "\t"
for cls in targetClass.classes:
clsType: str = ClassParser.GetClassTypeFromParsedSigs(ClassName(targetClass.namespacedClassedName), allParsedElements)
namespaceCode.append(f"{indentLevel}{clsType if clsType else 'class'} {cls} {{")
indentLevel += "\t"
indentedClassDefinition = [f"{indentLevel}{line}" for line in classDefinition]
indentedClassDefinition = [f"{indentLevel if '#pragma' not in line else ''}{line}" for line in fullClassDefinition]
namespaceCode.extend(indentedClassDefinition)
for cls in reversed(targetClass.classes):
indentLevel = indentLevel[:-1] # Remove one level of indentation
namespaceCode.append(f"{indentLevel}}}")
for namespace in reversed(targetClass.namespaces):
indentLevel = indentLevel[:-1] # Remove one level of indentation
namespaceCode.append(f"{indentLevel}}}")
classDefinition = namespaceCode
fullClassDefinition = namespaceCode
# Combine all parts of the header
headerParts = ["#pragma once", r"#include <EGSDK\Imports.h>", ""]
headerParts.extend(classDefinition)
# Add the target class definition
headerParts.extend(fullClassDefinition)
return headerParts
@ -249,13 +238,20 @@ def ExportClassHeader(targetClass: ClassName):
Generate and save a C++ header file for the target class.
Handles multiple levels of nested classes and also generates dependencies.
"""
# Get the parsed elements for the target class
allParsedElements = ClassParser.GetAllParsedClassVarsAndFuncs(targetClass)
global processedClasses
# Skip if we've already processed this class
if targetClass.namespacedClassedName in processedClasses:
print(f"Already processed class {targetClass.namespacedClassedName}, skipping.")
return
# Add to processed set to prevent infinite recursion
processedClasses.add(targetClass.namespacedClassedName)
# Generate the header code
headerCodeLines = GenerateHeaderCode(targetClass, allParsedElements)
headerCodeLines = GenerateHeaderCode(targetClass)
if not headerCodeLines:
print(f"No functions were found for class {targetClass.namespacedName}, therefore will not generate.")
print(f"No functions were found for class {targetClass.namespacedClassedName}, therefore will not generate.")
return
headerCode: str = "\n".join(headerCodeLines)

View File

@ -0,0 +1,84 @@
from ExportClassH import Utils, ClassParser
def SplitTypeFromName(fullName: str) -> tuple[str, str]:
CLASS_TYPES = ["class", "struct", "enum", "union"]
FUNC_QUALIFIERS = ["virtual", "static", "inline", "explicit", "friend"]
parts = Utils.ExtractTypeTokensFromString(fullName)
if not parts:
return "", ""
if len(parts) > 1:
if parts[0] in CLASS_TYPES:
return parts[0], parts[1]
elif len(parts) > 2 and parts[0] in FUNC_QUALIFIERS and parts[1] in CLASS_TYPES:
return parts[1], parts[2]
return "", fullName
def ExtractClassNameAndTemplateParams(templatedClassName: str) -> tuple[str, list[str]]:
templateParams = []
className = templatedClassName
templateOpen = templatedClassName.find('<')
templateClose = templatedClassName.rfind('>')
if templateOpen != -1 and templateClose != -1:
className = templatedClassName[:templateOpen].strip()
paramsStr = templatedClassName[templateOpen + 1:templateClose].strip()
# Split by commas, but only those outside of nested templates
templateParams = Utils.SplitByCommaOutsideTemplates(paramsStr)
return className, templateParams
def ParseClassStr(fullName: str):
classInfo = {
"type": "",
"name": "",
"templateParams": [],
"parentNamespace": [],
"parentClass": []
}
# Strip whitespace
fullName = fullName.strip()
if not fullName:
return classInfo
# Extract type (struct, class, etc.)
typeAndName = SplitTypeFromName(fullName)
if not typeAndName[0]:
return classInfo
classInfo["type"] = typeAndName[0]
templatedClassNameWithNS = typeAndName[1]
# Split into namespaced parts and the final class name with templates
lastClassSeparatorIndex = Utils.FindLastClassSeparatorOutsideTemplates(templatedClassNameWithNS)
namespacesAndClasses = ""
templatedClassName = ""
if lastClassSeparatorIndex != -1:
namespacesAndClasses = templatedClassNameWithNS[:lastClassSeparatorIndex].strip()
templatedClassName = templatedClassNameWithNS[lastClassSeparatorIndex+2:].strip()
# Extract template parameters
className, templateParams = ExtractClassNameAndTemplateParams(templatedClassName)
classInfo["name"] = className
classInfo["templateParams"] = templateParams
# Split namespaces and classes - for this example we'll use a simple approach
# where we assume the first part(s) are namespaces and later parts are parent classes
if namespacesAndClasses:
allParts = namespacesAndClasses.split("::")
continueOnlyWithClasses: bool = False
for part in allParts:
if not ClassParser.IsClassGenerable(classInfo) and not continueOnlyWithClasses:
classInfo["parentNamespace"].append(part)
else:
if not continueOnlyWithClasses:
continueOnlyWithClasses = True
classInfo["parentClass"].append(part)
return classInfo

View File

@ -43,7 +43,7 @@ def GetVTablePtr(targetClass: ClassName, targetClassRTTIName: str = "") -> int:
typeDescriptorPatternAddr: int = ida_bytes.bin_search(rdataStartAddr, ida_ida.cvar.inf.max_ea, compiledIDAPattern, ida_bytes.BIN_SEARCH_FORWARD)
if typeDescriptorPatternAddr == idc.BADADDR:
print(f"Type descriptor pattern '{typeDescriptorName}' not found for {targetClass.namespacedName}.")
print(f"Type descriptor pattern '{typeDescriptorName}' not found for {targetClass.namespacedClassedName}.")
return 0
# Adjust to get RTTI type descriptor
@ -89,7 +89,7 @@ def GetVTablePtr(targetClass: ClassName, targetClassRTTIName: str = "") -> int:
return vtableAddr
print(f"Failed to locate vtable pointer for {targetClass.namespacedName}.")
print(f"Failed to locate vtable pointer for {targetClass.namespacedClassedName}.")
return 0
def GetDemangledVTableFuncSigs(targetClass: ClassName, targetClassRTTIName: str = "") -> list[tuple[str, str]]:
@ -99,7 +99,7 @@ def GetDemangledVTableFuncSigs(targetClass: ClassName, targetClassRTTIName: str
"""
vtablePtr: int = GetVTablePtr(targetClass, targetClassRTTIName)
if not vtablePtr:
print(f"Vtable pointer not found for {targetClass.namespacedName}.")
print(f"Vtable pointer not found for {targetClass.namespacedClassedName}.")
return []
demangledVTableFuncSigsList: list[tuple[str, str]] = []

View File

@ -7,8 +7,6 @@ import idautils
import idc
IDA_NALT_ENCODING = ida_nalt.get_default_encoding_idx(ida_nalt.BPU_1B)
CLASS_TYPES = ("class", "struct", "enum", "union")
FUNC_QUALIFIERS = ("virtual", "static")
def FixTypeSpacing(type: str) -> str:
"""Fix spacing for pointers/references, commas, and angle brackets."""
@ -30,13 +28,13 @@ def ReplaceIDATypes(type: str) -> str:
"""Replace IDA types with normal ones"""
return type.replace("unsigned __int64", "uint64_t").replace("_QWORD", "uint64_t").replace("__int64", "int64_t").replace("unsigned int", "uint32_t")
def ExtractTypesFromString(types: str) -> list[str]:
def ExtractTypeTokensFromString(types: str) -> list[str]:
"""Extract potential type names from a string, properly handling template types."""
if not types:
return []
types = FixTypeSpacing(types)
result = []
result: list[str] = []
currentWord = ""
templateDepth = 0
@ -60,7 +58,7 @@ def ExtractTypesFromString(types: str) -> list[str]:
result.append(currentWord)
# Filter out empty strings
return [word for word in result if word]
return [word.strip() for word in result if word]
def SplitByCommaOutsideTemplates(params: str) -> list[str]:
parts = []

View File

@ -2,47 +2,48 @@
#include <EGSDK\Imports.h>
class cbs {
public:
#pragma region GENERATED by ExportClassToCPPH.py
public:
GAME_IMPORT class cbs::PrefabManager* g_PrefabManager;
GAME_IMPORT bool IsInDynamicRoot(class cbs::CPointer<class cbs::CEntity>, bool);
GAME_IMPORT void GetPCIDHierarchyPath(class ttl::span<class cbs::PrefabEntityComponent const* const, 4294967295>, uint32_t, class ttl::string_base<char>&);
GAME_IMPORT enum cbs::EntitySpawnOrigin GetEntitySpawnOrigin(class cbs::PrefabEntityComponent const*);
GAME_IMPORT bool CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class ILevel*, class mtx34 const&, class ttl::string_const<char>, class Replication::CreateObjectOptions, bool, bool);
GAME_IMPORT bool GetPrefabEntityComponents(class cbs::CEntity const*, class ttl::vector<class cbs::PrefabEntityComponent const*, class ttl::vector_allocators::heap_allocator<class cbs::PrefabEntityComponent const*>, 16>&);
GAME_IMPORT void GetPECHierarchyPath(class ttl::span<class cbs::PrefabEntityComponent const* const, 4294967295>, uint32_t, class ttl::string_base<char>&);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, enum cbs::pcid_t);
GAME_IMPORT class CLevel* GetCLevel(struct cbs::WorldIndex);
GAME_IMPORT void GetEntityDebugHierarchyPath(class cbs::CEntity const*, class ttl::string_base<char>&);
GAME_IMPORT bool operator==(class cbs::CPointer<class cbs::CEntity>, class cbs::CEntityPointer);
GAME_IMPORT class IPhysicsManager* GetPhysicsManager(struct cbs::WorldIndex);
GAME_IMPORT class cbs::EnumPropertyManager& GetEnumPropertyManager();
GAME_IMPORT bool operator!=(class cbs::CEntity const*, class cbs::CEntityPointer);
GAME_IMPORT bool operator!=(class cbs::CPointer<class cbs::CEntity>, class cbs::CEntityPointer);
GAME_IMPORT float GetTime(struct cbs::WorldIndex);
GAME_IMPORT float GetTimeDelta(struct cbs::WorldIndex);
GAME_IMPORT bool operator==(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT class cbs::CPcidPath operator+(enum cbs::pcid_t, class cbs::CPcidPath const&);
GAME_IMPORT bool operator==(class cbs::CEntity const*, class cbs::CEntityPointer);
GAME_IMPORT class cbs::PrefabManager& GetPrefabManager();
GAME_IMPORT class cbs::ComponentsPool& GetComponentsPool();
GAME_IMPORT class ILevel* GetILevel(struct cbs::WorldIndex);
GAME_IMPORT class cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class ILevel*, class mtx34 const&, class ttl::string_const<char>, class Replication::CreateObjectOptions, bool, bool);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath&&, class cbs::CPcidPath const&);
GAME_IMPORT void GatherSubtypeEntityInterfaces(class ttl::set<class CRTTI const*, struct ttl::less<class CRTTI const*>, class ttl::allocator>&, class CRTTIField const*);
GAME_IMPORT class ttl::string_base<char> GetSpawnContextDebugInfoString(class SpawnContext const&);
GAME_IMPORT bool CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class SpawnContext&);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPathView, class cbs::CPcidPath const&);
GAME_IMPORT class cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class ILevel*, class mtx34 const&, struct PresetId, class Replication::CreateObjectOptions, bool);
GAME_IMPORT enum cbs::EntitySpawnOrigin GetEntitySpawnOrigin(class cbs::CEntity const*);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath&&, class cbs::CPcidPath const&);
GAME_IMPORT bool operator!=(class cbs::CEntity const*, class cbs::CEntityPointer);
GAME_IMPORT bool IsAnyAncestorAlwaysSpawned(class cbs::CPointer<class cbs::CEntity>);
GAME_IMPORT class ttl::string_base<char> GetEntityDebugInfoString(class cbs::CEntity const*);
GAME_IMPORT bool operator==(class cbs::CPointer<class cbs::CEntity>, class cbs::CEntityPointer);
GAME_IMPORT float GetTime(struct cbs::WorldIndex);
GAME_IMPORT bool CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class ILevel*, class mtx34 const&, class ttl::string_const<char>, class Replication::CreateObjectOptions, bool, bool);
GAME_IMPORT class cbs::PrefabManager& GetPrefabManager();
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPathView, class cbs::CPcidPathView);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT class cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class SpawnContext&);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, class cbs::CPcidPathView);
GAME_IMPORT bool operator<(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT bool GetPrefabEntityComponents(class cbs::CEntity const*, class ttl::vector<class cbs::PrefabEntityComponent const*, class ttl::vector_allocators::heap_allocator<class cbs::PrefabEntityComponent const*>, 16>&);
GAME_IMPORT bool IsInDynamicRoot(class cbs::CPointer<class cbs::CEntity>, bool);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPathView, class cbs::CPcidPath const&);
GAME_IMPORT void GetEntityDebugHierarchyPath(class cbs::CEntity const*, class ttl::string_base<char>&);
GAME_IMPORT class IPhysicsManager* GetPhysicsManager(struct cbs::WorldIndex);
GAME_IMPORT class ttl::string_base<char> GetEntityDebugInfoString(class cbs::CEntity const*);
GAME_IMPORT void GatherSubtypeEntityInterfaces(class ttl::set<class CRTTI const*, struct ttl::less<class CRTTI const*>, class ttl::allocator>&, class CRTTIField const*);
GAME_IMPORT class cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class ILevel*, class mtx34 const&, struct PresetId, class Replication::CreateObjectOptions, bool);
GAME_IMPORT bool CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class SpawnContext&);
GAME_IMPORT class cbs::ComponentsPool& GetComponentsPool();
GAME_IMPORT bool CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class ILevel*, class mtx34 const&, struct PresetId, class Replication::CreateObjectOptions, bool);
GAME_IMPORT class cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class ILevel*, class mtx34 const&, class ttl::string_const<char>, class Replication::CreateObjectOptions, bool, bool);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, class cbs::CPcidPathView);
GAME_IMPORT void GetPCIDHierarchyPath(class ttl::span<class cbs::PrefabEntityComponent const* const, 4294967295>, uint32_t, class ttl::string_base<char>&);
GAME_IMPORT bool operator==(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT bool operator==(class cbs::CEntity const*, class cbs::CEntityPointer);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT class ILevel* GetILevel(struct cbs::WorldIndex);
GAME_IMPORT class cbs::CPcidPath operator+(enum cbs::pcid_t, class cbs::CPcidPath const&);
GAME_IMPORT bool operator<(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT void GetPECHierarchyPath(class ttl::span<class cbs::PrefabEntityComponent const* const, 4294967295>, uint32_t, class ttl::string_base<char>&);
GAME_IMPORT class cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class SpawnContext&);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, enum cbs::pcid_t);
GAME_IMPORT enum cbs::EntitySpawnOrigin GetEntitySpawnOrigin(class cbs::PrefabEntityComponent const*);
GAME_IMPORT float GetTimeDelta(struct cbs::WorldIndex);
GAME_IMPORT class CLevel* GetCLevel(struct cbs::WorldIndex);
GAME_IMPORT enum cbs::EntitySpawnOrigin GetEntitySpawnOrigin(class cbs::CEntity const*);
GAME_IMPORT class cbs::EnumPropertyManager& GetEnumPropertyManager();
GAME_IMPORT bool operator!=(class cbs::CPointer<class cbs::CEntity>, class cbs::CEntityPointer);
#pragma endregion
};