'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Finds class member variables that have been compiler-aligned
' Eric J Anderson
'
' cd X:\gta5\tools\script\coding\memory\
' python find_aligned.py
'
' 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 '
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 "" 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> | (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()