383 lines
8.0 KiB
Python
Executable File
383 lines
8.0 KiB
Python
Executable File
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
|
' Finds class member variables that have been compiler-aligned
|
|
' Eric J Anderson
|
|
'
|
|
' cd X:\gta5\tools\script\coding\memory\
|
|
' python find_aligned.py <input>
|
|
'
|
|
' Example: python find_aligned.py input.txt
|
|
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
|
|
|
|
import os
|
|
import os.path
|
|
import sys
|
|
import re
|
|
|
|
# Defines
|
|
PART_NONE = 0
|
|
PART_CLASS = 1
|
|
PART_VARIABLE = 2
|
|
PART_BITPACK = 3
|
|
PART_PADDING = 4
|
|
PART_BORDER = 5
|
|
|
|
PADDING_MIN = 1
|
|
|
|
# Globals
|
|
g_output = ""
|
|
|
|
# Class-member variable
|
|
class CVariable:
|
|
def __init__(self, name, offset):
|
|
self.name = name
|
|
self.offset = offset
|
|
self.padding = 0
|
|
|
|
def __str__(self):
|
|
if self.padding > 0:
|
|
prefix = " ***"
|
|
else:
|
|
prefix = " "
|
|
|
|
data = str(self.offset) + ':'
|
|
value = prefix + "{0:>7}".format(data) + " " + str(self.name)
|
|
|
|
if self.padding > 0:
|
|
value += " [" + str(self.padding) + ']'
|
|
value += '\n'
|
|
return value
|
|
|
|
def SetPadding(self, padding):
|
|
self.padding = padding
|
|
|
|
def GetName(self):
|
|
return self.name
|
|
|
|
def GetOffset(self):
|
|
return self.offset
|
|
|
|
def GetPadding(self):
|
|
return self.padding
|
|
|
|
# Class definition
|
|
class CClass:
|
|
def __init__(self, name, size):
|
|
self.name = name
|
|
self.size = size
|
|
self.variables = []
|
|
|
|
def __str__(self):
|
|
|
|
data = str(self.size) + ':'
|
|
padding = self.GetPadding()
|
|
if padding > 0:
|
|
value = " {0:>7}".format(data) + " " + str(self.name)
|
|
value += " [" + str(padding) + "]\n"
|
|
else:
|
|
value = " {0:>7}".format(data) + " " + str(self.name) + '\n'
|
|
|
|
for variable in self.variables:
|
|
value += str(variable)
|
|
|
|
return value
|
|
|
|
def Add(self, variable):
|
|
self.variables.append(variable)
|
|
|
|
def GetName(self):
|
|
return self.name
|
|
|
|
def GetSize(self):
|
|
return self.size
|
|
|
|
def GetLastVariable():
|
|
if len(self.variables) > 0:
|
|
return variables[len(self.variables) - 1]
|
|
return None
|
|
|
|
def GetPadding(self):
|
|
value = 0
|
|
for variable in self.variables:
|
|
value += variable.padding
|
|
|
|
return value
|
|
|
|
class CSummary:
|
|
def __init__(self, name, size, padding):
|
|
self.name = name
|
|
self.size = size
|
|
self.padding = padding
|
|
self.variables = []
|
|
|
|
def __str__(self):
|
|
return self.GetName() + ',' + str(self.GetSize()) + ',' + str(self.GetPadding())
|
|
|
|
def GetName(self):
|
|
return self.name
|
|
|
|
def GetSize(self):
|
|
return self.size
|
|
|
|
def GetPadding(self):
|
|
return self.padding
|
|
|
|
# Main
|
|
def GetBaseName(path):
|
|
|
|
start = 0
|
|
if path.rfind('/') >= 0:
|
|
start = path.rfind('/') + 1
|
|
if path.rfind('\\') >= 0:
|
|
start = path.rfind('\\') + 1
|
|
|
|
finish = path.rfind('.')
|
|
if finish == -1:
|
|
finish = len(path)
|
|
|
|
return path[start:finish]
|
|
|
|
def GetParams():
|
|
if len(sys.argv) < 2:
|
|
print 'USAGE: python find_aligned.py <input>'
|
|
exit(-1)
|
|
|
|
i = 1
|
|
paths = []
|
|
global g_output
|
|
|
|
while i < len(sys.argv):
|
|
path = sys.argv[i]
|
|
if os.path.isdir(path):
|
|
if g_output != "":
|
|
g_output += '_'
|
|
g_output += GetBaseName(path)
|
|
for subpath in os.listdir(path):
|
|
paths.append(path + '/' + subpath)
|
|
else:
|
|
if g_output != "":
|
|
g_output += '_'
|
|
g_output += GetBaseName(path)
|
|
paths.append(path)
|
|
|
|
i += 1
|
|
|
|
print "Output:", g_output
|
|
|
|
return paths
|
|
|
|
def GetPart(line):
|
|
if "+---" in line:
|
|
return PART_BORDER
|
|
|
|
if "<alignment member>" in line:
|
|
return PART_PADDING
|
|
|
|
if "bitstart=" in line:
|
|
return PART_BITPACK
|
|
|
|
if "| " in line:
|
|
return PART_VARIABLE
|
|
|
|
if line.startswith("class"):
|
|
return PART_CLASS
|
|
|
|
# Try to get the class another way....
|
|
data = re.split(r"[\s|]*", line)
|
|
if (len(data) > 2) and (data[0][len(data[0]) - 1] == '>') and (data[1] == "class"):
|
|
return PART_CLASS
|
|
|
|
return PART_NONE
|
|
|
|
#def mysplit(s, delim=None):
|
|
# return [x for x in s.split(delim) if x]
|
|
|
|
# 1> class attributeAttribute size(8):
|
|
def GetClass(line):
|
|
data = re.split(r"[\s|]*", line)
|
|
if len(data) < 3:
|
|
return None
|
|
|
|
# Name
|
|
name_index = 1
|
|
if data[0].endswith('>'):
|
|
name_index += 1
|
|
name = data[name_index - 1] + ' ' + data[name_index]
|
|
|
|
# Size
|
|
size_index = name_index + 1
|
|
size = int(data[size_index][data[size_index].find('(') + 1 : data[size_index].rfind(')')])
|
|
|
|
return CClass(name, size)
|
|
|
|
# 1> 5 | Inherited
|
|
def GetVariable(line):
|
|
data = re.split(r"[\s|]*", line)
|
|
if len(data) < 2:
|
|
return None
|
|
|
|
# Offset
|
|
offset_index = 0
|
|
if data[0].endswith('>'):
|
|
offset_index += 1
|
|
offset = int(data[offset_index])
|
|
|
|
# Name
|
|
name_index = len(data) - 1
|
|
tab_index = offset_index + 2
|
|
if (name_index >= tab_index and data[name_index - 1] != '|'):
|
|
name = data[name_index - 1][0:len(data[name_index - 1])] + ' ' + data[name_index][0:len(data[name_index])]
|
|
else:
|
|
name = data[name_index][0:len(data[name_index])]
|
|
|
|
return CVariable(name, offset)
|
|
|
|
# 1> 0. | __type_size (bitstart=0,nbits=16)
|
|
def GetBitPack(line):
|
|
data = re.split(r"[\s|]*", line)
|
|
if len(data) < 2:
|
|
return None
|
|
|
|
# Size
|
|
size_index = 0
|
|
if data[0].endswith('>'):
|
|
size_index += 1
|
|
size = int(data[size_index][0:len(data[size_index]) - 1])
|
|
|
|
# Name
|
|
name_index = len(data) - 1
|
|
name = data[name_index - 1][0:len(data[name_index])]
|
|
bitinfo = data[name_index][0:len(data[name_index])]
|
|
|
|
return CVariable(name + ' ' + bitinfo, size)
|
|
|
|
# 1> | <alignment member> (size=2)
|
|
def GetPadding(line):
|
|
return int(line[line.find("=") + 1 : line.rfind(')')])
|
|
|
|
def PaddingCompare(x, y):
|
|
xPadding = x.GetPadding()
|
|
yPadding = y.GetPadding()
|
|
result = cmp(yPadding, xPadding)
|
|
if result == 0:
|
|
result = cmp(x.GetName().lower(), y.GetName().lower())
|
|
return result
|
|
|
|
def NameCompare(x, y):
|
|
return cmp(x.GetName(), y.GetName())
|
|
|
|
def Exists(objects, name):
|
|
for object in objects:
|
|
if name == object.GetName():
|
|
return True
|
|
|
|
return False
|
|
|
|
def Main():
|
|
# Read
|
|
paths = GetParams()
|
|
objects = []
|
|
|
|
for path in paths:
|
|
print "Processing:", path, "..."
|
|
file = open(path, 'r')
|
|
current = None
|
|
variable = None
|
|
|
|
for line in file.readlines():
|
|
line = line.strip()
|
|
part = GetPart(line)
|
|
|
|
if PART_CLASS == part:
|
|
current = GetClass(line)
|
|
variable = None
|
|
continue
|
|
|
|
if current == None:
|
|
continue
|
|
|
|
if PART_BORDER == part:
|
|
continue
|
|
|
|
elif PART_VARIABLE == part:
|
|
if variable != None:
|
|
prev = variable
|
|
padding = prev.GetPadding()
|
|
|
|
variable = GetVariable(line)
|
|
if padding >= (variable.GetOffset() - prev.GetOffset()):
|
|
prev.SetPadding(0)
|
|
|
|
current.Add(prev)
|
|
else:
|
|
variable = GetVariable(line)
|
|
|
|
elif PART_BITPACK == part:
|
|
if variable != None:
|
|
prev = variable
|
|
padding = prev.GetPadding()
|
|
|
|
variable = GetBitPack(line)
|
|
if padding >= (variable.GetOffset() - prev.GetOffset()):
|
|
prev.SetPadding(0)
|
|
|
|
current.Add(prev)
|
|
else:
|
|
variable = GetBitPack(line)
|
|
|
|
elif PART_PADDING == part:
|
|
if variable != None:
|
|
padding = GetPadding(line)
|
|
variable.SetPadding(padding)
|
|
|
|
elif PART_NONE == part:
|
|
if variable != None:
|
|
current.Add(variable)
|
|
variable = None
|
|
|
|
if current != None and current.GetPadding() >= PADDING_MIN:
|
|
if not Exists(objects, current.GetName()):
|
|
objects.append(current)
|
|
current = None
|
|
|
|
current = None
|
|
variable = None
|
|
|
|
file.close()
|
|
|
|
# Sort
|
|
objects.sort(cmp = PaddingCompare)
|
|
|
|
# Total
|
|
padding = 0
|
|
for object in objects:
|
|
padding += object.GetPadding()
|
|
|
|
# Write
|
|
file = open(g_output + ".log", 'w')
|
|
file.write("Total Classes = " + str(len(objects)) + "\n")
|
|
file.write("Total Padding = " + str(padding) + " bytes\n\n")
|
|
for object in objects:
|
|
file.write(str(object))
|
|
file.write('\n')
|
|
file.close()
|
|
|
|
# Summary
|
|
summary = []
|
|
for item in objects:
|
|
copy = CSummary(item.GetName(), item.GetSize(), item.GetPadding())
|
|
summary.append(copy)
|
|
|
|
# Sort
|
|
#summary.sort(cmp = NameCompare)
|
|
|
|
file = open(g_output + ".csv", 'w')
|
|
file.write("Class,Size,Padding\n")
|
|
for item in summary:
|
|
file.write(str(item))
|
|
file.write('\n')
|
|
file.close()
|
|
|
|
# Run
|
|
if __name__ == "__main__":
|
|
Main()
|
|
|