# Helper tool to quickly convert a string representation of either a hexadecimal or decimal # u32 into the corresponding set of collision flags as defined in gtaArchetype.h # # Author: Richard Archibald # Started: 20/1/12 from __future__ import with_statement # Python libraries needed: import re import traceback import functools from Tkinter import * #----------------------------------------------------- # Local function definitions #----------------------------------------------------- def parseLine(line): """ Decides what is on a line in the enum definition. Skips any whitespace or comment lines. """ # Skip any line which doesn't meet expectations (e.g. a comment or whitespace). if re.match('^\ *GTA_.*=\ BIT([0-9]*)', line) and not re.match('^.*//', line): # Split enum def lines into the name and value. tokens = line.split("=") value = tokens[0].strip(",").strip() # Get rid of any remaining white-space too! key = int(tokens[1].split("(")[1].strip().split(")")[0].strip()) return (key, value) def readEnumNames(): """ Populate a dictionary with the data in gtaArchetype.h so that we can match include flag values to the enum value in C++ code. """ enum_dict = {} with open("x:/gta5/src/dev_ng/game/physics/gtaArchetype.h", "r") as enumDefFile: # Read until we hit the enum definition. for line in enumDefFile: line = line.strip() if line.find("namespace ArchetypeFlags")!=-1: break # Skip lines until we hit the actual enum definition. for line in enumDefFile: line = line.strip() if line.find("enum")!=-1: break for line in enumDefFile: line = line.strip() if line.find("{")!=-1: break # Add definitions to the dictionary until we reach the end tag. for line in enumDefFile: line = line.strip() if line.find("};")!=-1: break returnValue = parseLine(line) if returnValue: key = returnValue[0] value = returnValue[1] enum_dict[key] = value return enum_dict #-------------------------------- class exceptionDecorator(object): #-------------------------------- """Used to pop-up a little exception window when bad things happen.""" def __init__(self, fn): self.fn = fn def __get__(self, obj, objtype): """Support instance methods.""" return functools.partial(self.__call__, obj) def __call__(self, *args): try: self.fn(*args) except: root = Tk() root.title("UNHANDLED EXCEPTION:") def close_and_quit(): Tk().quit() root.protocol("WM_DELETE_WINDOW", close_and_quit) # Get screen width and height screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() # Size and position window near task bar. wx = screen_width/2 - 300 wy = screen_height/2 - 100 root.geometry('+%d+%d' % (wx, wy)) e_type = sys.exc_info()[1] e_text = "\"%s\"\n\n%s" % (e_type, traceback.format_exc()) msg = Text(root) msg.insert(INSERT, e_text) msg.pack() closebutton = Button(root, text="Close.", command=close_and_quit) closebutton.pack() root.mainloop() Tk().quit() finally: # Close any files or whatever. pass #--------- class App: #--------- @exceptionDecorator def __init__(self): self.root = Tk() self.root.title("GTA collision flag helper tool") # Get screen width and height screen_width = self.root.winfo_screenwidth() screen_height = self.root.winfo_screenheight() # Size and position window near task bar. window_w = 300 window_h = 490 wx = 100 wy = screen_height - window_h - 100 self.root.geometry('%dx%d+%d+%d' % (window_w, window_h, wx, wy)) # Layout of frames: # ---------------------- frmMain # | |----------------- | # | |frmH1 | | # | |----------------- | # | |----------------- | # | |frmH2 | | # | |----------------- | # | ... | # ---------------------- self.frmMain = Frame(self.root) self.frmMain.pack() # self.frmH1 = Frame(self.frmMain) self.frmH1.pack(side=TOP) # self.frmH2 = Frame(self.frmMain) self.frmH2.pack(side=TOP) # self.frmH3 = Frame(self.frmMain) self.frmH3.pack(side=TOP) # self.frmH4 = Frame(self.frmMain, bd=3, relief=SUNKEN) self.frmH4.pack(side=BOTTOM) # Create the widgets. self.status = Label(self.frmH1, text='Enter collision flags (use 0x for hex):') self.status.pack(side=LEFT) # self.col_flags = Entry(self.frmH1) self.col_flags.bind('', lambda e: self.lookup_flags()) self.col_flags.bind('', lambda e: self.exit_app()) self.col_flags.pack(side=RIGHT) # self.msg = Label(self.frmH2, text='', width=window_w) self.msg.pack(side=LEFT) # self.okButton = Button(self.frmH3, text="Ok.", command=self.lookup_flags, \ default=ACTIVE, state=ACTIVE) self.okButton.pack(side=LEFT) # self.cancelButton = Button(self.frmH3, text="Cancel.", command=self.exit_app) self.cancelButton.pack(side=RIGHT) # The results area. self.results = Label(self.frmH4, text='', width=75) self.results.pack(side=BOTTOM) # Run the lookup function as part of initialisation. self.lookup_flags() @exceptionDecorator def exit_app(self): self.root.destroy() self.root.quit() @exceptionDecorator def lookup_flags(self): """Check the user defined flag value against the enums and display the appropriate flags.""" self.enum_dict = readEnumNames() self.msg.config(text='') user_value = 0 user_flags = self.col_flags.get() if user_flags == '': user_flags = '0' flag_text = '' class OutOfRangeError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) try: if re.match('^0x', user_flags): user_value = long(user_flags, 16) self.msg.config(text='Converting from hex...', fg="darkgreen") else: user_value = long(user_flags, 10) self.msg.config(text='Converting from decimal...', fg="darkgreen") if user_value >= long(1<<32): raise OutOfRangeError(user_value) for k in self.enum_dict: if (long(1< 0: flag_text += "%s\n" % self.enum_dict[k] else: flag_text += "---\n" self.results.config(text=flag_text) except ValueError: # Catch bad user input data and inform them what they are doing wrong. self.msg.config(text=sys.exc_info()[1], fg="red") except OutOfRangeError: # Value user entered was bigger than a u32. self.msg.config(text="ERROR: value entered is greater than max for u32!", fg="red") @exceptionDecorator def run(self): self.root.mainloop() #----------------------------------------------------- # Main #----------------------------------------------------- if __name__ == "__main__": app = App() app.run()