backup scripts

This commit is contained in:
EricPlayZ
2025-03-19 03:14:20 +02:00
parent 0c5d0c76f9
commit 970f5745aa
6 changed files with 102 additions and 94 deletions

View File

@ -1,4 +1,4 @@
from typing import Optional, List, get_type_hints
from typing import Optional, List, Dict, get_type_hints
from prodict import Prodict
class ParsedParam(Prodict):
@ -18,7 +18,7 @@ class ParsedClass(Prodict):
name: str
templateParams: List["ParsedParam"]
fullClassName: str
childClasses: List["ParsedClass"]
childClasses: Prodict
functions: List["ParsedFunction"]
def init(self):
@ -28,7 +28,7 @@ class ParsedClass(Prodict):
self.name = ""
self.templateParams = []
self.fullClassName = ""
self.childClasses = []
self.childClasses = Prodict()
self.functions = []
class ParsedFunction(Prodict):
@ -38,6 +38,7 @@ class ParsedFunction(Prodict):
returnTypes: List[ParsedParam]
parentNamespaces: List[str]
parentClasses: List[str]
fullClassName: str
funcName: str
params: List[ParsedParam]
const: bool
@ -50,6 +51,7 @@ class ParsedFunction(Prodict):
self.returnTypes = []
self.parentNamespaces = []
self.parentClasses = []
self.fullClassName = ""
self.funcName = ""
self.params = []
self.const = False

View File

@ -15,4 +15,5 @@ DEFAULT_CONFIG = {
"OUTPUT_PATH": OUTPUT_PATH,
"LAST_CLICKED_RADIO": LAST_CLICKED_RADIO,
}
CONFIG_FILE = os.path.join(os.path.join(os.path.dirname(__file__), os.pardir), "ExportClassH.json")
CONFIG_FILE = os.path.join(os.path.join(os.path.dirname(__file__), os.pardir), "ExportClassH.json")
PARSED_CLASSES_OUTPUT_FILE = os.path.join(HEADER_OUTPUT_PATH, "parsed-classes.json")

View File

@ -1,3 +1,4 @@
from functools import cache
import idc
from ExportClassH import Utils
@ -28,7 +29,7 @@ def GetDemangledExportedSigs() -> list[str]:
entry_qty = idc.get_entry_qty()
for i in range(entry_qty):
ea: int = idc.get_entry(i)
exportedSig: str = idc.get_func_name(ea) or idc.get_name(ea)
#exportedSig: str = idc.get_func_name(ea) or idc.get_name(ea)
exportedSig = idc.get_entry_name(i)
if not exportedSig:
continue

View File

