88from StringIO import StringIO
99from collections import defaultdict
1010
11+ import urllib2
1112from mpyq import MPQArchive
1213
1314from sc2reader import utils
1415from sc2reader import log_utils
16+ from sc2reader import readers , data
1517from sc2reader .objects import Player , Observer , Team , PlayerSummary , Graph
1618from sc2reader .constants import REGIONS , LOCALIZED_RACES , GAME_SPEED_FACTOR , GAME_SPEED_CODES , RACE_CODES , PLAYER_TYPE_CODES , TEAM_COLOR_CODES , GAME_FORMAT_CODES , GAME_TYPE_CODES , DIFFICULTY_CODES
1719
@@ -107,14 +109,10 @@ class Replay(Resource):
107109 #: If there is a valid winning team this will contain a :class:`Team` otherwise it will be :class:`None`
108110 winner = None
109111
110- def __init__ (self , replay_file , filename = None , ** options ):
112+ def __init__ (self , replay_file , filename = None , load_level = 4 , ** options ):
111113 super (Replay , self ).__init__ (replay_file , filename , ** options )
112114 self .datapack = None
113115 self .raw_data = dict ()
114- self .listeners = defaultdict (list )
115-
116- self .__dict__ .update (utils .read_header (replay_file ))
117- self .archive = utils .open_archive (replay_file )
118116
119117 #default values, filled in during file read
120118 self .player_names = list ()
@@ -144,26 +142,52 @@ def __init__(self, replay_file, filename=None, **options):
144142
145143 self .objects = {}
146144
147-
148- def add_listener (self , event_class , listener ):
149- self .listeners [event_class ].append (listener )
150-
151- def read_data (self , data_file , reader ):
152- data = utils .extract_data_file (data_file ,self .archive )
153- if data :
154- data_buffer = utils .ReplayBuffer (data )
155- self .raw_data [data_file ] = reader (data_buffer , self )
156- elif self .opt .debug and data_file != 'replay.message.events' :
157- raise ValueError ("{0} not found in archive" .format (data_file ))
158- else :
159- self .logger .error ("{0} not found in archive" .format (data_file ))
145+ # Bootstrap the readers.
146+ self .registered_readers = defaultdict (list )
147+ self .register_default_readers ()
148+
149+ # Bootstrap the datapacks.
150+ self .registered_datapacks = list ()
151+ self .register_default_datapacks ()
152+
153+ # Unpack the MPQ and read header data if requested
154+ if load_level >= 0 :
155+ self .__dict__ .update (utils .read_header (replay_file ))
156+ self .archive = utils .open_archive (replay_file )
157+
158+ # Load basic details if requested
159+ if load_level >= 1 :
160+ for data_file in ('replay.initData' ,'replay.details' ,'replay.attributes.events' ):
161+ self ._read_data (data_file , self ._get_reader (data_file ))
162+ self .load_details ()
163+ self .datapack = self ._get_datapack ()
164+
165+ # Can only be effective if map data has been loaded
166+ if options .get ('load_map' , False ):
167+ map_url = Map .get_url (self .gateway , self .map_hash )
168+ print map_url
169+ map_file = StringIO (urllib2 .urlopen (map_url ).read ())
170+ replay .map = Map (map_file , filename = self .map , gateway = self .gateway , map_hash = self .map_hash )
171+
172+
173+ # Load players if requested
174+ if load_level >= 2 :
175+ for data_file in ('replay.message.events' ):
176+ self ._read_data (data_file , self ._get_reader (data_file ))
177+ self .load_players ()
178+
179+ # Load events if requested
180+ if load_level >= 3 :
181+ for data_file in ('replay.game.events' ):
182+ self ._read_data (data_file , self ._get_reader (data_file ))
183+ self .load_events ()
160184
161185 def load_details (self ):
162186 if 'replay.initData' in self .raw_data :
163187 initData = self .raw_data ['replay.initData' ]
164188 if initData .map_data :
165189 self .gateway = initData .map_data [0 ].gateway
166- self .map_hash = initData .map_data [- 1 ].map_hash
190+ self .map_hash = initData .map_data [- 1 ].map_hash . encode ( 'hex' )
167191
168192 #Expand this special case mapping
169193 if self .gateway == 'sg' :
@@ -331,9 +355,7 @@ def load_players(self):
331355 hash_input = self .gateway + ":" + ',' .join (player_names )
332356 self .people_hash = hashlib .sha256 (hash_input ).hexdigest ()
333357
334- def load_events (self , datapack = None ):
335- self .datapack = datapack
336-
358+ def load_events (self ):
337359 # Copy the events over
338360 # TODO: the events need to be fixed both on the reader and processor side
339361 if 'replay.game.events' in self .raw_data :
@@ -353,18 +375,88 @@ def load_events(self, datapack=None):
353375 if event .pid != 16 :
354376 self .person [event .pid ].events .append (event )
355377
356-
357- def start (self ):
358- self .stopped = False
359-
360- for listener in self .listeners :
361- listener .setup (self )
362-
363- for event in self .events :
364- for listener in self .listeners :
365- if listener .accepts (event ):
366- listener (event , self )
367-
378+ def register_reader (self , data_file , reader , filterfunc = lambda r : True ):
379+ """
380+ Allows you to specify your own reader for use when reading the data
381+ files packed into the .SC2Replay archives. Datapacks are checked for
382+ use with the supplied filterfunc in reverse registration order to give
383+ user registered datapacks preference over factory default datapacks.
384+
385+ Don't use this unless you know what you are doing.
386+
387+ :param data_file: The full file name that you would like this reader to
388+ parse.
389+
390+ :param reader: The :class:`Reader` object you wish to use to read the
391+ data file.
392+
393+ :param filterfunc: A function that accepts a partially loaded
394+ :class:`Replay` object as an argument and returns true if the
395+ reader should be used on this replay.
396+ """
397+ self .registered_readers [data_file ].insert (0 ,(filterfunc , reader ))
398+
399+ def register_datapack (self , datapack , filterfunc = lambda r : True ):
400+ """
401+ Allows you to specify your own datapacks for use when loading replays.
402+ Datapacks are checked for use with the supplied filterfunc in reverse
403+ registration order to give user registered datapacks preference over
404+ factory default datapacks.
405+
406+ This is how you would add mappings for your favorite custom map.
407+
408+ :param datapack: A :class:`BaseData` object to use for mapping unit
409+ types and ability codes to their corresponding classes.
410+
411+ :param filterfunc: A function that accepts a partially loaded
412+ :class:`Replay` object as an argument and returns true if the
413+ datapack should be used on this replay.
414+ """
415+ self .registered_datapacks .insert (0 ,(filterfunc , datapack ))
416+
417+
418+ # Override points
419+ def register_default_readers (self ):
420+ """Registers factory default readers."""
421+ self .register_reader ('replay.details' , readers .DetailsReader_Base ())
422+ self .register_reader ('replay.initData' , readers .InitDataReader_Base ())
423+ self .register_reader ('replay.message.events' , readers .MessageEventsReader_Base ())
424+ self .register_reader ('replay.attributes.events' , readers .AttributesEventsReader_Base (), lambda r : r .build < 17326 )
425+ self .register_reader ('replay.attributes.events' , readers .AttributesEventsReader_17326 (), lambda r : r .build >= 17326 )
426+ self .register_reader ('replay.game.events' , readers .GameEventsReader_Base (), lambda r : r .build < 16561 )
427+ self .register_reader ('replay.game.events' , readers .GameEventsReader_16561 (), lambda r : 16561 <= r .build < 18574 )
428+ self .register_reader ('replay.game.events' , readers .GameEventsReader_18574 (), lambda r : 18574 <= r .build < 19595 )
429+ self .register_reader ('replay.game.events' , readers .GameEventsReader_19595 (), lambda r : 19595 <= r .build )
430+
431+ def register_default_datapacks (self ):
432+ """Registers factory default datapacks."""
433+ self .register_datapack (data .Data_16561 , lambda r : 16561 <= r .build < 17326 )
434+ self .register_datapack (data .Data_17326 , lambda r : 17326 <= r .build < 18317 )
435+ self .register_datapack (data .Data_18317 , lambda r : 18317 <= r .build < 19595 )
436+ self .register_datapack (data .Data_19595 , lambda r : 19595 <= r .build )
437+
438+
439+ # Internal Methods
440+ def _get_reader (self , data_file ):
441+ for callback , reader in self .registered_readers [data_file ]:
442+ if callback (self ):
443+ return reader
444+
445+ def _get_datapack (self ):
446+ for callback , datapack in self .registered_datapacks :
447+ if callback (self ):
448+ return datapack
449+ return None
450+
451+ def _read_data (self , data_file , reader ):
452+ data = utils .extract_data_file (data_file ,self .archive )
453+ if data :
454+ data_buffer = utils .ReplayBuffer (data )
455+ self .raw_data [data_file ] = reader (data_buffer , self )
456+ elif self .opt .debug and data_file != 'replay.message.events' :
457+ raise ValueError ("{0} not found in archive" .format (data_file ))
458+ else :
459+ self .logger .error ("{0} not found in archive" .format (data_file ))
368460
369461class Map (Resource ):
370462 url_template = 'http://{0}.depot.battle.net:1119/{1}.s2ma'
0 commit comments