def open(self, filename): ''' Opens the demo file. ''' self.demofile = DemoFile() return self.demofile.open(filename)
class DemoDump(object): ''' Dumps a CSGO demo ''' def __init__(self): ''' Constructor ''' self.NET_MSG = { net_NOP: [], net_Disconnect: [], net_File: [], net_Tick: [], net_StringCmd: [], net_SetConVar: [], net_SignonState: [], svc_ServerInfo: [], svc_SendTable: [], svc_ClassInfo: [], svc_SetPause: [], svc_CreateStringTable: [], svc_UpdateStringTable: [], svc_VoiceInit: [], svc_VoiceData: [], svc_Print: [], svc_Sounds: [], svc_SetView: [], svc_FixAngle: [], svc_CrosshairAngle: [], svc_BSPDecal: [], svc_UserMessage: [], svc_GameEvent: [], svc_PacketEntities: [], svc_TempEntities: [], svc_Prefetch: [], svc_Menu: [], svc_GameEventList: [], svc_GetCvarValue: [] } self.USER_MESSAGES = {} self.GAME_EVENTS = {} self.current_tick = 0 self.descriptors = {} self.register_on_netmsg(svc_GameEvent, self._handle_gameevent) self.register_on_netmsg(svc_GameEventList, self._handle_gameeventlist) self.register_on_netmsg(svc_UserMessage, self._handle_usermessage) self.register_on_netmsg(svc_ServerInfo, self._server_info_update) self.register_on_netmsg(svc_CreateStringTable, self._server_create_stringtable) self.register_on_netmsg(svc_SendTable, self._send_table) self.register_on_netmsg(svc_UpdateStringTable, self._update_stringtable) self.register_on_netmsg(svc_ClassInfo, self._handle_classinfo) self.register_on_netmsg(net_Tick, self._handle_tick) def open(self, filename): ''' Opens the demo file. ''' self.demofile = DemoFile() return self.demofile.open(filename) def register_on_netmsg(self, msg, callback): ''' Will register a callback to call when the given netmessage is received. Will throw an exception if the netmessage doesn't exist. For all netmessages see: netmessages_public.proto SVC_Messages ''' if msg not in self.NET_MSG: raise "Net message not found" self.NET_MSG[msg].append(callback) def register_on_gameevent(self, msg, callback): ''' Will register a callback to call when the given game event is called. Can give ID or name, SHOULD USE NAME, BECAUSE IDS CHANGE (?) Game events can be found in data/game_events.txt ''' if msg not in self.GAME_EVENTS: self.GAME_EVENTS[msg] = [] self.GAME_EVENTS[msg].append(callback) def register_on_usermessage(self, msg, callback): ''' Not yet implemented. ''' if not msg in self.USER_MESSAGES: self.USER_MESSAGES[msg] = [] self.USER_MESSAGES[msg].append(callback) def dump(self): ''' Will start analyzing the demo and executing the necessary game events ''' finished = False while not finished: cmd, tick, playerslot = self.demofile.read_cmd_header() # print "%i - %i - % i " % (cmd, tick, playerslot) if cmd == DemoMessage.SYNCTICK: continue elif cmd == DemoMessage.STOP: finished = True break elif cmd == DemoMessage.CONSOLECMD: self.demofile.read_raw_data() elif cmd == DemoMessage.DATATABLES: self.demofile.read_raw_data() elif cmd == DemoMessage.STRINGTABLES: self.demofile.read_raw_data() elif cmd == DemoMessage.USERCMD: self.demofile.read_user_cmd() elif cmd == DemoMessage.SIGNON or cmd == DemoMessage.PACKET: self._handle_demo_packet() def _handle_usermessage(self, cmd, data): umsg = CSVCMsg_UserMessage() umsg.ParseFromString(data) name = ECstrike15UserMessages.Name(umsg.msg_type) callback_name = name.replace("CS_UM_", "") if callback_name in self.USER_MESSAGES: name = name.replace("CS_UM_", "CCSUsrMsg_") item = eval(name)() item.ParseFromString(umsg.msg_data) for callback in self.USER_MESSAGES[callback_name]: callback(item) def _handle_tick(self, cmd, data): tickinfo = CNETMsg_Tick() tickinfo.ParseFromString(data) self.current_tick = tickinfo.tick def _server_info_update(self, cmd, data): info = CSVCMsg_ServerInfo() info.ParseFromString(data) self.server_info = info def _handle_gameeventlist(self, cmd, data): ''' Parses the list of given game events by the server ''' gameeventlist = CSVCMsg_GameEventList() gameeventlist.ParseFromString(data) for desc in gameeventlist.descriptors: self.descriptors[desc.eventid] = (desc.eventid, desc.name, desc.keys, []) for key in desc.keys: self.descriptors[desc.eventid][3].append([key.type, key.name]) def _handle_gameevent(self, cmd, data): ''' handles the game events and fires the callback ''' gameevent = CSVCMsg_GameEvent() gameevent.ParseFromString(data) name = self.descriptors[gameevent.eventid][1] if gameevent.eventid in self.GAME_EVENTS : event = GameEvent(gameevent, self.descriptors[gameevent.eventid], name) for callback in self.GAME_EVENTS[event.raw.eventid]: callback(event) if name in self.GAME_EVENTS: event = GameEvent(gameevent, self.descriptors[gameevent.eventid], name) for callback in self.GAME_EVENTS[name]: callback(event) stringtable_data = {} def _server_create_stringtable(self, cmd, data): ''' Creates a string table from the given data. TO BE IMPLEMENTED ''' table = CSVCMsg_CreateStringTable() # table_data = CSVCMsg_SendTable() table.ParseFromString(data) if table.name != "userinfo": return # table_data.ParseFromString(table.string_data) self.stringtable_data[table.name] = {'entries': table.num_entries, 'user_data_fixed_size': table.user_data_fixed_size, 'user_data_size': table.user_data_size, 'user_data_size_bits': table.user_data_size_bits, 'flags': table.flags, # 'data': table_data } ''' int *pStringIndex = (int *)fieldInfo.pField; int nLen = pRestore->ReadInt(); char *pTemp = (char *)stackalloc( nLen ); pRestore->ReadString( pTemp, nLen, nLen ); *pStringIndex = m_pStringTable->AddString( CBaseEntity::IsServer(), pTemp ); ''' def _update_stringtable(self, cmd, data): ''' Updates a stringtable with the given data TO BE IMPLEMENTED ''' update = CSVCMsg_UpdateStringTable() update.ParseFromString(data) def _send_table(self, cmd, data): table = CSVCMsg_SendTable() table.ParseFromString(data) print "%s: is end: %r, needs_decoder: %r" % (table.net_table_name, table.is_end, table.needs_decoder) for prop in table.props: print "Name: %s type: %i" % (prop.var_name, prop.type) print "-------------------------------" def _handle_classinfo(self, cmd, data): info = CSVCMsg_ClassInfo() info.ParseFromString(data) #print "CLASS INFO COUNT: %s, CREATE ON CLIENT: %r" % (len(info.classes), info.create_on_client) #for c in info.classes: # print "%i: %s, %s" % (c.class_id, c.data_table_name, c.class_name) def _handle_demo_packet(self): info = self.demofile.read_cmd_info() self.demofile.read_sequence_info() # ignore result length, buf = self.demofile.read_raw_data() if length > 0: self._dump_packet(buf, length) def _dump_packet(self, buf, length): index = 0 while index < length: cmd, index = self.__read_int32(buf, index) size, index = self.__read_int32(buf, index) data = buf[index:index + size] if cmd in self.NET_MSG: for callback in self.NET_MSG[cmd]: callback(cmd, data); index = index + size def __read_int32(self, buf, index): ''' Reads an int32 from a binary buffer. ''' b = 0 count = 0 result = 0 cont = True while cont: if count == 5: return result b = struct.unpack_from("B", buf, index) b = b[0] index = index + 1 result |= (b & 0x7F) << (7 * count) count = count + 1 cont = b & 0x80 return result, index
class DemoDump(object): ''' Dumps a CSGO demo ''' def __init__(self): ''' Constructor ''' self.NET_MSG = { net_NOP: [], net_Disconnect: [], net_File: [], net_Tick: [], net_StringCmd: [], net_SetConVar: [], net_SignonState: [], svc_ServerInfo: [], svc_SendTable: [], svc_ClassInfo: [], svc_SetPause: [], svc_CreateStringTable: [], svc_UpdateStringTable: [], svc_VoiceInit: [], svc_VoiceData: [], svc_Print: [], svc_Sounds: [], svc_SetView: [], svc_FixAngle: [], svc_CrosshairAngle: [], svc_BSPDecal: [], svc_UserMessage: [], svc_GameEvent: [], svc_PacketEntities: [], svc_TempEntities: [], svc_Prefetch: [], svc_Menu: [], svc_GameEventList: [], svc_GetCvarValue: [] } self.USER_MESSAGES = {} self.GAME_EVENTS = {} self.current_tick = 0 self.descriptors = {} self.register_on_netmsg(svc_GameEvent, self._handle_gameevent) self.register_on_netmsg(svc_GameEventList, self._handle_gameeventlist) self.register_on_netmsg(svc_UserMessage, self._handle_usermessage) self.register_on_netmsg(svc_ServerInfo, self._server_info_update) self.register_on_netmsg(svc_CreateStringTable, self._server_create_stringtable) self.register_on_netmsg(svc_SendTable, self._send_table) self.register_on_netmsg(svc_UpdateStringTable, self._update_stringtable) self.register_on_netmsg(svc_ClassInfo, self._handle_classinfo) self.register_on_netmsg(net_Tick, self._handle_tick) def open(self, filename): ''' Opens the demo file. ''' self.demofile = DemoFile() return self.demofile.open(filename) def register_on_netmsg(self, msg, callback): ''' Will register a callback to call when the given netmessage is received. Will throw an exception if the netmessage doesn't exist. For all netmessages see: netmessages_public.proto SVC_Messages ''' if msg not in self.NET_MSG: raise "Net message not found" self.NET_MSG[msg].append(callback) def register_on_gameevent(self, msg, callback): ''' Will register a callback to call when the given game event is called. Can give ID or name, SHOULD USE NAME, BECAUSE IDS CHANGE (?) Game events can be found in data/game_events.txt ''' if msg not in self.GAME_EVENTS: self.GAME_EVENTS[msg] = [] self.GAME_EVENTS[msg].append(callback) def register_on_usermessage(self, msg, callback): ''' Not yet implemented. ''' if not msg in self.USER_MESSAGES: self.USER_MESSAGES[msg] = [] self.USER_MESSAGES[msg].append(callback) def dump(self): ''' Will start analyzing the demo and executing the necessary game events ''' finished = False while not finished: cmd, tick, playerslot = self.demofile.read_cmd_header() # print "%i - %i - % i " % (cmd, tick, playerslot) if cmd == DemoMessage.SYNCTICK: continue elif cmd == DemoMessage.STOP: finished = True break elif cmd == DemoMessage.CONSOLECMD: self.demofile.read_raw_data() elif cmd == DemoMessage.DATATABLES: self.demofile.read_raw_data() elif cmd == DemoMessage.STRINGTABLES: self.demofile.read_raw_data() elif cmd == DemoMessage.USERCMD: self.demofile.read_user_cmd() elif cmd == DemoMessage.SIGNON or cmd == DemoMessage.PACKET: self._handle_demo_packet() def _handle_usermessage(self, cmd, data): umsg = CSVCMsg_UserMessage() umsg.ParseFromString(data) if umsg.msg_type not in ECstrike15UserMessages.DESCRIPTOR.values_by_number: return # TODO: Find out what to do with this name = ECstrike15UserMessages.Name(umsg.msg_type) callback_name = name.replace("CS_UM_", "") if callback_name in self.USER_MESSAGES: name = name.replace("CS_UM_", "CCSUsrMsg_") item = eval(name)() item.ParseFromString(umsg.msg_data) for callback in self.USER_MESSAGES[callback_name]: callback(item) def _handle_tick(self, cmd, data): tickinfo = CNETMsg_Tick() tickinfo.ParseFromString(data) self.current_tick = tickinfo.tick def _server_info_update(self, cmd, data): info = CSVCMsg_ServerInfo() info.ParseFromString(data) self.server_info = info def _handle_gameeventlist(self, cmd, data): ''' Parses the list of given game events by the server ''' gameeventlist = CSVCMsg_GameEventList() gameeventlist.ParseFromString(data) for desc in gameeventlist.descriptors: self.descriptors[desc.eventid] = (desc.eventid, desc.name, desc.keys, []) for key in desc.keys: self.descriptors[desc.eventid][3].append([key.type, key.name]) def _handle_gameevent(self, cmd, data): ''' handles the game events and fires the callback ''' gameevent = CSVCMsg_GameEvent() gameevent.ParseFromString(data) name = self.descriptors[gameevent.eventid][1] if gameevent.eventid in self.GAME_EVENTS : event = GameEvent(gameevent, self.descriptors[gameevent.eventid], name) for callback in self.GAME_EVENTS[event.raw.eventid]: callback(event) if name in self.GAME_EVENTS: event = GameEvent(gameevent, self.descriptors[gameevent.eventid], name) for callback in self.GAME_EVENTS[name]: callback(event) stringtable_data = {} def _server_create_stringtable(self, cmd, data): ''' Creates a string table from the given data. TO BE IMPLEMENTED ''' table = CSVCMsg_CreateStringTable() # table_data = CSVCMsg_SendTable() table.ParseFromString(data) if table.name != "userinfo": return # table_data.ParseFromString(table.string_data) self.stringtable_data[table.name] = {'entries': table.num_entries, 'user_data_fixed_size': table.user_data_fixed_size, 'user_data_size': table.user_data_size, 'user_data_size_bits': table.user_data_size_bits, 'flags': table.flags, # 'data': table_data } ''' int *pStringIndex = (int *)fieldInfo.pField; int nLen = pRestore->ReadInt(); char *pTemp = (char *)stackalloc( nLen ); pRestore->ReadString( pTemp, nLen, nLen ); *pStringIndex = m_pStringTable->AddString( CBaseEntity::IsServer(), pTemp ); ''' def _update_stringtable(self, cmd, data): ''' Updates a stringtable with the given data TO BE IMPLEMENTED ''' update = CSVCMsg_UpdateStringTable() update.ParseFromString(data) def _send_table(self, cmd, data): table = CSVCMsg_SendTable() table.ParseFromString(data) print "%s: is end: %r, needs_decoder: %r" % (table.net_table_name, table.is_end, table.needs_decoder) for prop in table.props: print "Name: %s type: %i" % (prop.var_name, prop.type) print "-------------------------------" def _handle_classinfo(self, cmd, data): info = CSVCMsg_ClassInfo() info.ParseFromString(data) #print "CLASS INFO COUNT: %s, CREATE ON CLIENT: %r" % (len(info.classes), info.create_on_client) #for c in info.classes: # print "%i: %s, %s" % (c.class_id, c.data_table_name, c.class_name) def _handle_demo_packet(self): info = self.demofile.read_cmd_info() self.demofile.read_sequence_info() # ignore result length, buf = self.demofile.read_raw_data() if length > 0: self._dump_packet(buf, length) def _dump_packet(self, buf, length): index = 0 while index < length: cmd, index = self.__read_int32(buf, index) size, index = self.__read_int32(buf, index) data = buf[index:index + size] if cmd in self.NET_MSG: for callback in self.NET_MSG[cmd]: callback(cmd, data); index = index + size def __read_int32(self, buf, index): ''' Reads an int32 from a binary buffer. ''' b = 0 count = 0 result = 0 cont = True while cont: if count == 5: return result b = struct.unpack_from("B", buf, index) b = b[0] index = index + 1 result |= (b & 0x7F) << (7 * count) count = count + 1 cont = b & 0x80 return result, index