def __init__(self, demo_path, dump=None, ent="ALL"): self.demo_finished = False self._buf = Bytebuffer(open(demo_path, "rb").read()) self._pbuf_dict = dict() for item in pbuf.NET_Messages.items(): self._pbuf_dict.update({item[1]: item[0]}) for item in pbuf.SVC_Messages.items(): self._pbuf_dict.update({item[1]: item[0]}) self._subscribers = dict() self.dump = None self._ut_set = set() self._ut_set.add("userinfo") self._ent = ent.upper() if self._ent != "NONE": self.subscribe_to_event("packet_svc_PacketEntities", self._mypkt_svc_PacketEntities) self._ut_set.add("instancebaseline") self._ent_set = set() if self._ent != "ALL": if self._ent == "STATS": self._ent_set.add("CCSPlayerResource") else: self._ent_set.add("CCSPlayer") self._ent_set.add("CCSTeam") self._ent_set.add("CCSPlayerResource") self._ent_set.add("CCSGameRulesProxy") self._ent_set.add("CBaseCSGrenadeProjectile") if dump: self.dump = open(dump, "w", encoding="utf-8") self._counter = [[], [], []] # self.subscribe_to_event("gevent_player_death", self._my_player_death) # self.subscribe_to_event("gevent_begin_new_match", self._my_begin_new_match) # self.subscribe_to_event("gevent_round_end", self._my_round_end) # self.subscribe_to_event("gevent_round_officially_ended", self._my_round_officially_ended) self.subscribe_to_event("packet_svc_CreateStringTable", self._mypkt_svc_CreateStringTable) self.subscribe_to_event("packet_svc_UpdateStringTable", self._mypkt_svc_UpdateStringTable) self.subscribe_to_event("packet_svc_GameEvent", self._mypkt_svc_GameEvent) self.subscribe_to_event("packet_svc_GameEventList", self._mypkt_svc_GameEventList) self.header = None self._game_events_dict = dict() self._serv_class_list = list() self._pending_baselines_dict = dict() self._baselines_dict = dict() self._data_tables_dict = dict() self._string_tables_list = list() self._class_bits = None self._entities = dict() self._players_by_uid = dict() self.progress = 0 self._match_started = False self._round_current = 1 self.opr = False self.counttt = 0
def __init__(self, data): buf = Bytebuffer(data) self.command = struct.unpack("<B", buf.read(1))[0] self.tick = struct.unpack("<I", buf.read(4))[0] self.player = struct.unpack("<B", buf.read(1))[0] del buf
def __init__(self, data): buf = Bytebuffer(data) self.header = struct.unpack("8s", buf.read(8))[0].strip(b"\0").decode() self.demo_protocol = struct.unpack("<I", buf.read(4))[0] self.network_protocol = struct.unpack("<I", buf.read(4))[0] self.server_name = struct.unpack("260s", buf.read( c.MAX_PATH))[0].strip(b"\0").decode() self.client_name = struct.unpack("260s", buf.read( c.MAX_PATH))[0].strip(b"\0").decode() self.map_name = struct.unpack("260s", buf.read( c.MAX_PATH))[0].strip(b"\0").decode() self.game_directory = struct.unpack("260s", buf.read( c.MAX_PATH))[0].strip(b"\0").decode() self.playback_time = struct.unpack("<f", buf.read(4))[0] self.ticks = struct.unpack("<I", buf.read(4))[0] self.frames = struct.unpack("<I", buf.read(4))[0] self.signon_length = struct.unpack("<I", buf.read(4))[0] del buf
def __init__(self, data): buf = Bytebuffer(data) self.version = struct.unpack(">Q", buf.read(8))[0] self.xuid = struct.unpack(">Q", buf.read(8))[0] self.name = struct.unpack("128s", buf.read( c.MAX_PLAYER_NAME_LENGTH))[0].strip(b"\0").decode( errors="replace").strip("\n") self.user_id = struct.unpack(">I", buf.read(4))[0] self.guid = struct.unpack("33s", buf.read( c.SIGNED_GUID_LEN))[0].strip(b"\0").decode(errors="replace") self.friends_id = struct.unpack(">I", buf.read(4))[0] self.friends_name = struct.unpack( "128s", buf.read(c.MAX_PLAYER_NAME_LENGTH))[0].strip(b"\0").decode( errors="replace").strip("\n") self.fake_player = struct.unpack(">B", buf.read(1))[0] self.is_hltv = struct.unpack(">B", buf.read(1))[0] self.custom_files = struct.unpack(">IIII", buf.read(c.MAX_CUSTOM_FILES * 4)) self.files_downloaded = struct.unpack(">B", buf.read(1))[0] self.entity_id = struct.unpack(">I", buf.read(4))[0] self.tbd = struct.unpack(">I", buf.read(4))[0] del buf
class DemoParser: def __init__(self, demo_path, dump=None, ent="ALL"): self._buf = Bytebuffer(open(demo_path, "rb").read()) self._pbuf_dict = dict() for item in pbuf.NET_Messages.items(): self._pbuf_dict.update({item[1]: item[0]}) for item in pbuf.SVC_Messages.items(): self._pbuf_dict.update({item[1]: item[0]}) self._subscribers = dict() self.dump = None self._ut_set = set() self._ut_set.add("userinfo") self._ent = ent.upper() if self._ent != "NONE": self._ut_set.add("instancebaseline") self.subscribe_to_event("packet_svc_PacketEntities", self._mypkt_svc_PacketEntities) if self._ent != "ALL": self._ent_set = set() self._ent_set.add("CCSPlayer") self._ent_set.add("CCSTeam") self._ent_set.add("CCSPlayerResource") if self._ent_set == "P+G": self._ent_set.add("CBaseCSGrenadeProjectile") if dump: self.dump = open(dump, "w", encoding="utf-8") self._counter = [[], [], []] self.subscribe_to_event("gevent_begin_new_match", self._my_begin_new_match) self.subscribe_to_event("gevent_round_end", self._my_round_end) self.subscribe_to_event("gevent_round_officially_ended", self._my_round_officially_ended) self.subscribe_to_event("packet_svc_CreateStringTable", self._mypkt_svc_CreateStringTable) self.subscribe_to_event("packet_svc_UpdateStringTable", self._mypkt_svc_UpdateStringTable) self.subscribe_to_event("packet_svc_GameEvent", self._mypkt_svc_GameEvent) self.subscribe_to_event("packet_svc_GameEventList", self._mypkt_svc_GameEventList) self.header = None self._game_events_dict = dict() self._serv_class_dict = dict() self._pending_baselines_dict = dict() self._baselines_dict = dict() self._data_tables_dict = dict() self._string_tables_list = list() self._class_bits = None self._entities = dict() self._players_userinfo = dict() self.progress = 0 self._match_started = False self._round_current = 1 def subscribe_to_event(self, event: str, func: object): fncs = self._subscribers.get(event) if fncs: fncs.append(func) else: fncs = list() fncs.append(func) self._subscribers.update({event: fncs}) def unsubscribe_from_event(self, event: str, func=None): if func: fncs = self._subscribers.get(event) if len(fncs) == 1: self._subscribers.pop(event) return for i2 in range(len(fncs)): if fncs[i2] == func: fncs.pop(i2) break self._subscribers.update({event: fncs}) else: self._subscribers.pop(event) def _sub_event(self, event: str, data): ret = list() fncs = self._subscribers.get(event) if fncs: for func in fncs: rett = func(data) if rett: ret.append(rett) return ret def parse(self): self.header = DemoHeader(self._buf.read(1072)) assert self.header.header == "HL2DEMO" self._sub_event("parser_start", self.header) demo_finished = False while not demo_finished: command_header = CommandHeader(self._buf.read(6)) tick = command_header.tick self.progress = round(tick / self.header.ticks * 100, 2) cmd = command_header.command # self.dump.write("cmd= {}\n".format(cmd)) if self.dump: self._update_cmd_counter(cmd, cmd=True) if cmd in (c.DEM_SIGNON, c.DEM_PACKET): # 1 and 2 self._handle_packet() elif cmd in (c.DEM_SYNCTICK, c.DEM_CUSTOMDATA): # 3 and 8 pass elif cmd == c.DEM_CONSOLECMD: # 4 pass # self.read_raw_data(None, 0) elif cmd == c.DEM_USERCMD: # 5 pass # self.handle_usercmd(None, 0) elif cmd == c.DEM_DATATABLES: # 6 self._handle_datatables() elif cmd == c.DEM_STRINGTABLES: # 9 self._handle_stringtables() elif cmd == c.DEM_STOP: # 7 self._sub_event("cmd_dem_stop", None) # print(self.progress, "% >DEMO ENDED<") demo_finished = True else: demo_finished = True print("Demo command not recognised: ", cmd) self._demo_ended_stuff() extra_data = dict() extra_data.update({"file": self.dump}) self._sub_event("parser_demo_finished_print", self.dump) self._sub_event("parser_demo_finished", None) if self.dump: self.dump.close() return def _handle_packet(self): self._buf.read(152 + 4 + 4) length = self._buf.read_int() index = 0 while index < length: msg = self._buf.read_varint() size = self._buf.read_varint() data = self._buf.read(size) # self.dump.write("...msg= {} / size= {}\n".format(msg, size)) index += self._buf.varint_size(msg) + self._buf.varint_size(size) + size if self.dump: self._update_cmd_counter(msg, msg=True) self._sub_event("packet_" + self._pbuf_dict[msg], data) def _handle_datatables(self): length = self._buf.read_int() while True: v_type = self._buf.read_varint() size = self._buf.read_varint() data = self._buf.read(size) table = pbuf.CSVCMsg_SendTable() table.ParseFromString(data) if table.is_end: break self._data_tables_dict.update({table.net_table_name: table}) # print(table.net_table_name) sv_classes = self._buf.read_short() self._class_bits = int(math.ceil(math.log2(sv_classes))) for i in range(sv_classes): my_id = self._buf.read_short() assert my_id == i name = self._buf.read_string() dt = self._buf.read_string() if self._ent != "NONE": sv_cls = ServerClass(my_id, name, dt) sv_cls.fprops = self._flatten_dt(self._data_tables_dict[dt]) self._serv_class_dict.update({my_id: sv_cls}) baseline = self._pending_baselines_dict.get(my_id) if baseline: self._baselines_dict.update({my_id: self._get_baseline(baseline, my_id)}) self._pending_baselines_dict.pop(my_id) def _handle_stringtables(self): length = self._buf.read_int() data = self._buf.read(length) # PACKET MESSAGES > def _mypkt_svc_CreateStringTable(self, data): msg = pbuf.CSVCMsg_CreateStringTable() msg.ParseFromString(data) msg2 = StringTable(msg) uinfo = True if msg.name == "userinfo" else False self._update_string_table(msg.string_data, msg2, uinfo, msg.num_entries, msg.max_entries, msg.user_data_size_bits, msg.user_data_fixed_size) self._string_tables_list.append(msg2) def _update_string_table(self, data, res, uinfo, num_entries, max_entries, user_data_size, user_data_fixsize): _buf = Bitbuffer(data) history = [] # ret = [] entry = None entry_bits = int(math.log2(max_entries)) assert not _buf.read_bit() index = 0 last_index = -1 for i in range(num_entries): index = last_index + 1 if not _buf.read_bit(): index = _buf.read_uint_bits(entry_bits) last_index = index assert 0 <= index <= max_entries if _buf.read_bit(): if _buf.read_bit(): idx = _buf.read_uint_bits(5) assert 0 <= idx <= 32 btc = _buf.read_uint_bits(c.SUBSTRING_BITS) substring = history[idx][:btc] suffix = _buf.read_string() entry = substring + suffix else: entry = _buf.read_string() res.data[index]["entry"] = entry user_data = None if _buf.read_bit(): if user_data_fixsize: user_data = _buf.readBits(user_data_size) else: size = _buf.read_uint_bits(c.MAX_USERDATA_BITS) user_data = _buf.readBits(size * 8) if uinfo: user_data = UserInfo(user_data) self._update_pinfo(user_data) res.data[index]["user_data"] = user_data if len(history) == 32: history.pop(0) history.append(entry) if res.name == "instancebaseline" and user_data: cls_id = int(entry) if not self._serv_class_dict.get(cls_id): self._pending_baselines_dict.update({cls_id: user_data}) else: self._baselines_dict.update({cls_id: self._get_baseline(user_data, cls_id)}) def _mypkt_svc_UpdateStringTable(self, data): msg = pbuf.CSVCMsg_UpdateStringTable() msg.ParseFromString(data) obj = self._string_tables_list[msg.table_id] uinfo = True if obj.name == "userinfo" else False if obj.name in self._ut_set: self._update_string_table(msg.string_data, obj, uinfo, msg.num_changed_entries, obj.max_entries, obj.uds, obj.udfs) def _mypkt_svc_GameEvent(self, data): msg = pbuf.CSVCMsg_GameEvent() msg.ParseFromString(data) if self.dump: self._update_cmd_counter(msg.eventid, ev=True) args = {} for i in range(len(msg.keys)): key_name = self._game_events_dict[msg.eventid].keys[i].name typed = msg.keys[i].type if typed == 1: key_val = msg.keys[i].val_string elif typed == 2: key_val = msg.keys[i].val_float elif typed == 3: key_val = msg.keys[i].val_long elif typed == 4: key_val = msg.keys[i].val_short elif typed == 5: key_val = msg.keys[i].val_byte elif typed == 6: key_val = msg.keys[i].val_bool elif typed == 7: key_val = msg.keys[i].val_uint64 elif typed == 8: key_val = msg.keys[i].val_wstring else: key_val = None print("UNKNOWN >", msg.keys[i]) assert key_val is not None args.update({key_name: key_val}) self._sub_event("gevent_" + self._game_events_dict[msg.eventid].name, args) def _mypkt_svc_PacketEntities(self, data): msg = pbuf.CSVCMsg_PacketEntities() msg.ParseFromString(data) buf = Bitbuffer(msg.entity_data) entity_id = -1 for i2 in range(msg.updated_entries): entity_id += 1 + buf.readUBitVar() assert (0 <= entity_id <= (1 << c.MAX_EDICT_BITS)), "Entity id: {} < out of range".format(entity_id) if buf.read_bit(): self._entities.update({entity_id: None}) buf.read_bit() elif buf.read_bit(): cls_id = buf.read_uint_bits(self._class_bits) serial = buf.read_uint_bits(c.NUM_NETWORKED_EHANDLE_SERIAL_NUMBER_BITS) if self._ent != "ALL": if self._serv_class_dict[cls_id].name in self._ent_set: new_entity = Entity(self, entity_id, cls_id, serial) else: new_entity = Entity(self, entity_id, cls_id, serial, parse=False) else: new_entity = Entity(self, entity_id, cls_id, serial) self._entities.update({entity_id: new_entity}) self._read_new_entity(buf, new_entity) else: entity = self._entities[entity_id] self._read_new_entity(buf, entity) def _read_new_entity(self, buf, entity): sv_cls = self._serv_class_dict[entity.class_id] updates = self._handle_entity_update(buf, sv_cls, buffer=True) if entity.parse is False: return for update in updates: table_name = update["prop"]["table"].net_table_name var_name = update["prop"]["prop"].var_name entity.update(table_name, var_name, update["value"]) def _mypkt_svc_GameEventList(self, data): msg = pbuf.CSVCMsg_GameEventList() msg.ParseFromString(data) for event in msg.descriptors: self._game_events_dict.update({event.eventid: event}) # NO MORE PACKET MESSAGES < # EVENTS HANDLERS > def _my_begin_new_match(self, data): # pass self._match_started = True # print("MATCH STARTED.....................................................................") def _my_round_end(self, data): pass # if self._match_started: # print(" {} / {}".format(data["reason"], data["message"])) def _my_round_officially_ended(self, data): # pass if self._match_started: self._round_current += 1 # print("ROUND {}..........................................................".format(self._round_current)) # if self._round_current == 7: # p.print_entities(self.dump, self._entities) # NO MORE EVENTS HANDLERS < def _handle_entity_update(self, data, sv_cls, buffer=False): val = -1 new_props = list() indices = list() buf = Bitbuffer(data) if not buffer else data new_way = buf.read_bit() while True: val = buf.read_index(val, new_way) if val == -1: break indices.append(val) for i2 in indices: prop = sv_cls.fprops[i2] val2 = buf.decode(prop) new_props.append({ "prop": prop, "value": val2 }) return new_props def _get_baseline(self, data, id2): baseline = dict() sv_cls = self._serv_class_dict[id2] for item in self._handle_entity_update(data, sv_cls): table_name = item["prop"]["table"].net_table_name var_name = item["prop"]["prop"].var_name if not baseline.get(table_name): baseline.update({table_name: {}}) baseline[table_name].update({var_name: item["value"]}) return baseline def _flatten_dt(self, table): fprops = self._get_props(table, self._get_excl_props(table)) prio = set(p2["prop"].priority for p2 in fprops) prio.add(64) prio = sorted(list(prio)) start = 0 for pr in prio: while True: cur_prop = start while cur_prop < len(fprops): prop = fprops[cur_prop]["prop"] if prop.priority == pr or (pr == 64 and (prop.flags & c.SPROP_CHANGES_OFTEN)): if start != cur_prop: temp = fprops[start] fprops[start] = fprops[cur_prop] fprops[cur_prop] = temp start += 1 break cur_prop += 1 if cur_prop == len(fprops): break return fprops def _get_props(self, table, excl): flat = list() for id2, prop in enumerate(table.props): if (prop.flags & c.SPROP_INSIDEARRAY or prop.flags & c.SPROP_EXCLUDE or self._is_prop_excl(excl, table, prop)): continue if prop.type == c.PT_DataTable: sub_table = self._data_tables_dict[prop.dt_name] child_props = self._get_props(sub_table, excl) if (prop.flags & c.SPROP_COLLAPSIBLE) == 0: for cp in child_props: cp["col"] = False flat.extend(child_props) elif prop.type == c.PT_Array: flat.append({ "prop": prop, "arr": table.props[id2 - 1], "table": table }) else: flat.append({ "prop": prop, "table": table }) return sorted(flat, key=self._key_sort) def _key_sort(self, item): if item.get("col", True) is False: return 0 return 1 def _is_prop_excl(self, excl, table, prop): for item in excl: if table.net_table_name == item.dt_name and prop.var_name == item.var_name: return True return False def _get_excl_props(self, table): excl = list() for id2, prop in enumerate(table.props): if prop.flags & c.SPROP_EXCLUDE: excl.append(prop) if prop.type == c.PT_DataTable: sub_table = self._data_tables_dict[prop.dt_name] excl.extend(self._get_excl_props(sub_table)) return excl def _update_pinfo(self, data): self._sub_event("parser_update_pinfo", data) if data.guid != "BOT": exist = None for x in self._players_userinfo.items(): if data.xuid == x[1].xuid: exist = x[0] break if exist: self._players_userinfo.update({exist: data}) if exist != data.user_id: self._players_userinfo.update({data.user_id: self._players_userinfo[exist]}) self._players_userinfo.pop(exist) self._sub_event("parser_old_player_connected", data) else: self._sub_event("parser_new_player_connected", data) self._players_userinfo.update({data.user_id: data}) # self._max_players = len(self._players_userinfo) def _update_cmd_counter(self, value, cmd=False, msg=False, ev=False): if cmd is True: for item in self._counter[0]: if item[0] == value: item[1] += 1 return self._counter[0].append([value, 1]) return elif msg is True: for item in self._counter[1]: if item[0] == value: item[1] += 1 return self._counter[1].append([value, 1]) return elif ev is True: for item in self._counter[2]: if item[0] == value: item[1] += 1 return self._counter[2].append([value, 1]) return def _demo_ended_stuff(self): if self.dump: p.print_header(self.dump, self.header) p.print_event_list(self.dump, self._game_events_dict) p.print_counter(self.dump, self._counter) # p.print_userinfo(self.dump, self._string_tables_list) p.print_players_userinfo(self.dump, self._players_userinfo)