Пример #1
0
    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
Пример #2
0
    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()
Пример #3
0
    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))
Пример #4
0
    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)
Пример #5
0
    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]
        ]
Пример #6
0
    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)
Пример #7
0
    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)