@ -1,7 +1,7 @@
import json
from typing import Optional
from ExportClassH import Utils, IDAUtils, RTTIAnalyzer
from ExportClassH import Utils, IDAUtils, RTTIAnalyzer, Config
from ExportClassH.ClassDefs import ParsedClass, ParsedFunction, ParsedParam
CLASS_TYPES = ["namespace", "class", "struct", "enum", "union"]
@ -9,6 +9,7 @@ FUNC_TYPES = ["function", "strippedVirtual", "basicVirtual", "virtual"]
TYPES_OF_RETURN_TYPES = ["returnType", "classReturnType"]
STD_CLASSES = ["std", "rapidjson"]
parsedClassesLookupDict: dict[str, ParsedClass] = {}
parsedClassesDict: dict[str, ParsedClass] = {}
def GetTypeAndNameStr(fullName: str, returnFullName: bool = False) -> str:
@ -121,7 +122,7 @@ def ParseClassStr(clsStr: str) -> Optional[ParsedClass]:
virtualFuncDuplicateCounter: dict[tuple[str, str], int] = {}
virtualFuncPlaceholderCounter: dict[tuple[str, str], int] = {}
def ParseFuncStr(parsedClass: ParsedClass, funcStr: str, onlyVirtualFuncs: bool = False) -> Optional[ParsedFunction]:
def ParseFuncStr(funcStr: str, parsedClass: Optional[ParsedClass] = None, onlyVirtualFuncs: bool = False) -> Optional[ParsedFunction]:
global virtualFuncDuplicateCounter
global virtualFuncPlaceholderCounter
@ -130,7 +131,8 @@ def ParseFuncStr(parsedClass: ParsedClass, funcStr: str, onlyVirtualFuncs: bool
if not funcStr:
return None
parsedFunc = ParsedFunction(fullFuncSig=funcStr.strip())
parsedFunc = ParsedFunction()
parsedFunc.fullFuncSig = funcStr
# Handle special cases
isDuplicateFunc = False
@ -206,6 +208,7 @@ def ParseFuncStr(parsedClass: ParsedClass, funcStr: str, onlyVirtualFuncs: bool
else:
returnType = remainingInputBeforeParamsParen
parsedFunc.fullClassName = namespacesAndClasses
parentNamespacesAndClasses = Utils.SplitByClassSeparatorOutsideTemplates(namespacesAndClasses)
parentNamespaces, parentClasses = ExtractParentNamespacesAndClasses(parentNamespacesAndClasses)
parsedFunc.parentNamespaces = parentNamespaces
@ -213,7 +216,9 @@ def ParseFuncStr(parsedClass: ParsedClass, funcStr: str, onlyVirtualFuncs: bool
# Handle duplicate function naming
if isDuplicateFunc:
key = (parsedClass.fullClassName, funcStr)
if not parsedClass and not namespacesAndClasses:
raise Exception("parsedClass variable not provided and namespacesAndClasses is empty for ParseFuncStr when func is duplicate")
key = (parsedClass.fullClassName if parsedClass else namespacesAndClasses, funcStr)
if key not in virtualFuncDuplicateCounter:
virtualFuncDuplicateCounter[key] = 0
virtualFuncDuplicateCounter[key] += 1
@ -236,10 +241,13 @@ def ParseFuncStr(parsedClass: ParsedClass, funcStr: str, onlyVirtualFuncs: bool
returnType = Utils.ReplaceIDATypes(returnType)
returnType = Utils.CleanType(returnType)
returnTypes = GetParsedParamsFromList(Utils.ExtractTypeTokensFromString(returnType), "param")
returnTypes = GetParsedParamsFromList(Utils.ExtractTypeTokensFromString(returnType), "returnType")
parsedFunc.returnTypes = returnTypes
parsedFunc.funcName = funcName
elif onlyVirtualFuncs and funcStr == "_purecall":
if not parsedClass:
raise Exception("parsedClass variable not provided for ParseFuncStr when func is _purecall")
key = (parsedClass.fullClassName, funcStr)
if key not in virtualFuncPlaceholderCounter:
virtualFuncPlaceholderCounter[key] = 0
@ -315,8 +323,19 @@ def ExtractMainClassSigFromFuncSig(funcSig: str) -> str:
return f"{'class' if namespacesAndClasses.endswith(funcName) else 'namespace'} {namespacesAndClasses}" if namespacesAndClasses else ""
def BuildParsedClassesLookup(rootClasses: list[ParsedClass], lookupDict: dict[str, ParsedClass]):
lookupDict = {}
def build(parsedClasses: list[ParsedClass]):
for parsedClass in parsedClasses:
lookupDict[parsedClass.fullClassName] = parsedClass
if parsedClass.childClasses:
build(list(parsedClass.childClasses.values()))
build(rootClasses)
def ParseAllClasses():
global parsedClassesLookupDict
global parsedClassesDict
parsedClassesLookupDict = {}
parsedClassesDict = {}
# Get and parse all classes that are mentioned in a func sig, such as "class cbs::CPointer" in the params here: 'bool cbs::IsInDynamicRoot(class cbs::CPointer<class cbs::CEntity>, bool)'
@ -402,8 +421,11 @@ def ParseAllClasses():
if not parentClass:
continue
parentClass.childClasses.append(parsedClass)
parentClass.childClasses[parsedClass.fullClassName] = parsedClass
del parsedClassesDict[parsedClass.fullClassName]
# Build the lookup for parsed classes, so we can have faster and more efficient lookup times
BuildParsedClassesLookup(list(parsedClassesDict.values()), parsedClassesLookupDict)
def CreateParamNamesForVTFunc(parsedFunc: ParsedFunction, skipFirstParam: bool) -> str:
paramsList: list[str] = [param.name for param in parsedFunc.params if param.name]
@ -418,38 +440,62 @@ def CreateParamNamesForVTFunc(parsedFunc: ParsedFunction, skipFirstParam: bool)
newParams: str = ", ".join(f"{paramType} {paramName}" for paramType, paramName in zip(paramsList, paramNames))
return newParams
def ParseAllClassVTFuncs():
global parsedClassesDict
for parsedClass in parsedClassesDict.values():
for (demangledFuncSig, rawType) in RTTIAnalyzer.GetDemangledVTableFuncSigs(parsedClass):
if rawType:
parsedFunc = ParseFuncStr(parsedClass, rawType, True)
if not parsedFunc:
continue
def ParseClassVTFuncs(parsedClass: ParsedClass):
# Parse child classes first
for parsedChildClass in parsedClass.childClasses.values():
ParseClassVTFuncs(parsedChildClass)
if parsedFunc.returnTypes:
newParamTypes = CreateParamNamesForVTFunc(parsedFunc, True) if parsedFunc.params else ""
returnTypes = [returnType.name for returnType in parsedFunc.returnTypes if returnType.name]
returnTypesStr = ' '.join(returnTypes)
demangledFuncSig = f"{'DUPLICATE_FUNC ' if demangledFuncSig.startswith('DUPLICATE_FUNC') else ''}IDA_GEN_PARSED virtual {returnTypesStr} {demangledFuncSig.removeprefix('DUPLICATE_FUNC').strip()}({newParamTypes})"
elif demangledFuncSig.startswith("DUPLICATE_FUNC"):
parsedFunc = ParseFuncStr(parsedClass, demangledFuncSig.removeprefix("DUPLICATE_FUNC").strip(), True)
if not parsedFunc:
continue
if parsedFunc.returnTypes:
newParamTypes: str = CreateParamNamesForVTFunc(parsedFunc, False) if parsedFunc.params else ""
returnTypes = [returnType.name for returnType in parsedFunc.returnTypes if returnType.name]
returnTypesStr = ' '.join(returnTypes)
demangledFuncSig = f"DUPLICATE_FUNC {returnTypesStr} {parsedFunc.funcName}({newParamTypes})"
parsedFunc = ParseFuncStr(parsedClass, demangledFuncSig, True)
# Parse root class
for (demangledFuncSig, rawType) in RTTIAnalyzer.GetDemangledVTableFuncSigs(parsedClass):
if rawType:
parsedFunc = ParseFuncStr(rawType, parsedClass, True)
if not parsedFunc:
continue
parsedClass.functions.append(parsedFunc)
if parsedFunc.returnTypes:
newParamTypes = CreateParamNamesForVTFunc(parsedFunc, True) if parsedFunc.params else ""
returnTypes = [returnType.name for returnType in parsedFunc.returnTypes if returnType.name]
returnTypesStr = ' '.join(returnTypes)
demangledFuncSig = f"{'DUPLICATE_FUNC ' if demangledFuncSig.startswith('DUPLICATE_FUNC') else ''}IDA_GEN_PARSED virtual {returnTypesStr} {demangledFuncSig.removeprefix('DUPLICATE_FUNC').strip()}({newParamTypes})"
elif demangledFuncSig.startswith("DUPLICATE_FUNC"):
parsedFunc = ParseFuncStr(demangledFuncSig.removeprefix("DUPLICATE_FUNC").strip(), parsedClass, True)
if not parsedFunc:
continue
if parsedFunc.returnTypes:
newParamTypes: str = CreateParamNamesForVTFunc(parsedFunc, False) if parsedFunc.params else ""
returnTypes = [returnType.name for returnType in parsedFunc.returnTypes if returnType.name]
returnTypesStr = ' '.join(returnTypes)
demangledFuncSig = f"DUPLICATE_FUNC {returnTypesStr} {parsedFunc.funcName}({newParamTypes})"
parsedFunc = ParseFuncStr(demangledFuncSig, parsedClass, True)
if not parsedFunc:
continue
if parsedClass.type == "namespace":
parsedClass.type = "class"
parsedClass.functions.append(parsedFunc)
def ParseAllClassVTFuncs():
for parsedClass in parsedClassesDict.values():
ParseClassVTFuncs(parsedClass)
def ParseAllClassFuncs():
global parsedClassesLookupDict
for demangledExportedSig in IDAUtils.GetDemangledExportedSigs():
parsedFunc = ParseFuncStr(demangledExportedSig, None, False)
if not parsedFunc or not parsedFunc.fullClassName:
continue
parsedClass = parsedClassesLookupDict.get(parsedFunc.fullClassName)
if not parsedClass:
continue
parsedClass.functions.append(parsedFunc)
def GetAllParsedClasses():
ParseAllClasses()
ParseAllClassVTFuncs()
print(json.dumps(parsedClassesDict, indent=4))
ParseAllClassFuncs()
with open(Config.PARSED_CLASSES_OUTPUT_FILE, 'w') as fileStream:
json.dump(parsedClassesDict, fileStream, indent=4)

View File

@ -1,5 +1,5 @@
import re
from functools import lru_cache
from functools import cache
from typing import Tuple
import ida_nalt
import ida_bytes
@ -9,6 +9,7 @@ import idc
IDA_NALT_ENCODING = ida_nalt.get_default_encoding_idx(ida_nalt.BPU_1B)
@cache
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 '&'
@ -20,9 +21,11 @@ def FixTypeSpacing(type: str) -> str:
type = re.sub(r'\s+', ' ', type) # Collapse multiple spaces
return type.strip()
@cache
def CleanDoubleSpaces(str: str) -> str:
return " ".join(str.split())
@cache
def CleanEndOfClassStr(clsStr: str) -> str:
clsStr = clsStr.removesuffix("const")
while clsStr and clsStr[-1] in {')', ',', '&', '*'}:
@ -30,16 +33,18 @@ def CleanEndOfClassStr(clsStr: str) -> str:
clsStr = clsStr.removesuffix("const")
return clsStr
@cache
def CleanType(type: str) -> str:
"""Remove unwanted tokens from a type string, then fix spacing."""
type = re.sub(r'\b(__cdecl|__fastcall|__ptr64)\b', '', type)
return FixTypeSpacing(type)
@cache
def ReplaceIDATypes(type: str) -> str:
"""Replace IDA types with normal ones"""
return type.replace("unsigned __int64", "uint64_t").replace("_QWORD", "uint64_t").replace("__int64", "int64_t").replace("unsigned int", "uint32_t")
@lru_cache(maxsize=None)
@cache
def ExtractTypeTokensFromString(types: str) -> list[str]:
"""Extract potential type names from a string, properly handling template types."""
if not types:
@ -73,7 +78,7 @@ def ExtractTypeTokensFromString(types: str) -> list[str]:
# Filter out empty strings
return [word.strip() for word in result if word]
@lru_cache(maxsize=None)
@cache
def SplitByCommaOutsideTemplates(params: str) -> list[str]:
parts = []
current = []
@ -102,7 +107,7 @@ def SplitByCommaOutsideTemplates(params: str) -> list[str]:
parts.append(''.join(current).strip())
return parts
@lru_cache(maxsize=None)
@cache
def SplitByClassSeparatorOutsideTemplates(params: str) -> list[str]:
parts = []
current = []
@ -131,7 +136,7 @@ def SplitByClassSeparatorOutsideTemplates(params: str) -> list[str]:
parts.append(''.join(current).strip())
return parts
@lru_cache(maxsize=None)
@cache
def FindLastSpaceOutsideTemplates(s: str) -> int:
"""Return the index of the last space in s that is not inside '<' and '>'."""
depth = 0
@ -146,7 +151,7 @@ def FindLastSpaceOutsideTemplates(s: str) -> int:
return i
return -1
@lru_cache(maxsize=None)
@cache
def FindLastClassSeparatorOutsideTemplates(s: str) -> int:
"""Return the index of the last occurrence of "::" in s that is not inside '<' and '>'."""
depth = 0
@ -166,9 +171,11 @@ def FindLastClassSeparatorOutsideTemplates(s: str) -> int:
# IDA util functions
# -----------------------------------------------------------------------------
@cache
def DemangleSig(sig: str) -> str:
return idaapi.demangle_name(sig, idaapi.MNG_LONG_FORM)
@cache
def GetMangledTypePrefix(namespaces: tuple[str, ...], className: str) -> str:
"""
Get the appropriate mangled type prefix for a class name.
@ -188,7 +195,7 @@ def GetMangledTypePrefix(namespaces: tuple[str, ...], className: str) -> str:
# IDA pattern search utilities
# -----------------------------------------------------------------------------
@lru_cache(maxsize=None)
@cache
def BytesToIDAPattern(data: bytes) -> str:
"""Convert bytes to IDA-friendly hex pattern string."""
return " ".join("{:02X}".format(b) for b in data)

View File

@ -1,49 +0,0 @@
#pragma once
#include <EGSDK\Imports.h>
class cbs {
public:
#pragma region GENERATED by ExportClassToCPPH.py
public:
GAME_IMPORT class cbs::PrefabManager* g_PrefabManager;
GAME_IMPORT class ttl::string_base<char> GetSpawnContextDebugInfoString(class SpawnContext const&);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath&&, class cbs::CPcidPath const&);
GAME_IMPORT bool operator!=(class cbs::CEntity const*, class cbs::CEntityPointer);
GAME_IMPORT bool IsAnyAncestorAlwaysSpawned(class cbs::CPointer<class cbs::CEntity>);
GAME_IMPORT bool operator==(class cbs::CPointer<class cbs::CEntity>, class cbs::CEntityPointer);
GAME_IMPORT float GetTime(struct cbs::WorldIndex);
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 class cbs::PrefabManager& GetPrefabManager();
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPathView, class cbs::CPcidPathView);
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 bool IsInDynamicRoot(class cbs::CPointer<class cbs::CEntity>, bool);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPathView, class cbs::CPcidPath const&);
GAME_IMPORT void GetEntityDebugHierarchyPath(class cbs::CEntity const*, class ttl::string_base<char>&);
GAME_IMPORT class IPhysicsManager* GetPhysicsManager(struct cbs::WorldIndex);
GAME_IMPORT class ttl::string_base<char> GetEntityDebugInfoString(class cbs::CEntity 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 cbs::CPointer<class cbs::CEntity> CreateEntityFromPrefab(class ttl::string_const<char>, class ILevel*, class mtx34 const&, struct PresetId, class Replication::CreateObjectOptions, bool);
GAME_IMPORT bool CreateEntityFromPrefabDeferred(class cbs::CPointer<class cbs::CEntity>&, class ttl::string_const<char>, class SpawnContext&);
GAME_IMPORT class cbs::ComponentsPool& GetComponentsPool();
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);
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 const&, class cbs::CPcidPathView);
GAME_IMPORT void GetPCIDHierarchyPath(class ttl::span<class cbs::PrefabEntityComponent const* const, 4294967295>, uint32_t, class ttl::string_base<char>&);
GAME_IMPORT bool operator==(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT bool operator==(class cbs::CEntity const*, class cbs::CEntityPointer);
GAME_IMPORT class cbs::CPcidPath operator+(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT class ILevel* GetILevel(struct cbs::WorldIndex);
GAME_IMPORT class cbs::CPcidPath operator+(enum cbs::pcid_t, class cbs::CPcidPath const&);
GAME_IMPORT bool operator<(class cbs::CPcidPath const&, class cbs::CPcidPath const&);
GAME_IMPORT void GetPECHierarchyPath(class ttl::span<class cbs::PrefabEntityComponent const* const, 4294967295>, uint32_t, class ttl::string_base<char>&);
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&, enum cbs::pcid_t);
GAME_IMPORT enum cbs::EntitySpawnOrigin GetEntitySpawnOrigin(class cbs::PrefabEntityComponent const*);
GAME_IMPORT float GetTimeDelta(struct cbs::WorldIndex);
GAME_IMPORT class CLevel* GetCLevel(struct cbs::WorldIndex);
GAME_IMPORT enum cbs::EntitySpawnOrigin GetEntitySpawnOrigin(class cbs::CEntity const*);
GAME_IMPORT class cbs::EnumPropertyManager& GetEnumPropertyManager();
GAME_IMPORT bool operator!=(class cbs::CPointer<class cbs::CEntity>, class cbs::CEntityPointer);
#pragma endregion
};