def load_details(self): if 'replay.attributes.events' in self.raw_data: # Organize the attribute data to be useful self.attributes = defaultdict(dict) attributesEvents = self.raw_data['replay.attributes.events'] for attr in attributesEvents: self.attributes[attr.player][attr.name] = attr.value # Populate replay with attributes self.speed = self.attributes[16]['Game Speed'] self.category = self.attributes[16]['Game Mode'] self.type = self.game_type = self.attributes[16]['Teams'] self.is_ladder = (self.category == "Ladder") self.is_private = (self.category == "Private") if 'replay.details' in self.raw_data: details = self.raw_data['replay.details'] self.map_name = details['map_name'] self.gateway = details['cache_handles'][0].server.lower() self.map_hash = details['cache_handles'][-1].hash self.map_file = details['cache_handles'][-1] #Expand this special case mapping if self.gateway == 'sg': self.gateway = 'sea' dependency_hashes = [d.hash for d in details['cache_handles']] if hashlib.sha256('Standard Data: Swarm.SC2Mod'.encode( 'utf8')).hexdigest() in dependency_hashes: self.expansion = 'HotS' elif hashlib.sha256('Standard Data: Liberty.SC2Mod'.encode( 'utf8')).hexdigest() in dependency_hashes: self.expansion = 'WoL' else: self.expansion = '' self.windows_timestamp = details['file_time'] self.unix_timestamp = utils.windows_to_unix(self.windows_timestamp) self.end_time = datetime.utcfromtimestamp(self.unix_timestamp) # The utc_adjustment is either the adjusted windows timestamp OR # the value required to get the adjusted timestamp. We know the upper # limit for any adjustment number so use that to distinguish between # the two cases. if details['utc_adjustment'] < 10**7 * 60 * 60 * 24: self.time_zone = details['utc_adjustment'] / (10**7 * 60 * 60) else: self.time_zone = (details['utc_adjustment'] - details['file_time']) / (10**7 * 60 * 60) self.game_length = self.length self.real_length = utils.Length( seconds=int(self.length.seconds / GAME_SPEED_FACTOR[self.speed])) self.start_time = datetime.utcfromtimestamp( self.unix_timestamp - self.real_length.seconds) self.date = self.end_time # backwards compatibility
def __init__(self, summary_file, filename=None, **options): super(GameSummary, self).__init__(summary_file, filename, **options) self.team = dict() self.teams = list() self.players = list() self.winners = list() self.player = dict() self.build_orders = dict() self.image_urls = list() self.localization_urls = dict() self.lobby_properties = dict() self.lobby_player_properties = dict() # The first 16 bytes appear to be some sort of compression header buffer = utils.ReplayBuffer(zlib.decompress(summary_file.read()[16:])) # TODO: Is there a fixed number of entries? # TODO: Maybe the # of parts is recorded somewhere? self.parts = list() while buffer.left: self.parts.append(buffer.read_data_struct()) # Parse basic info self.game_speed = GAME_SPEED_CODES[''.join( reversed(self.parts[0][0][1]))] # time struct looks like this: # { 0: 11987, 1: 283385849, 2: 1334719793L} # 0, 1 might be an adjustment of some sort self.unknown_time = self.parts[0][2][2] # this one is alone as a unix timestamp self.time = self.parts[0][8] # in seconds self.game_length = utils.Length(seconds=self.parts[0][7]) self.real_length = utils.Length(seconds=self.parts[0][7] / GAME_SPEED_FACTOR[self.game_speed]) self.load_lobby_properties() self.load_player_info() self.load_player_graphs() self.load_map_data() self.load_player_builds()
def load_game_events(self): # Copy the events over # TODO: the events need to be fixed both on the reader and processor side if 'replay.game.events' not in self.raw_data: return self.game_events = self.raw_data['replay.game.events'] self.events = sorted(self.events+self.game_events, key=lambda e: e.frame) # hideous hack for HotS 2.0.0.23925, see https://github.com/GraylinKim/sc2reader/issues/87 if self.events and self.events[-1].frame > self.frames: self.frames = self.events[-1].frame self.length = utils.Length(seconds=int(self.frames/self.game_fps))
def load_events(self): # Copy the events over # TODO: the events need to be fixed both on the reader and processor side if 'replay.game.events' in self.raw_data: self.events += self.raw_data['replay.game.events'] self.events = sorted(self.events, key=lambda e: e.frame) # hideous hack for HotS 2.0.0.23925, see https://github.com/GraylinKim/sc2reader/issues/87 if self.events[-1].frame > self.frames: self.frames = self.events[-1].frame self.length = utils.Length(seconds=int(self.frames / self.game_fps)) self.camera_events = list() self.selection_events = list() self.ability_events = list() for event in self.events: is_camera = isinstance(event, CameraEvent) is_selection = isinstance(event, SelectionEvent) or isinstance( event, HotkeyEvent) is_ability = isinstance(event, AbilityEvent) if is_camera: self.camera_events.append(event) elif is_selection: self.selection_events.append(event) elif is_ability: self.ability_events.append(event) event.load_context(self) # TODO: Should this be documented or removed? I don't like it. if event.pid != 16: event.player.events.append(event) if is_camera: event.player.camera_events.append(event) elif is_selection: event.player.selection_events.append(event) elif is_ability: event.player.ability_events.append(event)
def __init__(self, summary_file, filename=None, lang='enUS', **options): super(GameSummary, self).__init__(summary_file, filename, lang=lang, **options) #: A dict of team# -> teams self.team = dict() #: A list of teams self.teams = list() #: Players, a dict of :class`PlayerSummary` from the game self.players = list() self.observers = list() #: Game start and end times self.start_time = None self.end_time = None self.winners = list() self.player = dict() self.settings = dict() self.player_stats = dict() self.player_settings = defaultdict(dict) self.build_orders = defaultdict(list) self.image_urls = list() self.localization_urls = dict() self.lobby_properties = dict() self.lobby_player_properties = dict() self.game_type = str() self.real_type = str() # The first 16 bytes appear to be some sort of compression header buffer = BitPackedDecoder(zlib.decompress(summary_file.read()[16:])) # TODO: Is there a fixed number of entries? # TODO: Maybe the # of parts is recorded somewhere? self.parts = list() while not buffer.done(): self.parts.append(buffer.read_struct()) self.end_time = datetime.utcfromtimestamp(self.parts[0][8]) self.game_speed = LOBBY_PROPERTIES[0xBB8][1][self.parts[0][0] [1].decode('utf8')] self.game_length = utils.Length(seconds=self.parts[0][7]) self.real_length = utils.Length( seconds=int(self.parts[0][7] / GAME_SPEED_FACTOR[self.game_speed])) self.start_time = datetime.utcfromtimestamp(self.parts[0][8] - self.real_length.seconds) self.load_translations() self.load_map_info() self.load_settings() self.load_player_stats() self.load_players() dependencies = [sheet[1] for sheet in self.lang_sheets['enUS']] if 'Swarm (Mod)' in dependencies: self.expansion = 'HotS' elif 'Liberty (Mod)' in dependencies: self.expansion = 'WoL' else: self.expansion = '' self.game_type = self.settings['Teams'].replace(" ", "") self.real_type = utils.get_real_type(self.teams) # The s2gs file also keeps reference to a series of s2mv files # Some of these appear to be encoded bytes and others appear to be # the preview images that authors may bundle with their maps. self.s2mv_urls = [ str(utils.DepotFile(file_hash)) for file_hash in self.parts[0][6][7] ]
def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.engine, **options): super(Replay, self).__init__(replay_file, filename, **options) self.datapack = None self.raw_data = dict() # The current load level of the replay self.load_level = None #default values, filled in during file read self.player_names = list() self.other_people = set() self.speed = "" self.type = "" self.game_type = "" self.real_type = "" self.category = "" self.is_ladder = False self.is_private = False self.map = None self.map_hash = "" self.gateway = "" self.events = list() self.events_by_type = defaultdict(list) self.teams, self.team = list(), dict() self.player = utils.PersonDict() self.observer = utils.PersonDict() self.human = utils.PersonDict() self.computer = utils.PersonDict() self.entity = utils.PersonDict() self.players = list() self.observers = list() # Unordered list of Observer self.humans = list() self.computers = list() self.entities = list() self.attributes = defaultdict(dict) self.messages = list() self.recorder = None # Player object self.packets = list() self.objects = {} self.active_units = {} self.game_fps = 16.0 self.tracker_events = list() self.game_events = list() # Bootstrap the readers. self.registered_readers = defaultdict(list) self.register_default_readers() # Bootstrap the datapacks. self.registered_datapacks = list() self.register_default_datapacks() # Unpack the MPQ and read header data if requested # Since the underlying traceback isn't important to most people, don't expose it in python2 anymore if load_level >= 0: self.load_level = 0 try: self.archive = mpyq.MPQArchive(replay_file, listfile=False) except Exception as e: raise exceptions.MPQError("Unable to construct the MPQArchive", e) header_content = self.archive.header['user_data_header']['content'] header_data = BitPackedDecoder(header_content).read_struct() self.versions = list(header_data[1].values()) self.frames = header_data[3] self.build = self.versions[4] self.base_build = self.versions[5] self.release_string = "{0}.{1}.{2}.{3}".format(*self.versions[1:5]) self.game_length = utils.Length(seconds=self.frames / 16) self.length = self.real_length = utils.Length( seconds=int(self.frames / self.game_fps)) # Load basic details if requested if load_level >= 1: self.load_level = 1 for data_file in [ 'replay.initData', 'replay.details', 'replay.attributes.events' ]: self._read_data(data_file, self._get_reader(data_file)) self.load_details() self.datapack = self._get_datapack() # Can only be effective if map data has been loaded if options.get('load_map', False): self.load_map() # Load players if requested if load_level >= 2: self.load_level = 2 for data_file in ['replay.message.events']: self._read_data(data_file, self._get_reader(data_file)) self.load_message_events() self.load_players() # Load tracker events if requested if load_level >= 3: self.load_level = 3 for data_file in ['replay.tracker.events']: self._read_data(data_file, self._get_reader(data_file)) self.load_tracker_events() # Load events if requested if load_level >= 4: self.load_level = 4 for data_file in ['replay.game.events']: self._read_data(data_file, self._get_reader(data_file)) self.load_game_events() # Run this replay through the engine as indicated if engine: engine.run(self)
def __init__(self, replay_file, filename=None, load_level=4, **options): super(Replay, self).__init__(replay_file, filename, **options) self.datapack = None self.raw_data = dict() #default values, filled in during file read self.player_names = list() self.other_people = set() self.speed = "" self.type = "" self.game_type = "" self.real_type = "" self.category = "" self.is_ladder = False self.is_private = False self.map = None self.map_hash = "" self.gateway = "" self.events = list() self.events_by_type = defaultdict(list) self.teams, self.team = list(), dict() self.players, self.player = list(), utils.PersonDict() self.observers = list() #Unordered list of Observer self.people, self.humans = list(), list( ) #Unordered list of Players+Observers self.person = utils.PersonDict() #Maps pid to Player/Observer self.attributes = defaultdict(dict) self.messages = list() self.recorder = None # Player object self.packets = list() self.objects = {} self.active_units = {} self.game_fps = 16.0 # Bootstrap the readers. self.registered_readers = defaultdict(list) self.register_default_readers() # Bootstrap the datapacks. self.registered_datapacks = list() self.register_default_datapacks() # Unpack the MPQ and read header data if requested if load_level >= 0: try: self.archive = mpyq.MPQArchive(replay_file, listfile=False) except Exception as e: trace = sys.exc_info()[2] raise exceptions.MPQError("Unable to construct the MPQArchive", e), None, trace header_content = self.archive.header['user_data_header']['content'] header_data = BitPackedDecoder(header_content).read_struct() self.versions = header_data[1].values() self.frames = header_data[3] self.build = self.versions[4] self.release_string = "{0}.{1}.{2}.{3}".format(*self.versions[1:5]) self.game_length = utils.Length(seconds=self.frames / 16) self.length = self.real_length = utils.Length( seconds=int(self.frames / self.game_fps)) # Load basic details if requested if load_level >= 1: for data_file in [ 'replay.initData', 'replay.details', 'replay.attributes.events' ]: self._read_data(data_file, self._get_reader(data_file)) self.load_details() self.datapack = self._get_datapack() # Can only be effective if map data has been loaded if options.get('load_map', False): self.load_map() # Load players if requested if load_level >= 2: for data_file in ['replay.message.events']: self._read_data(data_file, self._get_reader(data_file)) self.load_messages() self.load_players() # Load events if requested if load_level >= 3: for data_file in ['replay.game.events']: self._read_data(data_file, self._get_reader(data_file)) self.load_events() # Load tracker events if requested if load_level >= 4: for data_file in ['replay.tracker.events']: self._read_data(data_file, self._get_reader(data_file)) self.load_tracker_events() for event in self.events: event.load_context(self)