backup scripts

This commit is contained in:
EricPlayZ
2025-03-09 03:04:02 +02:00
parent 9962a58cb7
commit edad5f4ead
6 changed files with 188 additions and 334 deletions

View File

@ -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"
}
}

View File

@ -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).

View File

@ -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

View File

@ -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")

View File

@ -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

View 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
};