2025-03-09 01:10:59 +02:00
|
|
|
import os
|
|
|
|
|
2025-03-17 02:09:24 +02:00
|
|
|
from ExportClassH import ClassGen, Utils, Config
|
2025-03-09 01:10:59 +02:00
|
|
|
from ExportClassH.ClassDefs import ClassName, ParsedFunction, ParsedClassVar
|
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
processedClasses: set[str] = set()
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
currentAccess: str = "public"
|
2025-03-13 00:30:18 +02:00
|
|
|
def GenerateClassVarCode(classVar: ParsedClassVar) -> list[str]:
|
2025-03-09 01:10:59 +02:00
|
|
|
"""Generate code for a single class variable."""
|
|
|
|
global currentAccess
|
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
access: str = f"{classVar.access}:" if classVar.access else ""
|
2025-03-09 01:10:59 +02:00
|
|
|
if currentAccess == classVar.access:
|
2025-03-09 03:04:02 +02:00
|
|
|
access = "\t"
|
2025-03-09 01:10:59 +02:00
|
|
|
else:
|
|
|
|
currentAccess = classVar.access
|
|
|
|
|
|
|
|
if classVar.varType:
|
|
|
|
varType: str = Utils.ReplaceIDATypes(classVar.varType.fullName)
|
2025-03-09 03:04:02 +02:00
|
|
|
varType = Utils.CleanType(varType)
|
2025-03-09 01:10:59 +02:00
|
|
|
if varType:
|
|
|
|
varType += " "
|
|
|
|
varType = "GAME_IMPORT " + varType
|
|
|
|
else:
|
|
|
|
varType: str = ""
|
|
|
|
|
|
|
|
classVarSig: str = f"{varType}{classVar.varName}"
|
2025-03-13 00:30:18 +02:00
|
|
|
|
|
|
|
classVarLines: list[str] = []
|
|
|
|
if access:
|
|
|
|
classVarLines.append(access)
|
|
|
|
if classVarSig:
|
|
|
|
classVarLines.append(f"\t{classVarSig};")
|
|
|
|
return classVarLines
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
def GenerateClassFuncCode(func: ParsedFunction, vtFuncIndex: int = 0) -> list[str]:
|
2025-03-09 01:10:59 +02:00
|
|
|
"""Generate code for a single class method."""
|
|
|
|
global currentAccess
|
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
access: str = f"{func.access}:" if func.access else ""
|
2025-03-09 01:10:59 +02:00
|
|
|
if currentAccess == func.access:
|
2025-03-13 00:30:18 +02:00
|
|
|
access = ""
|
2025-03-09 01:10:59 +02:00
|
|
|
else:
|
|
|
|
currentAccess = func.access
|
|
|
|
|
|
|
|
const: str = " const" if func.const else ""
|
|
|
|
stripped_vfunc: str = " = 0" if func.type == "stripped_vfunc" else ""
|
|
|
|
|
|
|
|
if func.returnType:
|
|
|
|
returnType: str = Utils.ReplaceIDATypes(func.returnType.fullName)
|
2025-03-09 03:04:02 +02:00
|
|
|
returnType = Utils.CleanType(returnType)
|
2025-03-09 01:10:59 +02:00
|
|
|
if returnType:
|
|
|
|
if func.type == "basic_vfunc":
|
|
|
|
returnType = returnType.removeprefix("virtual").strip()
|
|
|
|
else:
|
|
|
|
returnType += " "
|
|
|
|
else:
|
|
|
|
returnType: str = ""
|
|
|
|
if func.type != "stripped_vfunc" and func.type != "basic_vfunc":
|
|
|
|
returnType = "GAME_IMPORT " + returnType
|
|
|
|
|
|
|
|
if func.params:
|
2025-03-09 03:04:02 +02:00
|
|
|
paramsList: list[str] = []
|
|
|
|
for param in func.params:
|
|
|
|
paramsList.append(param.fullName)
|
|
|
|
params: str = Utils.ReplaceIDATypes(", ".join(paramsList))
|
|
|
|
params = Utils.CleanType(params)
|
2025-03-09 01:10:59 +02:00
|
|
|
if params == "void":
|
|
|
|
params = ""
|
|
|
|
else:
|
|
|
|
params: str = ""
|
|
|
|
|
|
|
|
targetParams: str = ""
|
|
|
|
if func.type == "basic_vfunc":
|
2025-03-17 02:09:24 +02:00
|
|
|
targetParams = ClassGen.ExtractParamNames(params)
|
2025-03-09 01:10:59 +02:00
|
|
|
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})"
|
2025-03-13 00:30:18 +02:00
|
|
|
|
|
|
|
classFuncLines: list[str] = []
|
|
|
|
if access:
|
|
|
|
classFuncLines.append(access)
|
|
|
|
if funcSig:
|
|
|
|
classFuncLines.append(f"\t{funcSig};")
|
|
|
|
return classFuncLines
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
def GenerateClassContent(allParsedElements: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]) -> list[str]:
|
2025-03-09 01:10:59 +02:00
|
|
|
"""
|
|
|
|
Generate the content to be inserted into an existing header file.
|
|
|
|
This is just the class members, not the full header with includes, etc.
|
|
|
|
"""
|
2025-03-09 03:04:02 +02:00
|
|
|
global currentAccess
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
parsedVars, parsedVtFuncs, parsedFuncs = allParsedElements
|
2025-03-09 01:10:59 +02:00
|
|
|
if not parsedVars and not parsedVtFuncs and not parsedFuncs:
|
2025-03-09 03:04:02 +02:00
|
|
|
return []
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
firstVarOrFuncAccess = ""
|
2025-03-09 01:10:59 +02:00
|
|
|
currentAccess = ""
|
|
|
|
|
|
|
|
# Generate class content (just the members)
|
|
|
|
contentLines = [f"#pragma region GENERATED by ExportClassToCPPH.py"]
|
|
|
|
|
|
|
|
# Add class variables
|
|
|
|
for classVar in parsedVars:
|
|
|
|
if not firstVarOrFuncAccess:
|
|
|
|
firstVarOrFuncAccess = classVar.access
|
2025-03-13 00:30:18 +02:00
|
|
|
contentLines.extend(GenerateClassVarCode(classVar))
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
# Add newline between sections if both exist
|
|
|
|
if parsedVars and (parsedVtFuncs or parsedFuncs):
|
|
|
|
contentLines.append("")
|
|
|
|
|
|
|
|
# Add vtable functions
|
|
|
|
for index, vTableFunc in enumerate(parsedVtFuncs):
|
|
|
|
if not firstVarOrFuncAccess:
|
|
|
|
firstVarOrFuncAccess = vTableFunc.access
|
2025-03-13 00:30:18 +02:00
|
|
|
contentLines.extend(GenerateClassFuncCode(vTableFunc, index))
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
# Add newline between sections if both exist
|
|
|
|
if parsedVtFuncs and parsedFuncs:
|
|
|
|
contentLines.append("")
|
|
|
|
|
|
|
|
# Add regular functions
|
|
|
|
for func in parsedFuncs:
|
|
|
|
if not firstVarOrFuncAccess:
|
|
|
|
firstVarOrFuncAccess = func.access
|
2025-03-13 00:30:18 +02:00
|
|
|
contentLines.extend(GenerateClassFuncCode(func))
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
contentLines.append("#pragma endregion")
|
|
|
|
|
|
|
|
# Insert access specifier if needed
|
|
|
|
if not firstVarOrFuncAccess:
|
2025-03-09 03:04:02 +02:00
|
|
|
contentLines.insert(1, "public:")
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
return contentLines
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
def GenerateClassDefinition(targetClass: ClassName, forwardDeclare: bool = False) -> list[str]:
|
2025-03-09 01:10:59 +02:00
|
|
|
"""Generate a class definition from a list of methods."""
|
2025-03-13 00:30:18 +02:00
|
|
|
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
|
|
|
|
|
|
|
|
def GenerateHeaderCode(targetClass: ClassName) -> list[str]:
|
|
|
|
"""Generate header code for a standard class (not nested)."""
|
|
|
|
# Reset processed classes for this generation
|
|
|
|
global processedClasses, forwardDeclarations
|
|
|
|
processedClasses = set()
|
|
|
|
forwardDeclarations = set()
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
# Mark target class as processed to avoid self-dependency issues
|
|
|
|
processedClasses.add(targetClass.namespacedClassedName)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
# Get all parsed elements for the target class
|
2025-03-17 02:09:24 +02:00
|
|
|
allParsedElements = ClassGen.GetAllParsedClassVarsAndFuncs(targetClass)
|
2025-03-13 00:30:18 +02:00
|
|
|
|
|
|
|
# Generate the target class definition
|
|
|
|
classContent = GenerateClassContent(allParsedElements)
|
|
|
|
classDefinition = GenerateClassDefinition(targetClass)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
fullClassDefinition = classDefinition
|
|
|
|
if (classContent):
|
|
|
|
fullClassDefinition = fullClassDefinition[:len(classDefinition) - 1] + classContent + fullClassDefinition[len(classDefinition) - 1:]
|
2025-03-09 03:04:02 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
# Wrap target class in namespace blocks if needed
|
2025-03-09 01:10:59 +02:00
|
|
|
if targetClass.namespaces:
|
|
|
|
namespaceCode = []
|
|
|
|
indentLevel = ""
|
|
|
|
|
|
|
|
# Opening namespace blocks with increasing indentation
|
|
|
|
for namespace in targetClass.namespaces:
|
|
|
|
namespaceCode.append(f"{indentLevel}namespace {namespace} {{")
|
|
|
|
indentLevel += "\t"
|
2025-03-13 00:30:18 +02:00
|
|
|
|
|
|
|
for cls in targetClass.classes:
|
2025-03-17 02:09:24 +02:00
|
|
|
clsType: str = ClassGen.GetClassTypeFromParsedSigs(ClassName(targetClass.namespacedClassedName), allParsedElements)
|
2025-03-13 00:30:18 +02:00
|
|
|
namespaceCode.append(f"{indentLevel}{clsType if clsType else 'class'} {cls} {{")
|
|
|
|
indentLevel += "\t"
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
indentedClassDefinition = [f"{indentLevel if '#pragma' not in line else ''}{line}" for line in fullClassDefinition]
|
2025-03-09 03:04:02 +02:00
|
|
|
namespaceCode.extend(indentedClassDefinition)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
for cls in reversed(targetClass.classes):
|
|
|
|
indentLevel = indentLevel[:-1] # Remove one level of indentation
|
|
|
|
namespaceCode.append(f"{indentLevel}}}")
|
|
|
|
|
2025-03-09 01:10:59 +02:00
|
|
|
for namespace in reversed(targetClass.namespaces):
|
|
|
|
indentLevel = indentLevel[:-1] # Remove one level of indentation
|
|
|
|
namespaceCode.append(f"{indentLevel}}}")
|
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
fullClassDefinition = namespaceCode
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
# Combine all parts of the header
|
2025-03-09 03:04:02 +02:00
|
|
|
headerParts = ["#pragma once", r"#include <EGSDK\Imports.h>", ""]
|
2025-03-13 00:30:18 +02:00
|
|
|
|
|
|
|
# Add the target class definition
|
|
|
|
headerParts.extend(fullClassDefinition)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
return headerParts
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
def WriteHeaderToFile(targetClass: ClassName, headerCode: str, fileName: str) -> bool:
|
2025-03-09 01:10:59 +02:00
|
|
|
"""Write the generated header code to a file."""
|
2025-03-09 03:04:02 +02:00
|
|
|
outputFolderPath: str = Config.HEADER_OUTPUT_PATH
|
|
|
|
|
2025-03-09 01:10:59 +02:00
|
|
|
if targetClass.namespaces:
|
|
|
|
# Create folder structure for namespaces
|
|
|
|
classFolderPath: str = os.path.join(*targetClass.namespaces)
|
2025-03-09 03:04:02 +02:00
|
|
|
outputFolderPath: str = os.path.join(outputFolderPath, classFolderPath)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
# Create directory if it doesn't exist
|
|
|
|
os.makedirs(outputFolderPath, exist_ok=True)
|
|
|
|
# Output file path is inside the namespace folder
|
2025-03-09 03:04:02 +02:00
|
|
|
outputFilePath: str = os.path.join(classFolderPath, fileName)
|
2025-03-09 01:10:59 +02:00
|
|
|
else:
|
2025-03-09 03:04:02 +02:00
|
|
|
# Create directory if it doesn't exist
|
|
|
|
os.makedirs(outputFolderPath, exist_ok=True)
|
2025-03-09 01:10:59 +02:00
|
|
|
# No namespace, just save in current directory
|
2025-03-09 03:04:02 +02:00
|
|
|
outputFilePath: str = fileName
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
outputFilePath = os.path.join(Config.HEADER_OUTPUT_PATH, outputFilePath)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
|
|
|
try:
|
|
|
|
with open(outputFilePath, 'w') as headerFile:
|
|
|
|
headerFile.write(headerCode)
|
|
|
|
print(f"Header file '{outputFilePath}' created successfully.")
|
|
|
|
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
print(f"Error writing header file '{outputFilePath}': {e}")
|
|
|
|
return False
|
|
|
|
|
|
|
|
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.
|
2025-03-09 03:04:02 +02:00
|
|
|
"""
|
2025-03-13 00:30:18 +02:00
|
|
|
global processedClasses
|
|
|
|
|
|
|
|
# Skip if we've already processed this class
|
|
|
|
if targetClass.namespacedClassedName in processedClasses:
|
|
|
|
print(f"Already processed class {targetClass.namespacedClassedName}, skipping.")
|
|
|
|
return
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-13 00:30:18 +02:00
|
|
|
# Add to processed set to prevent infinite recursion
|
|
|
|
processedClasses.add(targetClass.namespacedClassedName)
|
|
|
|
|
2025-03-09 01:10:59 +02:00
|
|
|
# Generate the header code
|
2025-03-13 00:30:18 +02:00
|
|
|
headerCodeLines = GenerateHeaderCode(targetClass)
|
2025-03-09 03:04:02 +02:00
|
|
|
if not headerCodeLines:
|
2025-03-13 00:30:18 +02:00
|
|
|
print(f"No functions were found for class {targetClass.namespacedClassedName}, therefore will not generate.")
|
2025-03-09 01:10:59 +02:00
|
|
|
return
|
2025-03-09 03:04:02 +02:00
|
|
|
headerCode: str = "\n".join(headerCodeLines)
|
2025-03-09 01:10:59 +02:00
|
|
|
|
2025-03-09 03:04:02 +02:00
|
|
|
WriteHeaderToFile(targetClass, headerCode, f"{targetClass.name}.h")
|