mirror of
https://github.com/EricPlayZ/EGameTools.git
synced 2025-07-19 01:47:50 +08:00
259 lines
12 KiB
Python
259 lines
12 KiB
Python
from dataclasses import dataclass, field
|
|
from typing import Optional
|
|
|
|
from ExportClassToCPPH 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)
|
|
name: str = ""
|
|
namespacedName: str = ""
|
|
fullName: str = ""
|
|
type: str = ""
|
|
|
|
def __init__(self, fullName: str):
|
|
object.__setattr__(self, "namespaces", ())
|
|
object.__setattr__(self, "name", "")
|
|
object.__setattr__(self, "namespacedName", "")
|
|
object.__setattr__(self, "fullName", "")
|
|
object.__setattr__(self, "type", "")
|
|
|
|
fullName = (fullName or "").strip()
|
|
if not fullName:
|
|
return
|
|
|
|
object.__setattr__(self, "fullName", fullName)
|
|
types = Utils.ExtractTypesFromString(fullName)
|
|
if len(types) > 1:
|
|
if (types[0] in Utils.CLASS_TYPES):
|
|
object.__setattr__(self, "type", types[0])
|
|
fullName = types[1]
|
|
elif (len(types) > 2 and types[0] in Utils.FUNC_QUALIFIERS and types[1] in Utils.CLASS_TYPES):
|
|
object.__setattr__(self, "type", types[1])
|
|
fullName = types[2]
|
|
else:
|
|
return
|
|
|
|
parts = fullName.split("::")
|
|
if len(parts) == 1:
|
|
object.__setattr__(self, "name", parts[0])
|
|
object.__setattr__(self, "namespacedName", self.name)
|
|
else:
|
|
object.__setattr__(self, "namespaces", tuple(parts[:-1]))
|
|
object.__setattr__(self, "name", parts[-1])
|
|
object.__setattr__(self, "namespacedName", f"{'::'.join(self.namespaces)}::{self.name}")
|
|
|
|
virtualFuncPlaceholderCounter: int = 0 # Counter for placeholder virtual functions
|
|
virtualFuncDuplicateCounter: dict[str, int] = {} # Counter for duplicate virtual functions
|
|
|
|
@dataclass(frozen=True)
|
|
class ParsedFunction:
|
|
"""Parse a demangled function signature and return an instance."""
|
|
fullFuncSig: str = ""
|
|
type: str = ""
|
|
access: str = ""
|
|
returnType: Optional[ClassName] = None
|
|
className: Optional[ClassName] = None
|
|
funcName: str = ""
|
|
params: str = ""
|
|
const: bool = False
|
|
|
|
def __init__(self, signature: str, onlyVirtualFuncs: bool):
|
|
global virtualFuncPlaceholderCounter
|
|
global virtualFuncDuplicateCounter
|
|
|
|
object.__setattr__(self, "fullFuncSig", signature)
|
|
object.__setattr__(self, "type", "")
|
|
object.__setattr__(self, "access", "")
|
|
object.__setattr__(self, "returnType", None)
|
|
object.__setattr__(self, "className", None)
|
|
object.__setattr__(self, "funcName", "")
|
|
object.__setattr__(self, "params", "")
|
|
object.__setattr__(self, "const", False)
|
|
|
|
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 = ""
|
|
if signature.startswith("public:"):
|
|
access = "public"
|
|
elif signature.startswith("protected:"):
|
|
access = "protected"
|
|
elif signature.startswith("private:"):
|
|
access = "private"
|
|
signature = signature.removeprefix(f"{access}:").strip()
|
|
|
|
# Find parameters and const qualifier
|
|
paramsOpenParenIndex: int = signature.find('(')
|
|
paramsCloseParenIndex: int = signature.rfind(')')
|
|
|
|
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 ""
|
|
|
|
returnType: str = ""
|
|
classAndFuncName: str = ""
|
|
className: str = ""
|
|
funcName: str = ""
|
|
if not isIDAGeneratedType:
|
|
# Find the last space outside of angle brackets
|
|
lastSpaceIndex: int = -1
|
|
lastClassSeparatorIndex: int = -1
|
|
|
|
templateDepth: int = 0
|
|
for i in range(len(remainingInputBeforeParamsParen)):
|
|
if remainingInputBeforeParamsParen[i] == '<':
|
|
templateDepth += 1
|
|
elif remainingInputBeforeParamsParen[i] == '>':
|
|
templateDepth -= 1
|
|
elif templateDepth == 0 and remainingInputBeforeParamsParen[i] == ' ':
|
|
lastSpaceIndex = i
|
|
|
|
if lastSpaceIndex != -1:
|
|
# Split at the last space outside angle brackets
|
|
returnType = remainingInputBeforeParamsParen[:lastSpaceIndex].strip()
|
|
classAndFuncName = remainingInputBeforeParamsParen[lastSpaceIndex+1:].strip()
|
|
|
|
templateDepth = 0
|
|
# Find the last class separator outside of angle brackets
|
|
for i in range(len(classAndFuncName)):
|
|
if classAndFuncName[i] == '<':
|
|
templateDepth += 1
|
|
elif classAndFuncName[i] == '>':
|
|
templateDepth -= 1
|
|
elif templateDepth == 0 and classAndFuncName[i:i+2] == '::':
|
|
lastClassSeparatorIndex = i
|
|
|
|
if lastClassSeparatorIndex != -1:
|
|
className = classAndFuncName[:lastClassSeparatorIndex]
|
|
funcName = classAndFuncName[lastClassSeparatorIndex+2:]
|
|
else:
|
|
className = "::".join(classAndFuncName.split("::")[:-1])
|
|
funcName = classAndFuncName.split("::")[-1]
|
|
else:
|
|
templateDepth = 0
|
|
# Find the last class separator outside of angle brackets
|
|
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:
|
|
classAndFuncName: str = remainingInputBeforeParamsParen
|
|
className: str = classAndFuncName[:lastClassSeparatorIndex]
|
|
funcName: str = classAndFuncName[lastClassSeparatorIndex+2:]
|
|
else:
|
|
returnType = remainingInputBeforeParamsParen
|
|
|
|
if isDuplicateFunc:
|
|
if signature not in virtualFuncDuplicateCounter:
|
|
virtualFuncDuplicateCounter[signature] = 0
|
|
virtualFuncDuplicateCounter[signature] += 1
|
|
funcName = f"_{funcName}{virtualFuncDuplicateCounter[signature]}"
|
|
|
|
if onlyVirtualFuncs:
|
|
if isIDAGeneratedType or isIDAGeneratedTypeParsed or isDuplicateFunc or "virtual" not in returnType:
|
|
type = "basic_vfunc"
|
|
else:
|
|
type = "vfunc"
|
|
else:
|
|
if "virtual" not in returnType:
|
|
type = "func"
|
|
elif isIDAGeneratedType or isIDAGeneratedTypeParsed or isDuplicateFunc:
|
|
type = "basic_vfunc"
|
|
else:
|
|
type = "vfunc"
|
|
#type = "func" if not (onlyVirtualFuncs or "virtual" in returnType) 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, "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, "const", bool(const))
|
|
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, "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."""
|
|
access: str = ""
|
|
varType: Optional[ClassName] = None
|
|
className: Optional[ClassName] = None
|
|
varName: str = ""
|
|
|
|
def __init__(self, signature: str):
|
|
# Initialize defaults.
|
|
object.__setattr__(self, "access", "")
|
|
object.__setattr__(self, "varType", None)
|
|
object.__setattr__(self, "className", None)
|
|
object.__setattr__(self, "varName", "")
|
|
|
|
signature = signature.strip()
|
|
|
|
# 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()
|
|
break
|
|
|
|
# For class variables, we expect no parameters (i.e. no parentheses).
|
|
if signature.find('(') == -1 and signature.rfind(')') == -1:
|
|
# Use a backward search to find the last space outside templates.
|
|
last_space = Utils.FindLastSpaceOutsideTemplates(signature)
|
|
if last_space != -1:
|
|
varType = signature[:last_space].strip()
|
|
classAndVarName = signature[last_space+1:].strip()
|
|
else:
|
|
# If no space, assume there's no varType
|
|
varType = ""
|
|
classAndVarName = signature
|
|
|
|
# Find the last "::" separator outside templates.
|
|
last_sep = Utils.FindLastClassSeparatorOutsideTemplates(classAndVarName)
|
|
if last_sep != -1:
|
|
class_name_str = classAndVarName[:last_sep].strip()
|
|
var_name = classAndVarName[last_sep+2:].strip()
|
|
else:
|
|
# Fallback: if there are "::" tokens, split them; otherwise, take entire string as varName.
|
|
parts = classAndVarName.split("::")
|
|
if len(parts) > 1:
|
|
class_name_str = "::".join(parts[:-1]).strip()
|
|
var_name = parts[-1].strip()
|
|
else:
|
|
class_name_str = ""
|
|
var_name = classAndVarName.strip()
|
|
|
|
object.__setattr__(self, "access", access if access else "public")
|
|
object.__setattr__(self, "varType", ClassName(varType) if varType else None)
|
|
object.__setattr__(self, "className", ClassName(class_name_str) if class_name_str else None)
|
|
object.__setattr__(self, "varName", var_name)
|
|
return |