mirror of
https://github.com/EricPlayZ/EGameTools.git
synced 2025-07-18 17:37:53 +08:00
backup scripts
This commit is contained in:
34
_IDAScripts/.vscode/settings.json
vendored
34
_IDAScripts/.vscode/settings.json
vendored
@ -1,34 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"atomic": "cpp",
|
||||
"cmath": "cpp",
|
||||
"compare": "cpp",
|
||||
"concepts": "cpp",
|
||||
"cstddef": "cpp",
|
||||
"cstdint": "cpp",
|
||||
"cstdio": "cpp",
|
||||
"cstdlib": "cpp",
|
||||
"cstring": "cpp",
|
||||
"ctime": "cpp",
|
||||
"cwchar": "cpp",
|
||||
"exception": "cpp",
|
||||
"functional": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"iosfwd": "cpp",
|
||||
"limits": "cpp",
|
||||
"list": "cpp",
|
||||
"memory": "cpp",
|
||||
"new": "cpp",
|
||||
"tuple": "cpp",
|
||||
"type_traits": "cpp",
|
||||
"typeinfo": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"utility": "cpp",
|
||||
"vector": "cpp",
|
||||
"xhash": "cpp",
|
||||
"xmemory": "cpp",
|
||||
"xstring": "cpp",
|
||||
"xtr1common": "cpp",
|
||||
"xutility": "cpp"
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ from ExportClassH import Utils
|
||||
@dataclass(frozen=True)
|
||||
class ClassName:
|
||||
"""Split a potentially namespaced class name into namespace parts and class name."""
|
||||
namespaces: tuple[str] = field(default_factory=tuple)
|
||||
namespaces: tuple[str] = field(default_factory=tuple[str])
|
||||
name: str = ""
|
||||
namespacedName: str = ""
|
||||
fullName: str = ""
|
||||
@ -35,7 +35,6 @@ class ClassName:
|
||||
else:
|
||||
return
|
||||
|
||||
|
||||
parts = fullName.split("::")
|
||||
if len(parts) == 1:
|
||||
object.__setattr__(self, "name", parts[0])
|
||||
@ -57,7 +56,7 @@ class ParsedFunction:
|
||||
returnType: Optional[ClassName] = None
|
||||
className: Optional[ClassName] = None
|
||||
funcName: str = ""
|
||||
params: str = ""
|
||||
params: list[ClassName] = field(default_factory=list[ClassName])
|
||||
const: bool = False
|
||||
|
||||
def __init__(self, signature: str, onlyVirtualFuncs: bool):
|
||||
@ -70,7 +69,7 @@ class ParsedFunction:
|
||||
object.__setattr__(self, "returnType", None)
|
||||
object.__setattr__(self, "className", None)
|
||||
object.__setattr__(self, "funcName", "")
|
||||
object.__setattr__(self, "params", "")
|
||||
object.__setattr__(self, "params", [])
|
||||
object.__setattr__(self, "const", False)
|
||||
|
||||
signature = signature.strip()
|
||||
@ -89,13 +88,11 @@ class ParsedFunction:
|
||||
signature = signature.removeprefix("IDA_GEN_PARSED").strip()
|
||||
|
||||
access: str = ""
|
||||
if signature.startswith("public:"):
|
||||
access = "public"
|
||||
elif signature.startswith("protected:"):
|
||||
access = "protected"
|
||||
elif signature.startswith("private:"):
|
||||
access = "private"
|
||||
signature = signature.removeprefix(f"{access}:").strip()
|
||||
for keyword in ("public:", "protected:", "private:"):
|
||||
if signature.startswith(keyword):
|
||||
access = keyword[:-1] # remove the colon
|
||||
signature = signature[len(keyword):].strip()
|
||||
break
|
||||
|
||||
# Find parameters and const qualifier
|
||||
paramsOpenParenIndex: int = signature.find('(')
|
||||
@ -103,6 +100,8 @@ class ParsedFunction:
|
||||
|
||||
if paramsOpenParenIndex != -1 and paramsCloseParenIndex != -1:
|
||||
params: str = signature[paramsOpenParenIndex + 1:paramsCloseParenIndex]
|
||||
paramsStrList: list[str] = Utils.SplitByCommaOutsideTemplates(params)
|
||||
paramsList: list[ClassName] = [ClassName(paramStr) for paramStr in paramsStrList if paramStr]
|
||||
|
||||
remainingInputBeforeParamsParen: str = signature[:paramsOpenParenIndex].strip()
|
||||
remainingInputAfterParamsParen: str = signature[paramsCloseParenIndex + 1:].strip()
|
||||
@ -189,7 +188,7 @@ class ParsedFunction:
|
||||
object.__setattr__(self, "returnType", ClassName(returnType) if returnType else None)
|
||||
object.__setattr__(self, "className", ClassName(className) if className else None)
|
||||
object.__setattr__(self, "funcName", funcName)
|
||||
object.__setattr__(self, "params", params)
|
||||
object.__setattr__(self, "params", paramsList)
|
||||
object.__setattr__(self, "const", bool(const))
|
||||
return
|
||||
|
||||
@ -200,7 +199,7 @@ class ParsedFunction:
|
||||
object.__setattr__(self, "access", access if access else "public")
|
||||
object.__setattr__(self, "returnType", ClassName("virtual void"))
|
||||
object.__setattr__(self, "funcName", f"_StrippedVFunc{virtualFuncPlaceholderCounter}")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ParsedClassVar:
|
||||
"""Parse a demangled global class var signature and return an instance."""
|
||||
@ -218,10 +217,10 @@ class ParsedClassVar:
|
||||
|
||||
# Extract access specifier.
|
||||
access = ""
|
||||
for kw in ("public:", "protected:", "private:"):
|
||||
if signature.startswith(kw):
|
||||
access = kw[:-1] # remove the colon
|
||||
signature = signature[len(kw):].strip()
|
||||
for keyword in ("public:", "protected:", "private:"):
|
||||
if signature.startswith(keyword):
|
||||
access = keyword[:-1] # remove the colon
|
||||
signature = signature[len(keyword):].strip()
|
||||
break
|
||||
|
||||
# For class variables, we expect no parameters (i.e. no parentheses).
|
||||
|
@ -16,8 +16,7 @@ allClassVarsAreParsed = False # Flag to indicate if all class vars have been par
|
||||
allFuncsAreParsed = False # Flag to indicate if all functions have been parsed
|
||||
|
||||
def CreateParamNamesForVTFunc(parsedFunc: ParsedFunction, skipFirstParam: bool) -> str:
|
||||
paramsList: list[str] = [param.strip() for param in parsedFunc.params.split(',')
|
||||
if param.strip()]
|
||||
paramsList: list[str] = [param.fullName for param in parsedFunc.params if param.fullName]
|
||||
if len(paramsList) == 1 and paramsList[0] == "void":
|
||||
return "void"
|
||||
# Skip the first parameter (typically the "this" pointer)
|
||||
@ -30,8 +29,7 @@ def CreateParamNamesForVTFunc(parsedFunc: ParsedFunction, skipFirstParam: bool)
|
||||
return newParams
|
||||
|
||||
def ExtractParamNames(params: str) -> str:
|
||||
paramsList: list[str] = [param.strip() for param in params.split(',')
|
||||
if param.strip()]
|
||||
paramsList: list[str] = [param.strip() for param in params.split(',') if param.strip()]
|
||||
if len(paramsList) == 1 and paramsList[0] == "void":
|
||||
return ""
|
||||
|
||||
@ -39,6 +37,32 @@ def ExtractParamNames(params: str) -> str:
|
||||
newParams: str = ", ".join(paramNames)
|
||||
return newParams
|
||||
|
||||
def GetClassTypeFromParsedSigs(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> str:
|
||||
"""Determine the class type (class, struct, etc.) from parsed signatures."""
|
||||
if targetClass.type:
|
||||
return ""
|
||||
parsedClassVars, parsedVtFuncs, _ = allParsedElements
|
||||
# Check class vars first
|
||||
for parsedClassVar in parsedClassVars:
|
||||
if (parsedClassVar.varType and
|
||||
parsedClassVar.varType.namespacedName == targetClass.namespacedName 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.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.type):
|
||||
return parsedFunc.returnType.type
|
||||
|
||||
return ""
|
||||
|
||||
def ComputeUnparsedExportedSigs(demangledExportedSigs: list[str], parsedSigs: list[str]) -> list[str]:
|
||||
# Join all parsed signatures into one large string.
|
||||
big_parsed = "\n".join(parsedSigs)
|
||||
@ -225,5 +249,12 @@ def GetAllParsedClassVarsAndFuncs(targetClass: ClassName) -> tuple[list[ParsedCl
|
||||
parsedFunc for parsedFunc in parsedClassFuncs
|
||||
if parsedFunc.fullFuncSig not in vTableFuncsSet
|
||||
]
|
||||
|
||||
allParsedElements = (parsedClassVars, parsedVTableClassFuncs, finalParsedClassFuncs)
|
||||
|
||||
# Get and set class type if available
|
||||
classType: str = GetClassTypeFromParsedSigs(targetClass, allParsedElements)
|
||||
if classType:
|
||||
object.__setattr__(targetClass, "type", classType)
|
||||
|
||||
return (parsedClassVars, parsedVTableClassFuncs, finalParsedClassFuncs)
|
||||
return allParsedElements
|
@ -1,5 +1,4 @@
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
from ExportClassH import Utils, Config, ClassParser
|
||||
from ExportClassH.ClassDefs import ClassName, ParsedFunction, ParsedClassVar
|
||||
@ -42,20 +41,19 @@ def IdentifyClassHierarchy(targetClass: ClassName) -> list[tuple[ClassName, bool
|
||||
return hierarchy
|
||||
|
||||
currentAccess: str = "public"
|
||||
|
||||
def GenerateClassVarCode(classVar: ParsedClassVar, indent: str = "", cleanedTypes: bool = True) -> str:
|
||||
def GenerateClassVarCode(classVar: ParsedClassVar) -> str:
|
||||
"""Generate code for a single class variable."""
|
||||
global currentAccess
|
||||
|
||||
access: str = f"{indent}{classVar.access}:\n{indent}\t" if classVar.access else f"{indent}\t"
|
||||
access: str = f"{classVar.access}:\n\t" if classVar.access else "\t"
|
||||
if currentAccess == classVar.access:
|
||||
access = f"{indent}\t"
|
||||
access = "\t"
|
||||
else:
|
||||
currentAccess = classVar.access
|
||||
|
||||
if classVar.varType:
|
||||
varType: str = Utils.ReplaceIDATypes(classVar.varType.fullName)
|
||||
varType = Utils.CleanType(varType) if cleanedTypes else classVar.varType.fullName
|
||||
varType = Utils.CleanType(varType)
|
||||
if varType:
|
||||
varType += " "
|
||||
varType = "GAME_IMPORT " + varType
|
||||
@ -65,13 +63,13 @@ def GenerateClassVarCode(classVar: ParsedClassVar, indent: str = "", cleanedType
|
||||
classVarSig: str = f"{varType}{classVar.varName}"
|
||||
return f"{access}{classVarSig};"
|
||||
|
||||
def GenerateClassFuncCode(func: ParsedFunction, indent: str = "", cleanedTypes: bool = True, vtFuncIndex: int = 0) -> str:
|
||||
def GenerateClassFuncCode(func: ParsedFunction, vtFuncIndex: int = 0) -> str:
|
||||
"""Generate code for a single class method."""
|
||||
global currentAccess
|
||||
|
||||
access: str = f"{indent}{func.access}:\n{indent}\t" if func.access else f"{indent}\t"
|
||||
access: str = f"{func.access}:\n\t" if func.access else "\t"
|
||||
if currentAccess == func.access:
|
||||
access = f"{indent}\t"
|
||||
access = "\t"
|
||||
else:
|
||||
currentAccess = func.access
|
||||
|
||||
@ -80,7 +78,7 @@ def GenerateClassFuncCode(func: ParsedFunction, indent: str = "", cleanedTypes:
|
||||
|
||||
if func.returnType:
|
||||
returnType: str = Utils.ReplaceIDATypes(func.returnType.fullName)
|
||||
returnType = Utils.CleanType(returnType) if cleanedTypes else func.returnType.fullName
|
||||
returnType = Utils.CleanType(returnType)
|
||||
if returnType:
|
||||
if func.type == "basic_vfunc":
|
||||
returnType = returnType.removeprefix("virtual").strip()
|
||||
@ -92,8 +90,11 @@ def GenerateClassFuncCode(func: ParsedFunction, indent: str = "", cleanedTypes:
|
||||
returnType = "GAME_IMPORT " + returnType
|
||||
|
||||
if func.params:
|
||||
params: str = Utils.ReplaceIDATypes(func.params)
|
||||
params = Utils.CleanType(params) if cleanedTypes else func.params
|
||||
paramsList: list[str] = []
|
||||
for param in func.params:
|
||||
paramsList.append(param.fullName)
|
||||
params: str = Utils.ReplaceIDATypes(", ".join(paramsList))
|
||||
params = Utils.CleanType(params)
|
||||
if params == "void":
|
||||
params = ""
|
||||
else:
|
||||
@ -107,64 +108,28 @@ def GenerateClassFuncCode(func: ParsedFunction, indent: str = "", cleanedTypes:
|
||||
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};"
|
||||
|
||||
def GetClassTypeFromParsedSigs(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> str:
|
||||
"""Determine the class type (class, struct, etc.) from parsed signatures."""
|
||||
if targetClass.type:
|
||||
return ""
|
||||
|
||||
parsedVars, parsedVtFuncs, _ = allParsedElements
|
||||
|
||||
# Check class vars first
|
||||
for parsedClassVar in parsedVars:
|
||||
if (parsedClassVar.varType and
|
||||
parsedClassVar.varType.namespacedName == targetClass.namespacedName 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.type):
|
||||
return parsedVTFunc.returnType.type
|
||||
|
||||
# Check all parsed functions last
|
||||
for parsedFunc in ClassParser.allParsedFuncs:
|
||||
if (parsedFunc.returnType and
|
||||
parsedFunc.returnType.namespacedName == targetClass.namespacedName and
|
||||
parsedFunc.returnType.type):
|
||||
return parsedFunc.returnType.type
|
||||
|
||||
return ""
|
||||
|
||||
def GenerateClassContent(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], indent: str = "", cleanedTypes: bool = True) -> str:
|
||||
def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> list[str]:
|
||||
"""
|
||||
Generate the content to be inserted into an existing header file.
|
||||
This is just the class members, not the full header with includes, etc.
|
||||
"""
|
||||
parsedVars, parsedVtFuncs, parsedFuncs = allParsedElements
|
||||
|
||||
if not parsedVars and not parsedVtFuncs and not parsedFuncs:
|
||||
return ""
|
||||
|
||||
global currentAccess
|
||||
|
||||
parsedVars, parsedVtFuncs, parsedFuncs = allParsedElements
|
||||
if not parsedVars and not parsedVtFuncs and not parsedFuncs:
|
||||
return []
|
||||
|
||||
firstVarOrFuncAccess = ""
|
||||
currentAccess = ""
|
||||
|
||||
# Get and set class type if available
|
||||
classType: str = GetClassTypeFromParsedSigs(targetClass, allParsedElements)
|
||||
if classType:
|
||||
object.__setattr__(targetClass, "type", classType)
|
||||
|
||||
# Generate class content (just the members)
|
||||
contentLines = [f"#pragma region GENERATED by ExportClassToCPPH.py"]
|
||||
|
||||
firstVarOrFuncAccess = ""
|
||||
|
||||
# Add class variables
|
||||
for classVar in parsedVars:
|
||||
if not firstVarOrFuncAccess:
|
||||
firstVarOrFuncAccess = classVar.access
|
||||
contentLines.append(GenerateClassVarCode(classVar, indent, cleanedTypes))
|
||||
contentLines.append(GenerateClassVarCode(classVar))
|
||||
|
||||
# Add newline between sections if both exist
|
||||
if parsedVars and (parsedVtFuncs or parsedFuncs):
|
||||
@ -174,7 +139,7 @@ def GenerateClassContent(targetClass: ClassName, allParsedElements: tuple[list[P
|
||||
for index, vTableFunc in enumerate(parsedVtFuncs):
|
||||
if not firstVarOrFuncAccess:
|
||||
firstVarOrFuncAccess = vTableFunc.access
|
||||
contentLines.append(GenerateClassFuncCode(vTableFunc, indent, cleanedTypes, index))
|
||||
contentLines.append(GenerateClassFuncCode(vTableFunc, index))
|
||||
|
||||
# Add newline between sections if both exist
|
||||
if parsedVtFuncs and parsedFuncs:
|
||||
@ -184,134 +149,44 @@ def GenerateClassContent(targetClass: ClassName, allParsedElements: tuple[list[P
|
||||
for func in parsedFuncs:
|
||||
if not firstVarOrFuncAccess:
|
||||
firstVarOrFuncAccess = func.access
|
||||
contentLines.append(GenerateClassFuncCode(func, indent, cleanedTypes))
|
||||
contentLines.append(GenerateClassFuncCode(func))
|
||||
|
||||
contentLines.append("#pragma endregion")
|
||||
|
||||
# Insert access specifier if needed
|
||||
if not firstVarOrFuncAccess:
|
||||
contentLines.insert(1, f"{indent}public:")
|
||||
contentLines.insert(1, "public:")
|
||||
|
||||
return "\n".join(contentLines)
|
||||
return contentLines
|
||||
|
||||
def GenerateClassDefinition(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], indent: str = "", cleanedTypes: bool = True) -> str:
|
||||
def GenerateClassDefinition(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> 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 ""
|
||||
return []
|
||||
|
||||
classContent: str = GenerateClassContent(targetClass, allParsedElements, indent, cleanedTypes)
|
||||
classContent: list[str] = GenerateClassContent(allParsedElements)
|
||||
|
||||
classLines: list[str] = [f"{indent}{targetClass.type if targetClass.type else 'class'} {targetClass.name} {{"]
|
||||
classLines: list[str] = []
|
||||
if classContent:
|
||||
classLines.append(classContent)
|
||||
classLines.append(f"{indent}}};")
|
||||
classLines.extend(classContent)
|
||||
|
||||
return "\n".join(classLines)
|
||||
|
||||
def GenerateClassDefinitionRecursive(currentClass: ClassName, currentElements: tuple, nestedStructure: dict, indent: str, cleanedTypes: bool, placeholderClasses: list = []) -> str:
|
||||
"""
|
||||
Recursively generate class definitions with nested classes and type dependencies.
|
||||
"""
|
||||
parsedVars, parsedVtFuncs, parsedFuncs = currentElements
|
||||
|
||||
# Check if we have any content (including placeholders)
|
||||
if not parsedVars and not parsedVtFuncs and not parsedFuncs and not nestedStructure and not placeholderClasses:
|
||||
return ""
|
||||
|
||||
# Start the class definition
|
||||
classLines = [f"{indent}class {currentClass.name} {{"]
|
||||
|
||||
# Add placeholder class forward declarations if any
|
||||
if placeholderClasses:
|
||||
classLines.append(f"{indent}public:")
|
||||
|
||||
# Add explicitly requested placeholders
|
||||
if placeholderClasses:
|
||||
for placeholderClass in placeholderClasses:
|
||||
classLines.append(f"{indent}\tclass {placeholderClass};")
|
||||
|
||||
classLines.append("") # Add empty line after placeholders
|
||||
|
||||
# Add nested classes with additional indentation
|
||||
if nestedStructure:
|
||||
# Only add public: if not already added for placeholders
|
||||
if not placeholderClasses:
|
||||
classLines.append(f"{indent}public:")
|
||||
|
||||
for nestedClass, (nestedStructureLevel, nestedElements) in nestedStructure.items():
|
||||
nestedDefinition = GenerateClassDefinitionRecursive(
|
||||
nestedClass,
|
||||
nestedElements,
|
||||
nestedStructureLevel or {},
|
||||
indent + "\t",
|
||||
cleanedTypes
|
||||
)
|
||||
if nestedDefinition:
|
||||
classLines.append(nestedDefinition)
|
||||
classLines.append("")
|
||||
|
||||
# Add the current class content
|
||||
classContent = GenerateClassContent(currentClass, currentElements, indent, cleanedTypes)
|
||||
classLines.insert(0, f"{targetClass.type if targetClass.type else 'class'} {targetClass.name}")
|
||||
if classContent:
|
||||
classLines.append(classContent)
|
||||
|
||||
# Close the class
|
||||
classLines.append(f"{indent}}};")
|
||||
|
||||
return "\n".join(classLines)
|
||||
|
||||
def BuildNestedStructure(hierarchy: list[tuple[ClassName, bool]], cleanedTypes: bool = True) -> tuple[Optional[ClassName], dict]:
|
||||
"""
|
||||
Build a nested dictionary structure representing the class hierarchy.
|
||||
Returns (outermost_class, nested_structure_dict)
|
||||
"""
|
||||
if not hierarchy:
|
||||
return None, {}
|
||||
|
||||
outermostClass, _ = hierarchy[0]
|
||||
if len(hierarchy) == 1:
|
||||
return outermostClass, {}
|
||||
|
||||
# Recursively build the nested structure
|
||||
nestedStructure = {}
|
||||
currentLevel = nestedStructure
|
||||
|
||||
for i in range(1, len(hierarchy)):
|
||||
currentClass, isClass = hierarchy[i]
|
||||
if isClass:
|
||||
currentElements = ClassParser.GetAllParsedClassVarsAndFuncs(currentClass)
|
||||
|
||||
# If this is not the last class, create a new level
|
||||
if i < len(hierarchy) - 1:
|
||||
currentLevel[currentClass] = ({}, currentElements)
|
||||
currentLevel = currentLevel[currentClass][0]
|
||||
else:
|
||||
# Last class in the hierarchy
|
||||
currentLevel[currentClass] = (None, currentElements)
|
||||
|
||||
return outermostClass, nestedStructure
|
||||
|
||||
def GenerateHeaderCode(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool = True) -> str:
|
||||
"""
|
||||
Generate a C++ header file for the target class.
|
||||
Supports handling nested classes with multiple levels of nesting.
|
||||
"""
|
||||
# Identify the class hierarchy
|
||||
hierarchy = IdentifyClassHierarchy(targetClass)
|
||||
|
||||
# If none of the namespace parts are classes (standard case), generate a normal class definition
|
||||
if not any(isClass for _, isClass in hierarchy[:-1]):
|
||||
return GenerateStandardHeaderCode(targetClass, allParsedElements, cleanedTypes, hierarchy)
|
||||
classLines[0] = f"{classLines[0]} {{"
|
||||
if targetClass.type == "struct":
|
||||
classLines[1] = "public:"
|
||||
classLines.append("};")
|
||||
else:
|
||||
return GenerateNestedHeaderCode(targetClass, allParsedElements, cleanedTypes, hierarchy)
|
||||
classLines[0] = f"{classLines[0]};"
|
||||
|
||||
def GenerateStandardHeaderCode(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool, hierarchy: list[tuple[ClassName, bool]]) -> str:
|
||||
return classLines
|
||||
|
||||
def GenerateHeaderCode(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> list[str]:
|
||||
"""Generate header code for a standard class (not nested)."""
|
||||
classDefinition = GenerateClassDefinition(targetClass, allParsedElements, "", cleanedTypes)
|
||||
classDefinition = GenerateClassDefinition(targetClass, allParsedElements)
|
||||
if not classDefinition:
|
||||
return ""
|
||||
return []
|
||||
|
||||
# Wrap in namespace blocks if needed
|
||||
if targetClass.namespaces:
|
||||
@ -323,92 +198,41 @@ def GenerateStandardHeaderCode(targetClass: ClassName, allParsedElements: tuple[
|
||||
namespaceCode.append(f"{indentLevel}namespace {namespace} {{")
|
||||
indentLevel += "\t"
|
||||
|
||||
classLines = classDefinition.split('\n')
|
||||
indentedClassLines = [f"{indentLevel}{line}" for line in classLines]
|
||||
indentedClassDefinition = "\n".join(indentedClassLines)
|
||||
|
||||
namespaceCode.append(f"{indentedClassDefinition}")
|
||||
indentedClassDefinition = [f"{indentLevel}{line}" for line in classDefinition]
|
||||
namespaceCode.extend(indentedClassDefinition)
|
||||
|
||||
for namespace in reversed(targetClass.namespaces):
|
||||
indentLevel = indentLevel[:-1] # Remove one level of indentation
|
||||
namespaceCode.append(f"{indentLevel}}}")
|
||||
|
||||
classDefinition = "\n".join(namespaceCode)
|
||||
classDefinition = namespaceCode
|
||||
|
||||
# Combine all parts of the header
|
||||
headerParts = ["#pragma once\n", r"#include <EGSDK\Imports.h>", "\n\n"]
|
||||
headerParts.append(classDefinition)
|
||||
headerParts = ["#pragma once", r"#include <EGSDK\Imports.h>", ""]
|
||||
headerParts.extend(classDefinition)
|
||||
|
||||
return "".join(headerParts)
|
||||
return headerParts
|
||||
|
||||
def GenerateNestedHeaderCode(targetClass: ClassName, allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool, hierarchy: list[tuple[ClassName, bool]], placeholderClasses: list = []) -> str:
|
||||
"""Generate header code for nested classes."""
|
||||
# Handle nested classes
|
||||
outermostClassIndex = next((i for i, (_, isClass) in enumerate(hierarchy) if isClass), -1)
|
||||
outermostClass, _ = hierarchy[outermostClassIndex]
|
||||
outermostClassElements = ClassParser.GetAllParsedClassVarsAndFuncs(outermostClass)
|
||||
|
||||
# Build the nested structure
|
||||
_, nestedStructure = BuildNestedStructure(hierarchy[outermostClassIndex:], cleanedTypes)
|
||||
|
||||
# Generate the class definition recursively
|
||||
classDefinition = GenerateClassDefinitionRecursive(
|
||||
outermostClass,
|
||||
outermostClassElements,
|
||||
nestedStructure,
|
||||
"",
|
||||
cleanedTypes,
|
||||
placeholderClasses
|
||||
)
|
||||
|
||||
if not classDefinition:
|
||||
return ""
|
||||
|
||||
# Wrap in namespace blocks for any namespaces before the outermost class
|
||||
if outermostClassIndex > 0:
|
||||
namespaceCode = []
|
||||
indentLevel = ""
|
||||
|
||||
# Opening namespace blocks with increasing indentation
|
||||
for namespace_part, _ in hierarchy[:outermostClassIndex]:
|
||||
namespaceCode.append(f"{indentLevel}namespace {namespace_part.name} {{")
|
||||
indentLevel += "\t"
|
||||
|
||||
classLines = classDefinition.split('\n')
|
||||
indentedClassLines = [f"{indentLevel}{line}" for line in classLines]
|
||||
indentedClassDefinition = "\n".join(indentedClassLines)
|
||||
|
||||
namespaceCode.append(f"{indentedClassDefinition}")
|
||||
|
||||
for namespace_part, _ in reversed(hierarchy[:outermostClassIndex]):
|
||||
indentLevel = indentLevel[:-1] # Remove one level of indentation
|
||||
namespaceCode.append(f"{indentLevel}}}")
|
||||
|
||||
classDefinition = "\n".join(namespaceCode)
|
||||
|
||||
# Combine all parts of the header
|
||||
headerParts = ["#pragma once\n", r"#include <EGSDK\Imports.h>", "\n\n"]
|
||||
headerParts.append(classDefinition)
|
||||
|
||||
return "".join(headerParts)
|
||||
|
||||
def WriteHeaderToFile(targetClass: ClassName, headerCode: str, fileName: str = "") -> bool:
|
||||
def WriteHeaderToFile(targetClass: ClassName, headerCode: str, fileName: str) -> bool:
|
||||
"""Write the generated header code to a file."""
|
||||
outputFolderPath: str = Config.HEADER_OUTPUT_PATH
|
||||
|
||||
if targetClass.namespaces:
|
||||
# Create folder structure for namespaces
|
||||
classFolderPath: str = os.path.join(*targetClass.namespaces)
|
||||
outputFolderPath: str = os.path.join(Config.HEADER_OUTPUT_PATH, classFolderPath)
|
||||
outputFolderPath: str = os.path.join(outputFolderPath, classFolderPath)
|
||||
|
||||
# Create directory if it doesn't exist
|
||||
os.makedirs(outputFolderPath, exist_ok=True)
|
||||
|
||||
# Output file path is inside the namespace folder
|
||||
outputFilePath: str = os.path.join(classFolderPath, f"{targetClass.name}.h" if not fileName else fileName)
|
||||
outputFilePath: str = os.path.join(classFolderPath, fileName)
|
||||
else:
|
||||
# Create directory if it doesn't exist
|
||||
os.makedirs(outputFolderPath, exist_ok=True)
|
||||
# No namespace, just save in current directory
|
||||
outputFilePath: str = f"{targetClass.name}.h" if not fileName else fileName
|
||||
outputFilePath: str = fileName
|
||||
|
||||
outputFilePath: str = os.path.join(Config.HEADER_OUTPUT_PATH, outputFilePath)
|
||||
outputFilePath = os.path.join(Config.HEADER_OUTPUT_PATH, outputFilePath)
|
||||
|
||||
try:
|
||||
with open(outputFilePath, 'w') as headerFile:
|
||||
@ -424,54 +248,15 @@ 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.
|
||||
"""
|
||||
# Identify the class hierarchy
|
||||
hierarchy = IdentifyClassHierarchy(targetClass)
|
||||
|
||||
"""
|
||||
# Get the parsed elements for the target class
|
||||
allParsedElements = ClassParser.GetAllParsedClassVarsAndFuncs(targetClass)
|
||||
|
||||
# If this is a nested class with no elements, add it as a placeholder
|
||||
if len(hierarchy) > 1 and not IsClassGenerable(targetClass):
|
||||
# Find the parent class (nearest generable class in hierarchy)
|
||||
parentIndex = -1
|
||||
for i in range(len(hierarchy) - 2, -1, -1):
|
||||
if hierarchy[i][1]: # If this part is a class
|
||||
parentIndex = i
|
||||
break
|
||||
|
||||
if parentIndex >= 0:
|
||||
# Get the parent class and its elements
|
||||
parentClass, _ = hierarchy[parentIndex]
|
||||
parentElements = ClassParser.GetAllParsedClassVarsAndFuncs(parentClass)
|
||||
|
||||
# Use the existing nested structure generation with an added placeholder
|
||||
_, nestedStructure = BuildNestedStructure(hierarchy[parentIndex:], True)
|
||||
|
||||
# Add the placeholder
|
||||
placeholderClasses = [targetClass.name]
|
||||
|
||||
# Generate the header code with the placeholder
|
||||
headerCode = GenerateNestedHeaderCode(
|
||||
parentClass,
|
||||
parentElements,
|
||||
True,
|
||||
hierarchy[:parentIndex+1], # Only include up to parent
|
||||
placeholderClasses
|
||||
)
|
||||
|
||||
if headerCode:
|
||||
WriteHeaderToFile(targetClass, headerCode)
|
||||
print(f"Generated header for class {parentClass.namespacedName} with placeholder for {targetClass.name}")
|
||||
return
|
||||
|
||||
# Generate the header code
|
||||
headerCode = GenerateHeaderCode(targetClass, allParsedElements, True)
|
||||
if not headerCode:
|
||||
headerCodeLines = GenerateHeaderCode(targetClass, allParsedElements)
|
||||
if not headerCodeLines:
|
||||
print(f"No functions were found for class {targetClass.namespacedName}, therefore will not generate.")
|
||||
return
|
||||
headerCode: str = "\n".join(headerCodeLines)
|
||||
|
||||
WriteHeaderToFile(targetClass, headerCode)
|
||||
|
||||
headerCodeUnclean = GenerateHeaderCode(targetClass, allParsedElements, False)
|
||||
WriteHeaderToFile(targetClass, headerCodeUnclean, f"{targetClass.name}-unclean.h")
|
||||
WriteHeaderToFile(targetClass, headerCode, f"{targetClass.name}.h")
|
@ -13,16 +13,17 @@ FUNC_QUALIFIERS = ("virtual", "static")
|
||||
def FixTypeSpacing(type: str) -> str:
|
||||
"""Fix spacing for pointers/references, commas, and angle brackets."""
|
||||
type = re.sub(r'\s+([*&])', r'\1', type) # Remove space before '*' or '&'
|
||||
type = re.sub(r'([*&])(?!\s)', r'\1 ', type) # Ensure '*' or '&' is followed by one space if it's not already.
|
||||
type = re.sub(r'([*&])(?![\s*&])', r'\1 ', type) # Ensure '*' or '&' is followed by one space if it's not already.
|
||||
type = re.sub(r'\s*,\s*', ', ', type) # Ensure comma followed by one space
|
||||
type = re.sub(r'<\s+', '<', type) # Remove space after '<'
|
||||
type = re.sub(r'\s+>', '>', type) # Remove space before '>'
|
||||
type = re.sub(r'\s+([\),])', r'\1', type)
|
||||
type = re.sub(r'\s+', ' ', type) # Collapse multiple spaces
|
||||
return type.strip()
|
||||
|
||||
def CleanType(type: str) -> str:
|
||||
"""Remove unwanted tokens from a type string, then fix spacing."""
|
||||
type = re.sub(r'\b(__cdecl|__fastcall|__ptr64|class|struct|enum|union)\b', '', type)
|
||||
type = re.sub(r'\b(__cdecl|__fastcall|__ptr64)\b', '', type)
|
||||
return FixTypeSpacing(type)
|
||||
|
||||
def ReplaceIDATypes(type: str) -> str:
|
||||
@ -61,6 +62,30 @@ def ExtractTypesFromString(types: str) -> list[str]:
|
||||
# Filter out empty strings
|
||||
return [word for word in result if word]
|
||||
|
||||
def SplitByCommaOutsideTemplates(params: str) -> list[str]:
|
||||
parts = []
|
||||
current = []
|
||||
depth = 0
|
||||
|
||||
for char in params:
|
||||
if char == '<':
|
||||
depth += 1
|
||||
elif char == '>':
|
||||
# It's good to check for consistency:
|
||||
if depth > 0:
|
||||
depth -= 1
|
||||
# If we see a comma at top level, split here.
|
||||
if char == ',' and depth == 0:
|
||||
parts.append(''.join(current).strip())
|
||||
current = []
|
||||
else:
|
||||
current.append(char)
|
||||
|
||||
# Append any remaining characters as the last parameter.
|
||||
if current:
|
||||
parts.append(''.join(current).strip())
|
||||
return parts
|
||||
|
||||
def FindLastSpaceOutsideTemplates(s: str) -> int:
|
||||
"""Return the index of the last space in s that is not inside '<' and '>'."""
|
||||
depth = 0
|
||||
|
48
_IDAScripts/generated/cbs.h
Normal file
48
_IDAScripts/generated/cbs.h
Normal file
@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
#include <EGSDK\Imports.h>
|
||||
|
||||
class cbs {
|
||||
#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 bool IsAnyAncestorAlwaysSpawned(class cbs::CPointer<class cbs::CEntity>);
|
||||
GAME_IMPORT class ttl::string_base<char> GetEntityDebugInfoString(class cbs::CEntity const*);
|
||||
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 CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class ILevel*, class mtx34 const&, struct PresetId, class Replication::CreateObjectOptions, bool);
|
||||
#pragma endregion
|
||||
};
|
Reference in New Issue
Block a user