Files
EGameTools/_IDAScripts/ExportClassH/ProjectManager.py
2025-03-17 02:09:24 +02:00

210 lines
8.2 KiB
Python

import os
import re
from ExportClassH import ClassGen, Config, HeaderGen
from ExportClassH.ClassDefs import ClassName
def FindClassDefInFile(className: str, filePath: str) -> tuple[bool, str, int, int, str]:
"""
Search for a class definition in a header file.
Returns:
- bool: Whether the class was found
- str: The class type (class, struct, union, etc.)
- int: Start line of the class definition
- int: End line of the class definition (or -1 if not found)
"""
try:
with open(filePath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
# Regular expression to match class definitions with potential attributes like EGameSDK_API
classPattern = re.compile(r'^\s*(class|struct|union|enum)\s+(?:[A-Za-z0-9_]+\s+)*' + re.escape(className) + r'\s*(?::|{)')
for i, line in enumerate(lines):
match = classPattern.search(line)
if match:
# Found the class definition
classType = match.group(1)
# Find the end of the class definition (closing brace)
braceCount = 0
foundOpenBrace = False
indent = ""
for j in range(i, len(lines)):
if '{' in lines[j]:
foundOpenBrace = True
braceCount += lines[j].count('{')
# If this is the first open brace, determine the indentation for the class content
if braceCount == 1:
# Look at the next non-empty line to determine indentation
if lines[j].strip():
# Extract indentation
indentMatch = re.match(r'^(\s+)', lines[j])
if indentMatch:
indent = indentMatch.group(1)
break
if '}' in lines[j]:
braceCount -= lines[j].count('}')
if foundOpenBrace and braceCount == 0:
return True, classType, i, j, indent
# If we couldn't find the end, just return the start
return True, classType, i, -1, indent
return False, "", -1, -1, ""
except Exception as e:
print(f"Error reading file '{filePath}': {e}")
return False, "", -1, -1, ""
def FindGeneratedRegionInFile(filePath: str) -> tuple[int, int, str]:
"""
Search for an existing generated region in a header file.
Returns:
- int: Start line of the generated region
- int: End line of the generated region
"""
try:
with open(filePath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
startPattern = r'^\s*#pragma\s+region\s+GENERATED\s+by\s+ExportClassToCPPH\.py'
endPattern = r'^\s*#pragma\s+endregion'
startLine = -1
indent = ""
for i, line in enumerate(lines):
if re.search(startPattern, line):
startLine = i
# Extract indentation
indentMatch = re.match(r'^(\s+)', line)
if indentMatch:
indent = indentMatch.group(1)
break
if startLine != -1:
for i in range(startLine + 1, len(lines)):
if re.search(endPattern, lines[i]):
return startLine, i, indent
return -1, -1, ""
except Exception as e:
print(f"Error reading file '{filePath}': {e}")
return -1, -1, ""
def FindExistingHeaderFiles(basePath: str) -> dict[str, str]:
"""
Find all header files in the project directory.
Returns a dictionary mapping class names to file paths.
"""
classToFile = {}
for root, _, files in os.walk(basePath):
for file in files:
if file.endswith(".h") or file.endswith(".hpp"):
filePath = os.path.join(root, file)
# Extract the base name without extension
className = os.path.splitext(file)[0]
# If the file name matches a potential class name, add it to our dictionary
classToFile[className] = filePath
return classToFile
def UpdateExistingHeaderFile(targetClass: ClassName, filePath: str, generatedCode: str) -> bool:
"""
Update an existing header file with the generated code.
If a generated region already exists, replace it.
Otherwise, insert the generated code at the start of the class definition.
"""
try:
with open(filePath, 'r', encoding='utf-8', errors='ignore') as f:
lines = f.readlines()
# First, check if there's an existing generated region
startRegion, endRegion, indent = FindGeneratedRegionInFile(filePath)
if startRegion != -1 and endRegion != -1:
# Replace existing region
updatedLines = lines[:startRegion] + [generatedCode + ('\n' if endRegion + 1 < len(lines) and lines[endRegion + 1].strip() == "};" else '\n\n')] + lines[endRegion+1:]
with open(filePath, 'w', encoding='utf-8') as f:
f.writelines(updatedLines)
print(f"Updated existing generated region in '{filePath}'")
return True
# If no existing region, find the class definition
found, classType, classStart, _, indent = FindClassDefInFile(targetClass.name, filePath)
if found:
# Find the line after the opening brace
braceLine = -1
for i in range(classStart, len(lines)):
if '{' in lines[i]:
braceLine = i
break
if braceLine != -1:
# Insert generated code after the opening brace
insertPos = braceLine + 1
updatedLines = lines[:insertPos] + [generatedCode + '\n'] + lines[insertPos:]
with open(filePath, 'w', encoding='utf-8') as f:
f.writelines(updatedLines)
print(f"Inserted generated code in '{filePath}'")
return True
print(f"Could not find a suitable position to insert code in '{filePath}'")
return False
except Exception as e:
print(f"Error updating file '{filePath}': {e}")
return False
def ProcessExistingHeaders():
"""
Scan PROJECT_PATH for header files, find matching class definitions,
and update them with generated code.
"""
print(f"Scanning {Config.PROJECT_INCLUDES_PATH} for header files...")
classFiles = FindExistingHeaderFiles(Config.PROJECT_INCLUDES_PATH)
print(f"Found {len(classFiles)} header files.")
processedCount = 0
for className, filePath in classFiles.items():
# Create a ClassName object
targetClass = ClassName(className)
# Check if this class has any functions or variables to export
allParsedClassVarsAndFuncs = ClassGen.GetAllParsedClassVarsAndFuncs(targetClass)
hasContent = (
len(allParsedClassVarsAndFuncs[0]) > 0 or
len(allParsedClassVarsAndFuncs[1]) > 0 or
len(allParsedClassVarsAndFuncs[2]) > 0
)
if hasContent:
# Verify the class exists in the file
found, class_type, _, _, indent = FindClassDefInFile(className, filePath)
if found:
print(f"Found {class_type} {className} in {filePath}")
# Generate and insert the code
generatedContent = f"{HeaderGen.GenerateClassContent(targetClass, allParsedClassVarsAndFuncs, indent)}"
if generatedContent:
success = UpdateExistingHeaderFile(targetClass, filePath, generatedContent)
if success:
processedCount += 1
print(f"Successfully processed {processedCount} header files.")