def parse_event(self): """ Parse custom Fortnite events """ event_id = self.replay.read_string() group = self.replay.read_string() metadata = self.replay.read_string() start_time = self.replay.read_uint32() end_time = self.replay.read_uint32() size = self.replay.read_uint32() current_pos = self.replay.bytepos logger.info( f'parse_event(), event id => {event_id}, group id => {group}, current offset => {current_pos}' ) if group == EventTypes.PLAYER_ELIMINATION.value: try: self.parse_elimination_event(start_time) except: logger.error("Couldnt parse event PLAYER_ELIMINATION") self.replay.bytepos = current_pos + size if metadata == EventTypes.MATCH_STATS.value: self.parse_matchstats_event() if metadata == EventTypes.TEAM_STATS.value: self.parse_teamstats_event()
def parse_replaydata(self): """ Parse incremental changes to the last checkpoint """ logger.info('parse_replaydata()') start = self.replay.read_uint32() end = self.replay.read_uint32() remaining_offset = self.replay.read_uint32() _ = self.replay.read_uint32() remaining_offset = self.replay.read_uint32()
def parse_header(self, size): """ Parse metadata of the file replay (Fortnite) """ logger.info('parse_header()') logger.debug(self.replay.read(f'bytes:{size}')) self.replay.bytepos -= size self.replay.skip(4) header_version = self.replay.read_uint32() server_side_version = self.replay.read_uint32() season = self.replay.read_uint32() _01 = self.replay.read_uint32() if header_version > HeaderTypes.HEADER_GUID.value: guid = self.replay.read_guid() else: guid = "" _4 = self.replay.read_uint16() some_increasing_number = self.replay.read_uint32() fortnite_version = self.replay.read_uint32() release = self.replay.read_string() if self.replay.read_bool(): game_map = self.replay.read_string() else: game_map = "" _02 = self.replay.read_uint32() _3 = self.replay.read_uint32() if self.replay.read_bool(): game_sub = self.replay.read_string() else: game_sub = "" self.header = Header(header_version=header_version, fortnite_version=fortnite_version, server_side_version=server_side_version, season=season, release=release, game_map=game_map, game_sub=game_sub, guid=guid, unknown0=_01, unknown1=_4, unknown2=_02, unknown3=_3, unknown4=some_increasing_number) logger.debug(self.header)
def parse_meta(self): """ Parse metadata of the file replay (Unreal Engine) """ logger.info('parse_meta()') magic = self.replay.read_uint32() if (magic != FILE_MAGIC): raise InvalidReplayException() file_version = self.replay.read_uint32() lenght_in_ms = self.replay.read_uint32() network_version = self.replay.read_uint32() change_list = self.replay.read_uint32() friendly_name = self.replay.read_string() is_live = self.replay.read_bool() if file_version >= HistoryTypes.HISTORY_RECORDED_TIMESTAMP.value: time_stamp = self.replay.read_uint64() if file_version >= HistoryTypes.HISTORY_COMPRESSION.value: is_compressed = self.replay.read_bool() is_encrypted = False encryption_key = bytearray() if file_version >= HistoryTypes.HISTORY_ENCRYPTION.value: is_encrypted = self.replay.read_bool() encryption_key_size = self.replay.read_uint32() encryption_key = self.replay.read_bytes(encryption_key_size) if (not is_live and is_encrypted and len(encryption_key) == 0): logger.error( "Completed replay is marked encrypted but has no key!") raise InvalidReplayException() if (is_live and is_encrypted): logger.error( "Replay is marked encrypted but not yet marked as completed!") raise InvalidReplayException() self.meta = Meta( file_version=file_version, lenght_in_ms=lenght_in_ms, network_version=network_version, change_list=change_list, friendly_name=friendly_name, is_live=is_live, time_stamp=time_stamp, is_compressed=is_compressed, is_encrypted=is_encrypted, encryption_key=encryption_key, )
def __enter__(self): logger.info(f'__enter__() replay file {self.src}') if isinstance(self.src, str): self._file = open(self.src, 'rb') self._close_on_exit = True elif isinstance(self.src, bytes): self._file = self.src else: raise TypeError() self.replay = ConstBitStreamWrapper(self._file) self.parse_meta() self.parse_chunks() return self
def parse_meta(self): """ Parse metadata of the file replay (Unreal Engine) """ logger.info('parse_meta()') magic = self.replay.read_uint32() if (magic != FILE_MAGIC): raise InvalidReplayException() file_version = self.replay.read_uint32() lenght_in_ms = self.replay.read_uint32() network_version = self.replay.read_uint32() change_list = self.replay.read_uint32() friendly_name = self.replay.read_string() is_live = self.replay.read_uint32() if file_version >= HistoryTypes.HISTORY_RECORDED_TIMESTAMP.value: time_stamp = self.replay.read_uint64() if file_version >= HistoryTypes.HISTORY_COMPRESSION.value: is_compressed = self.replay.read_uint32()
def parse_header(self, size): """ Parse metadata of the file replay (Fortnite) """ logger.info('parse_header()') magic = self.replay.read_uint32() if (magic != NETWORK_MAGIC): raise InvalidReplayException() network_version = self.replay.read_uint32() network_checksum = self.replay.read_uint32() engine_network_version = self.replay.read_uint32() game_network_protocol = self.replay.read_uint32() if network_version > HeaderTypes.HEADER_GUID.value: guid = self.replay.read_guid() else: guid = "" major = self.replay.read_uint16() minor = self.replay.read_uint16() patch = self.replay.read_uint16() changelist = self.replay.read_uint32() branch = self.replay.read_string() levelnames_and_times = self.replay.read_tuple_array( self.replay.read_string, self.replay.read_uint32) flags = self.replay.read_uint32() game_specific_data = self.replay.read_array(self.replay.read_string) self.header = Header( network_version=network_version, network_checksum=network_checksum, engine_network_version=engine_network_version, game_network_protocol=game_network_protocol, guid=guid, major=major, minor=minor, patch=patch, changelist=changelist, branch=branch, levelnames_and_times=levelnames_and_times, flags=flags, game_specific_data=game_specific_data, )
def parse_chunks(self): """ Parse chunks of the file replay """ logger.info('parse_chunks()') while (self.replay.pos < len(self.replay)): chunk_type = self.replay.read_uint32() chunk_size = self.replay.read_int32() offset = self.replay.bytepos if chunk_type == ChunkTypes.CHECKPOINT.value: self.parse_checkpoint() elif chunk_type == ChunkTypes.EVENT.value: self.parse_event() elif chunk_type == ChunkTypes.REPLAYDATA.value: self.parse_replaydata() elif chunk_type == ChunkTypes.HEADER.value: self.parse_header(chunk_size) self.replay.bytepos = offset + chunk_size
def __exit__(self, *args): logger.info(f'__exit__() replay file {self.src}') if self._close_on_exit: self._file.close()
def parse_checkpoint(self): """ Parse snapshot of the game environment """ logger.info('parse_checkpoint()') checkpointId = self.replay.read_string() checkpoint = self.replay.read_string()