''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' 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()