update scripts

This commit is contained in:
EricPlayZ
2025-03-04 02:19:36 +02:00
parent 8b226df725
commit 65553c05f9
4 changed files with 28041 additions and 171 deletions

View File

@ -108,6 +108,7 @@ class ClassName:
@dataclass(frozen=True) @dataclass(frozen=True)
class ParsedFunction: class ParsedFunction:
"""Parse a demangled function signature and return an instance.""" """Parse a demangled function signature and return an instance."""
fullFuncSig: str = ""
type: str = "" type: str = ""
access: str = "" access: str = ""
returnType: Optional[ClassName] = None returnType: Optional[ClassName] = None
@ -120,6 +121,7 @@ class ParsedFunction:
global virtualFuncPlaceholderCounter global virtualFuncPlaceholderCounter
global virtualFuncDuplicateCounter global virtualFuncDuplicateCounter
object.__setattr__(self, "fullFuncSig", signature)
object.__setattr__(self, "type", "") object.__setattr__(self, "type", "")
object.__setattr__(self, "access", "") object.__setattr__(self, "access", "")
object.__setattr__(self, "returnType", None) object.__setattr__(self, "returnType", None)
@ -214,15 +216,14 @@ class ParsedFunction:
lastClassSeparatorIndex = i lastClassSeparatorIndex = i
if lastClassSeparatorIndex != -1: if lastClassSeparatorIndex != -1:
classAndFuncName: str = remainingInputBeforeParamsParen.strip() classAndFuncName: str = remainingInputBeforeParamsParen
className: str = classAndFuncName[:lastClassSeparatorIndex] className: str = classAndFuncName[:lastClassSeparatorIndex]
funcName: str = classAndFuncName[lastClassSeparatorIndex+2:] funcName: str = classAndFuncName[lastClassSeparatorIndex+2:]
else: else:
returnType = remainingInputBeforeParamsParen.strip() returnType = remainingInputBeforeParamsParen
if funcName.startswith("~"): if funcName.startswith("~"):
return return
#returnType = returnType.removeprefix("virtual").strip()
if isDuplicateFunc: if isDuplicateFunc:
if signature not in virtualFuncDuplicateCounter: if signature not in virtualFuncDuplicateCounter:
virtualFuncDuplicateCounter[signature] = 0 virtualFuncDuplicateCounter[signature] = 0
@ -255,10 +256,7 @@ class ParsedClassVar:
className: Optional[ClassName] = None className: Optional[ClassName] = None
varName: str = "" varName: str = ""
def __init__(self, signature: str, onlyVirtualFuncs: bool): def __init__(self, signature: str):
global virtualFuncPlaceholderCounter
global virtualFuncDuplicateCounter
object.__setattr__(self, "access", "") object.__setattr__(self, "access", "")
object.__setattr__(self, "varType", None) object.__setattr__(self, "varType", None)
object.__setattr__(self, "className", None) object.__setattr__(self, "className", None)
@ -266,19 +264,6 @@ class ParsedClassVar:
signature = signature.strip() signature = signature.strip()
isDuplicateFunc: bool = False
isIDAGeneratedType: bool = False
isIDAGeneratedTypeParsed: bool = False
if (signature.startswith("DUPLICATE_FUNC")):
isDuplicateFunc = True
signature = signature.removeprefix("DUPLICATE_FUNC").strip()
if (signature.startswith("IDA_GEN_TYPE")):
isIDAGeneratedType = True
signature = signature.removeprefix("IDA_GEN_TYPE").strip()
elif (signature.startswith("IDA_GEN_PARSED")):
isIDAGeneratedTypeParsed = True
signature = signature.removeprefix("IDA_GEN_PARSED").strip()
access: str = "" access: str = ""
if signature.startswith("public:"): if signature.startswith("public:"):
access = "public" access = "public"
@ -292,99 +277,74 @@ class ParsedClassVar:
paramsOpenParenIndex: int = signature.find('(') paramsOpenParenIndex: int = signature.find('(')
paramsCloseParenIndex: int = signature.rfind(')') paramsCloseParenIndex: int = signature.rfind(')')
if paramsOpenParenIndex != -1 and paramsCloseParenIndex != -1: if paramsOpenParenIndex == -1 and paramsCloseParenIndex == -1:
params: str = signature[paramsOpenParenIndex + 1:paramsCloseParenIndex]
remainingInputBeforeParamsParen: str = signature[:paramsOpenParenIndex].strip()
remainingInputAfterParamsParen: str = signature[paramsCloseParenIndex + 1:].strip()
const: str = "const" if "const" in remainingInputAfterParamsParen else ""
varType: str = "" varType: str = ""
classAndVarName: str = "" classAndVarName: str = ""
className: str = "" className: str = ""
varName: str = "" varName: str = ""
if not isIDAGeneratedType:
# Find the last space outside of angle brackets
lastSpaceIndex: int = -1
lastClassSeparatorIndex: int = -1
templateDepth: int = 0 # Find the last space outside of angle brackets
for i in range(len(remainingInputBeforeParamsParen)): lastSpaceIndex: int = -1
if remainingInputBeforeParamsParen[i] == '<': lastClassSeparatorIndex: int = -1
templateDepth: int = 0
for i in range(len(signature)):
if signature[i] == '<':
templateDepth += 1
elif signature[i] == '>':
templateDepth -= 1
elif templateDepth == 0 and signature[i] == ' ':
lastSpaceIndex = i
if lastSpaceIndex != -1:
# Split at the last space outside angle brackets
varType = signature[:lastSpaceIndex].strip()
classAndVarName = signature[lastSpaceIndex+1:].strip()
templateDepth = 0
# Find the last class separator outside of angle brackets
for i in range(len(classAndVarName)):
if classAndVarName[i] == '<':
templateDepth += 1 templateDepth += 1
elif remainingInputBeforeParamsParen[i] == '>': elif classAndVarName[i] == '>':
templateDepth -= 1 templateDepth -= 1
elif templateDepth == 0 and remainingInputBeforeParamsParen[i] == ' ': elif templateDepth == 0 and classAndVarName[i:i+2] == '::':
lastSpaceIndex = i lastClassSeparatorIndex = i
if lastSpaceIndex != -1: if lastClassSeparatorIndex != -1:
# Split at the last space outside angle brackets className = classAndVarName[:lastClassSeparatorIndex]
varType = remainingInputBeforeParamsParen[:lastSpaceIndex].strip() varName = classAndVarName[lastClassSeparatorIndex+2:]
classAndVarName = remainingInputBeforeParamsParen[lastSpaceIndex+1:].strip()
templateDepth = 0
# Find the last class separator outside of angle brackets
for i in range(len(classAndVarName)):
if classAndVarName[i] == '<':
templateDepth += 1
elif classAndVarName[i] == '>':
templateDepth -= 1
elif templateDepth == 0 and classAndVarName[i:i+2] == '::':
lastClassSeparatorIndex = i
if lastClassSeparatorIndex != -1:
className = classAndVarName[:lastClassSeparatorIndex]
varName = classAndVarName[lastClassSeparatorIndex+2:]
else:
className = "::".join(classAndVarName.split("::")[:-1])
varName = classAndVarName.split("::")[-1]
else: else:
templateDepth = 0 className = "::".join(classAndVarName.split("::")[:-1])
# Find the last class separator outside of angle brackets varName = classAndVarName.split("::")[-1]
for i in range(len(remainingInputBeforeParamsParen)):
if remainingInputBeforeParamsParen[i] == '<':
templateDepth += 1
elif remainingInputBeforeParamsParen[i] == '>':
templateDepth -= 1
elif templateDepth == 0 and remainingInputBeforeParamsParen[i:i+2] == '::':
lastClassSeparatorIndex = i
if lastClassSeparatorIndex != -1:
classAndVarName: str = remainingInputBeforeParamsParen.strip()
className: str = classAndVarName[:lastClassSeparatorIndex]
varName: str = classAndVarName[lastClassSeparatorIndex+2:]
else: else:
varType = remainingInputBeforeParamsParen.strip() templateDepth = 0
# Find the last class separator outside of angle brackets
for i in range(len(signature)):
if signature[i] == '<':
templateDepth += 1
elif signature[i] == '>':
templateDepth -= 1
elif templateDepth == 0 and signature[i:i+2] == '::':
lastClassSeparatorIndex = i
if lastClassSeparatorIndex != -1:
classAndVarName: str = signature
className: str = classAndVarName[:lastClassSeparatorIndex]
varName: str = classAndVarName[lastClassSeparatorIndex+2:]
if varName.startswith("~"):
return
#varType = varType.removeprefix("virtual").strip()
if isDuplicateFunc:
if signature not in virtualFuncDuplicateCounter:
virtualFuncDuplicateCounter[signature] = 0
virtualFuncDuplicateCounter[signature] += 1
varName = f"_{varName}{virtualFuncDuplicateCounter[signature]}"
type = "func" if not (onlyVirtualFuncs or "virtual" in varType) else ("basic_vfunc" if isIDAGeneratedType or isIDAGeneratedTypeParsed or isDuplicateFunc else "vfunc")
object.__setattr__(self, "type", type)
object.__setattr__(self, "access", access if access else "public") object.__setattr__(self, "access", access if access else "public")
object.__setattr__(self, "varType", ClassName(varType) if varType else None) object.__setattr__(self, "varType", ClassName(varType) if varType else None)
object.__setattr__(self, "className", ClassName(className) if className else None) object.__setattr__(self, "className", ClassName(className) if className else None)
object.__setattr__(self, "varName", varName) object.__setattr__(self, "varName", varName)
return return
# Generate a simple virtual void function
if onlyVirtualFuncs and signature == "_purecall":
virtualFuncPlaceholderCounter += 1
object.__setattr__(self, "type", "stripped_vfunc")
object.__setattr__(self, "access", access if access else "public")
object.__setattr__(self, "varType", ClassName("virtual void"))
object.__setattr__(self, "varName", f"_StrippedVFunc{virtualFuncPlaceholderCounter}")
# Global caches # Global caches
parsedFuncsByClass: dict[ClassName, list[ParsedFunction]] = {} # Cache of parsed functions by class name parsedClassVarsByClass: dict[ClassName, list[ParsedClassVar]] = {} # Cache of parsed class vars by class name
parsedVTableFuncsByClass: dict[ClassName, list[ParsedFunction]] = {} # Cache of parsed functions by class name parsedVTableFuncsByClass: dict[ClassName, list[ParsedFunction]] = {} # Cache of parsed functions by class name
allFunctionsAreParsed = False # Flag to indicate if all functions have been parsed parsedFuncsByClass: dict[ClassName, list[ParsedFunction]] = {} # Cache of parsed functions by class name
allClassVarsAreParsed = False # Flag to indicate if all class vars have been parsed
allFuncsAreParsed = False # Flag to indicate if all functions have been parsed
processedClasses: set[ClassName] = set() # Track classes we've already processed to avoid recursion processedClasses: set[ClassName] = set() # Track classes we've already processed to avoid recursion
template_class_info = {} # Store information about detected template classes template_class_info = {} # Store information about detected template classes
@ -392,8 +352,8 @@ template_class_info = {} # Store information about detected template classes
# IDA util functions # IDA util functions
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def DemangleFuncSig(funcSig: str) -> str: def DemangleSig(sig: str) -> str:
return idaapi.demangle_name(funcSig, idaapi.MNG_LONG_FORM) return idaapi.demangle_name(sig, idaapi.MNG_LONG_FORM)
def GetMangledTypePrefix(targetClass: ClassName) -> str: def GetMangledTypePrefix(targetClass: ClassName) -> str:
""" """
@ -564,6 +524,90 @@ def ExtractParamNames(params: str) -> str:
newParams: str = ", ".join(paramNames) newParams: str = ", ".join(paramNames)
return newParams return newParams
def GetDemangledExportedSigs() -> list[str]:
"""
Generate a list of demangled function sigs from IDA's database
"""
cachedFilePath = os.path.join(CACHE_FOLDER, "demangled_exported_sigs_cache.txt")
if os.path.exists(cachedFilePath):
try:
with open(cachedFilePath, "r") as cachedFile:
return [line.strip() for line in cachedFile if line.strip()]
except Exception as e:
PrintMsg(f"Error opening '{cachedFilePath}' for reading: {e}\n")
demangledExportedSigsList: list[str] = []
for i in range(idc.get_entry_qty()):
ea: int = idc.get_entry(i)
exportedSig: str = idc.get_func_name(ea) or idc.get_name(ea)
if not exportedSig:
continue
demangledExportedSig: str = DemangleSig(exportedSig)
if not demangledExportedSig:
continue
if demangledExportedSig not in demangledExportedSigsList:
demangledExportedSigsList.append(demangledExportedSig)
os.makedirs(CACHE_FOLDER, exist_ok=True)
try:
with open(cachedFilePath, "w") as cachedFile:
for demangledExportedSig in demangledExportedSigsList:
cachedFile.write(demangledExportedSig + "\n")
except Exception as e:
PrintMsg(f"Error opening '{cachedFilePath}' for writing: {e}\n")
return demangledExportedSigsList
def GetParsedClassVars(targetClass: Optional[ClassName] = None) -> 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.
Caches results for better performance on subsequent calls.
"""
global parsedClassVarsByClass, allClassVarsAreParsed, parsedFuncsByClass
if not allClassVarsAreParsed:
for demangledExportedSig in GetDemangledExportedSigs():
isAlreadyPresentInParsedFuncs: bool = False
for parsedFuncList in list(parsedFuncsByClass.values()):
for parsedFunc in parsedFuncList:
if demangledExportedSig in parsedFunc.fullFuncSig:
isAlreadyPresentInParsedFuncs = True
break
if isAlreadyPresentInParsedFuncs:
break
if isAlreadyPresentInParsedFuncs:
continue
# Skip invalid functions
if demangledExportedSig.endswith("::$TSS0") or "::`vftable'" in demangledExportedSig:
continue
parsedClassVar: ParsedClassVar = ParsedClassVar(demangledExportedSig)
if not parsedClassVar.className or not parsedClassVar.varName:
PrintMsg(f"Failed parsing class var sig: \"{demangledExportedSig}\"")
continue
if parsedClassVar.className not in parsedClassVarsByClass:
parsedClassVarsByClass[parsedClassVar.className] = []
parsedClassVarsByClass[parsedClassVar.className].append(parsedClassVar)
allClassVarsAreParsed = True
# Return the requested functions
if not targetClass:
# Return all parsed functions
allClassVars: list[ParsedClassVar] = []
for classVars in parsedClassVarsByClass.values():
allClassVars.extend(classVars)
return allClassVars
else:
# Return only functions for the specified class
return parsedClassVarsByClass.get(targetClass, [])
def GetDemangledVTableFuncSigs(targetClass: ClassName, targetClassRTTIName: str = "") -> list[tuple[str, str]]: def GetDemangledVTableFuncSigs(targetClass: ClassName, targetClassRTTIName: str = "") -> list[tuple[str, str]]:
""" """
Get the ordered list of function names from a class's vtable. Get the ordered list of function names from a class's vtable.
@ -588,7 +632,7 @@ def GetDemangledVTableFuncSigs(targetClass: ClassName, targetClassRTTIName: str
# Force function decompilation to generate the full function type signature # Force function decompilation to generate the full function type signature
funcSig: str = idc.get_func_name(ptr) funcSig: str = idc.get_func_name(ptr)
demangledFuncSig: str = DemangleFuncSig(funcSig) demangledFuncSig: str = DemangleSig(funcSig)
demangledFuncSig = demangledFuncSig if demangledFuncSig else funcSig demangledFuncSig = demangledFuncSig if demangledFuncSig else funcSig
rawType: str = "" rawType: str = ""
@ -640,55 +684,16 @@ def GetParsedVTableFuncs(targetClass: ClassName) -> list[ParsedFunction]:
return parsedVTableFuncsByClass.get(targetClass, []) return parsedVTableFuncsByClass.get(targetClass, [])
def GetDemangledFuncSigs() -> list[str]:
"""
Generate a list of demangled function sigs from IDA's database
"""
cachedFilePath = os.path.join(CACHE_FOLDER, "demangled_func_sigs_cache.txt")
if os.path.exists(cachedFilePath):
try:
with open(cachedFilePath, "r") as cachedFile:
return [line.strip() for line in cachedFile if line.strip()]
except Exception as e:
PrintMsg(f"Error opening '{cachedFilePath}' for reading: {e}\n")
demangledFuncSigsList: list[str] = []
for i in range(idc.get_entry_qty()):
ea: int = idc.get_entry(i)
funcSig: str = idc.get_func_name(ea) or idc.get_name(ea)
if not funcSig:
continue
demangledFuncSig: str = DemangleFuncSig(funcSig)
if not demangledFuncSig:
continue
if demangledFuncSig not in demangledFuncSigsList:
demangledFuncSigsList.append(demangledFuncSig)
os.makedirs(CACHE_FOLDER, exist_ok=True)
try:
with open(cachedFilePath, "w") as cachedFile:
for demangledFuncSig in demangledFuncSigsList:
cachedFile.write(demangledFuncSig + "\n")
except Exception as e:
PrintMsg(f"Error opening '{cachedFilePath}' for writing: {e}\n")
return demangledFuncSigsList
def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFunction]: def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFunction]:
""" """
Collect and parse all function signatures from the IDA database. Collect and parse all function signatures from the IDA database.
If target_class is provided, only return functions for that class. If target_class is provided, only return functions for that class.
Caches results for better performance on subsequent calls. Caches results for better performance on subsequent calls.
""" """
global parsedFuncsByClass, allFunctionsAreParsed global parsedFuncsByClass, allFuncsAreParsed
if not allFunctionsAreParsed: if not allFuncsAreParsed:
for demangledFuncSig in GetDemangledFuncSigs(): for demangledFuncSig in GetDemangledExportedSigs():
demangledFuncSig: str
# Skip invalid functions # Skip invalid functions
if demangledFuncSig.endswith("::$TSS0") or "::`vftable'" in demangledFuncSig: if demangledFuncSig.endswith("::$TSS0") or "::`vftable'" in demangledFuncSig:
continue continue
@ -702,16 +707,15 @@ def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFuncti
parsedFuncsByClass[parsedFunc.className] = [] parsedFuncsByClass[parsedFunc.className] = []
parsedFuncsByClass[parsedFunc.className].append(parsedFunc) parsedFuncsByClass[parsedFunc.className].append(parsedFunc)
allFunctionsAreParsed = True allFuncsAreParsed = True
# Return the requested functions # Return the requested functions
if not targetClass: if not targetClass:
# Return all parsed functions # Return all parsed functions
all_functions: list[ParsedFunction] = [] allFuncs: list[ParsedFunction] = []
for funcs in parsedFuncsByClass.values(): for funcs in parsedFuncsByClass.values():
funcs: list[ParsedFunction] allFuncs.extend(funcs)
all_functions.extend(funcs) return allFuncs
return all_functions
else: else:
# Return only functions for the specified class # Return only functions for the specified class
return parsedFuncsByClass.get(targetClass, []) return parsedFuncsByClass.get(targetClass, [])
@ -721,6 +725,28 @@ def GetParsedFuncs(targetClass: Optional[ClassName] = None) -> list[ParsedFuncti
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
currentAccess: str = "public" currentAccess: str = "public"
def GenerateClassVarCode(classVar: ParsedClassVar, cleanedTypes: bool = True) -> str:
"""Generate code for a single class method."""
global currentAccess
access: str = f"{classVar.access}:\n " if classVar.access else " "
if currentAccess == classVar.access:
access = " "
else:
currentAccess = classVar.access
if classVar.varType:
varType: str = ReplaceIDATypes(classVar.varType.fullName)
varType = CleanType(varType) if cleanedTypes else classVar.varType.fullName
if varType:
varType += " "
else:
varType: str = ""
varType = "GAME_IMPORT " + varType
classVarSig: str = f"{varType}{classVar.varName}"
return f"{access}{classVarSig};"
def GenerateClassFuncCode(func: ParsedFunction, cleanedTypes: bool = True, vtFuncIndex: int = 0) -> str: def GenerateClassFuncCode(func: ParsedFunction, cleanedTypes: bool = True, vtFuncIndex: int = 0) -> str:
"""Generate code for a single class method.""" """Generate code for a single class method."""
global currentAccess global currentAccess
@ -763,67 +789,79 @@ def GenerateClassFuncCode(func: ParsedFunction, cleanedTypes: bool = True, vtFun
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})" 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};" return f"{access}{funcSig};"
def GenerateClassDefinition(targetClass: ClassName, allParsedClassFuncs: tuple[list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool = True) -> str: def GenerateClassDefinition(targetClass: ClassName, allParsedClassVarsAndFuncs: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool = True) -> str:
"""Generate a class definition from a list of methods.""" """Generate a class definition from a list of methods."""
# Build the class definition # Build the class definition
if not allParsedClassFuncs[0] and not allParsedClassFuncs[1]: if not allParsedClassVarsAndFuncs[0] and not allParsedClassVarsAndFuncs[1] and not allParsedClassVarsAndFuncs[2]:
return "" return ""
if not targetClass.type: if not targetClass.type:
targetClassType: str = "" targetClassType: str = ""
for parsedFuncsList in allParsedClassFuncs: for parsedClassVarsList in allParsedClassVarsAndFuncs[1:]:
for parsedFunc in parsedFuncsList: for parsedClassVar in parsedClassVarsList:
if parsedFunc.returnType and parsedFunc.returnType.namespacedName == targetClass.namespacedName and parsedFunc.returnType.type: if parsedClassVar.returnType and parsedClassVar.returnType.namespacedName == targetClass.namespacedName and parsedClassVar.returnType.type:
targetClassType = parsedFunc.returnType.type targetClassType = parsedClassVar.returnType.type
object.__setattr__(targetClass, "type", targetClassType) object.__setattr__(targetClass, "type", targetClassType)
break break
if targetClassType: if targetClassType:
break break
if not targetClassType:
for parsedClassVar in allParsedClassVarsAndFuncs[0]:
if parsedClassVar.varType and parsedClassVar.varType.namespacedName == targetClass.namespacedName and parsedClassVar.varType.type:
targetClassType = parsedClassVar.varType.type
object.__setattr__(targetClass, "type", targetClassType)
break
classLines: list[str] = [f"{targetClass.type if targetClass.type else 'class'} {targetClass.name} {{", " #pragma region GENERATED by ExportClassToCPPH.py"] classLines: list[str] = [f"{targetClass.type if targetClass.type else 'class'} {targetClass.name} {{", " #pragma region GENERATED by ExportClassToCPPH.py"]
firstFuncAccess: str = "" firstVarOrFuncAccess: str = ""
for index, vTableFunc in enumerate(allParsedClassFuncs[0]): for classVar in allParsedClassVarsAndFuncs[0]:
if not firstFuncAccess: if not firstVarOrFuncAccess:
firstFuncAccess = vTableFunc.access firstVarOrFuncAccess = classVar.access
classLines.append(GenerateClassFuncCode(vTableFunc, cleanedTypes, index)) classLines.append(GenerateClassVarCode(classVar, cleanedTypes))
if allParsedClassFuncs[0] and allParsedClassFuncs[1]: if allParsedClassVarsAndFuncs[0] and allParsedClassVarsAndFuncs[1] and allParsedClassVarsAndFuncs[2]:
classLines.append("") classLines.append("")
for func in allParsedClassFuncs[1]: for index, vTableFunc in enumerate(allParsedClassVarsAndFuncs[1]):
if not firstFuncAccess: if not firstVarOrFuncAccess:
firstFuncAccess = func.access firstVarOrFuncAccess = vTableFunc.access
classLines.append(GenerateClassFuncCode(vTableFunc, cleanedTypes, index))
if allParsedClassVarsAndFuncs[0] and allParsedClassVarsAndFuncs[1] and allParsedClassVarsAndFuncs[2]:
classLines.append("")
for func in allParsedClassVarsAndFuncs[2]:
if not firstVarOrFuncAccess:
firstVarOrFuncAccess = func.access
classLines.append(GenerateClassFuncCode(func, cleanedTypes)) classLines.append(GenerateClassFuncCode(func, cleanedTypes))
classLines.append(" #pragma endregion") classLines.append(" #pragma endregion")
classLines.append("};") classLines.append("};")
# Insert first function access if there is any, otherwise just make it public by default # Insert first function access if there is any, otherwise just make it public by default
classLines.insert(2, f"{firstFuncAccess if firstFuncAccess else 'public'}:") classLines.insert(2, f"{firstVarOrFuncAccess if firstVarOrFuncAccess else 'public'}:")
return "\n".join(classLines) return "\n".join(classLines)
def GenerateHeaderCode(targetClass: ClassName, allParsedClassFuncs: tuple[list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool = True) -> str: def GenerateHeaderCode(targetClass: ClassName, allParsedClassVarsAndFuncs: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]], cleanedTypes: bool = True) -> str:
""" """
Generate a C++ header file for the target class. Generate a C++ header file for the target class.
Organizes methods with vtable order first, then remaining methods. Organizes methods with vtable order first, then remaining methods.
Also handles dependencies by generating classes for missing types. Also handles dependencies by generating classes for missing types.
""" """
# Generate the class definition # Generate the class definition
classDefinition: str = GenerateClassDefinition(targetClass, allParsedClassFuncs, cleanedTypes) classDefinition: str = GenerateClassDefinition(targetClass, allParsedClassVarsAndFuncs, cleanedTypes)
if not classDefinition: if not classDefinition:
return "" return ""
# Combine all parts of the header # Combine all parts of the header
header_parts = ["#pragma once\n", r"#include <EGSDK\Imports.h>", "\n"] headerParts = ["#pragma once\n", r"#include <EGSDK\Imports.h>", "\n"]
# Add the main class definition # Add the main class definition
header_parts.append("\n" + classDefinition) headerParts.append("\n" + classDefinition)
return "".join(header_parts) return "".join(headerParts)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Main functionality # Main functionality
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
def GetAllParsedClassFuncs(targetClass: ClassName) -> tuple[list[ParsedFunction], list[ParsedFunction]]: def GetAllParsedClassVarsAndFuncs(targetClass: ClassName) -> tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]]:
parsedVTableClassFuncs: list[ParsedFunction] = GetParsedVTableFuncs(targetClass) parsedVTableClassFuncs: list[ParsedFunction] = GetParsedVTableFuncs(targetClass)
if not parsedVTableClassFuncs: if not parsedVTableClassFuncs:
PrintMsg(f"No matching VTable function signatures were found for {targetClass.fullName}.\n") PrintMsg(f"No matching VTable function signatures were found for {targetClass.fullName}.\n")
@ -831,13 +869,18 @@ def GetAllParsedClassFuncs(targetClass: ClassName) -> tuple[list[ParsedFunction]
parsedClassFuncs: list[ParsedFunction] = GetParsedFuncs(targetClass) parsedClassFuncs: list[ParsedFunction] = GetParsedFuncs(targetClass)
if not parsedClassFuncs: if not parsedClassFuncs:
PrintMsg(f"No matching function signatures were found for {targetClass.fullName}.\n") PrintMsg(f"No matching function signatures were found for {targetClass.fullName}.\n")
parsedClassVars: list[ParsedClassVar] = GetParsedClassVars(targetClass)
if not parsedClassVars:
PrintMsg(f"No matching class var signatures were found for {targetClass.fullName}.\n")
# Get non-vtable methods # Get non-vtable methods
finalParsedClassFuncs: list[ParsedFunction] = [ finalParsedClassFuncs: list[ParsedFunction] = [
parsedFunc for parsedFunc in parsedClassFuncs parsedFunc for parsedFunc in parsedClassFuncs
if parsedFunc not in parsedVTableClassFuncs if parsedFunc not in parsedVTableClassFuncs
] ]
return (parsedVTableClassFuncs, finalParsedClassFuncs) return (parsedClassVars, parsedVTableClassFuncs, finalParsedClassFuncs)
def WriteHeaderToFile(targetClass: ClassName, headerCode: str, fileName: str = "") -> bool: def WriteHeaderToFile(targetClass: ClassName, headerCode: str, fileName: str = "") -> bool:
if targetClass.namespaces: if targetClass.namespaces:
@ -876,22 +919,22 @@ def ExportClassHeader(targetClass: ClassName):
# Add the target class to processed classes to prevent recursion # Add the target class to processed classes to prevent recursion
processedClasses.add(targetClass) processedClasses.add(targetClass)
allParsedClassFuncs: tuple[list[ParsedFunction], list[ParsedFunction]] = GetAllParsedClassFuncs(targetClass) allParsedClassVarsAndFuncs: tuple[list[ParsedClassVar], list[ParsedFunction], list[ParsedFunction]] = GetAllParsedClassVarsAndFuncs(targetClass)
headerCode: str = GenerateHeaderCode(targetClass, allParsedClassFuncs) headerCode: str = GenerateHeaderCode(targetClass, allParsedClassVarsAndFuncs)
if not headerCode: if not headerCode:
PrintMsg(f"No functions were found for class {targetClass.fullName}, therefore will not generate.") PrintMsg(f"No functions were found for class {targetClass.fullName}, therefore will not generate.")
return return
WriteHeaderToFile(targetClass, headerCode) WriteHeaderToFile(targetClass, headerCode)
nonCleanedHeaderCode: str = GenerateHeaderCode(targetClass, allParsedClassFuncs, False) nonCleanedHeaderCode: str = GenerateHeaderCode(targetClass, allParsedClassVarsAndFuncs, False)
WriteHeaderToFile(targetClass, nonCleanedHeaderCode, f"{targetClass.name}-unclean.h") WriteHeaderToFile(targetClass, nonCleanedHeaderCode, f"{targetClass.name}-unclean.h")
def Main(): def Main():
"""Main entry point for the script.""" """Main entry point for the script."""
# Ask user for target class # Ask user for target class
#targetClass = ida_kernwin.ask_str("IModelObject", 0, "Enter target class name (supports namespaces and templates):") #targetClass = ida_kernwin.ask_str("IModelObject", 0, "Enter target class name (supports namespaces and templates):")
targetClassName: str = "CRTTI" targetClassName: str = "CModelObject"
if not targetClassName: if not targetClassName:
PrintMsg("No target class specified. Aborting.\n") PrintMsg("No target class specified. Aborting.\n")
return return

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,23 @@
#include <EGSDK\Imports.h> #include <EGSDK\Imports.h>
class CModelObject { class CModelObject {
#pragma region GENERATED by ExportClassToCPPH.py
public:
static bool __INTERNAL_crtti_fields_initialized_field;
static class rtti::Type & m_RTTI;
static class vec4 s_BoxColor;
static class vec4 s_HighlightedColor;
static class vec4 s_LockedBoxColor;
static bool s_RenderCollisionHulls;
static bool s_RenderFaces;
static float s_RenderHullEdgesDistance2;
static float s_RenderHullsDistance2;
static bool s_RenderTraceHulls;
static bool sm_AnimLoadBlocked;
protected:
static unsigned char near * sm_aMessageBuffer;
static int sm_nMessageBufferContent;
public: public:
VIRTUAL_CALL(0, __int64 __fastcall, sub_7E9B40, (__int64 a1, unsigned __int8 a2), a1, a2); VIRTUAL_CALL(0, __int64 __fastcall, sub_7E9B40, (__int64 a1, unsigned __int8 a2), a1, a2);
VIRTUAL_CALL(1, CModelObject *__fastcall, sub_663A40, (char a1), a1); VIRTUAL_CALL(1, CModelObject *__fastcall, sub_663A40, (char a1), a1);
@ -420,4 +437,5 @@ private:
GAME_IMPORT static class rtti::Type & __INTERNAL_type_factory(); GAME_IMPORT static class rtti::Type & __INTERNAL_type_factory();
public: public:
GAME_IMPORT static class rtti::Type & typeinfo(); GAME_IMPORT static class rtti::Type & typeinfo();
#pragma endregion
}; };

View File

@ -2,6 +2,23 @@
#include <EGSDK\Imports.h> #include <EGSDK\Imports.h>
class CModelObject { class CModelObject {
#pragma region GENERATED by ExportClassToCPPH.py
public:
static bool __INTERNAL_crtti_fields_initialized_field;
static rtti::Type& m_RTTI;
static vec4 s_BoxColor;
static vec4 s_HighlightedColor;
static vec4 s_LockedBoxColor;
static bool s_RenderCollisionHulls;
static bool s_RenderFaces;
static float s_RenderHullEdgesDistance2;
static float s_RenderHullsDistance2;
static bool s_RenderTraceHulls;
static bool sm_AnimLoadBlocked;
protected:
static unsigned char near* sm_aMessageBuffer;
static int sm_nMessageBufferContent;
public: public:
VIRTUAL_CALL(0, int64_t, sub_7E9B40, (int64_t a1, unsigned __int8 a2), a1, a2); VIRTUAL_CALL(0, int64_t, sub_7E9B40, (int64_t a1, unsigned __int8 a2), a1, a2);
VIRTUAL_CALL(1, CModelObject*, sub_663A40, (char a1), a1); VIRTUAL_CALL(1, CModelObject*, sub_663A40, (char a1), a1);
@ -420,4 +437,5 @@ private:
GAME_IMPORT static rtti::Type& __INTERNAL_type_factory(); GAME_IMPORT static rtti::Type& __INTERNAL_type_factory();
public: public:
GAME_IMPORT static rtti::Type& typeinfo(); GAME_IMPORT static rtti::Type& typeinfo();
#pragma endregion
}; };