@@ -268,11 +268,19 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en
268268 self .length = self .game_length = self .real_length = utils .Length (seconds = int (self .frames / fps ))
269269
270270 # Load basic details if requested
271+ # .backup files are read in case the main files are missing or removed
271272 if load_level >= 1 :
272273 self .load_level = 1
273- for data_file in ['replay.initData' , 'replay.details' , 'replay.attributes.events' ]:
274+ files = [
275+ 'replay.initData.backup' ,
276+ 'replay.details.backup' ,
277+ 'replay.attributes.events' ,
278+ 'replay.initData' ,
279+ 'replay.details'
280+ ]
281+ for data_file in files :
274282 self ._read_data (data_file , self ._get_reader (data_file ))
275- self .load_details ()
283+ self .load_all_details ()
276284 self .datapack = self ._get_datapack ()
277285
278286 # Can only be effective if map data has been loaded
@@ -311,18 +319,24 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en
311319
312320 engine .run (self )
313321
314- def load_details (self ):
322+ def load_init_data (self ):
315323 if 'replay.initData' in self .raw_data :
316324 initData = self .raw_data ['replay.initData' ]
317- options = initData ['game_description' ]['game_options' ]
318- self .amm = options ['amm' ]
319- self .ranked = options ['ranked' ]
320- self .competitive = options ['competitive' ]
321- self .practice = options ['practice' ]
322- self .cooperative = options ['cooperative' ]
323- self .battle_net = options ['battle_net' ]
324- self .hero_duplicates_allowed = options ['hero_duplicates_allowed' ]
325+ elif 'replay.initData.backup' in self .raw_data :
326+ initData = self .raw_data ['replay.initData.backup' ]
327+ else :
328+ return
325329
330+ options = initData ['game_description' ]['game_options' ]
331+ self .amm = options ['amm' ]
332+ self .ranked = options ['ranked' ]
333+ self .competitive = options ['competitive' ]
334+ self .practice = options ['practice' ]
335+ self .cooperative = options ['cooperative' ]
336+ self .battle_net = options ['battle_net' ]
337+ self .hero_duplicates_allowed = options ['hero_duplicates_allowed' ]
338+
339+ def load_attribute_events (self ):
326340 if 'replay.attributes.events' in self .raw_data :
327341 # Organize the attribute data to be useful
328342 self .attributes = defaultdict (dict )
@@ -337,57 +351,75 @@ def load_details(self):
337351 self .is_ladder = (self .category == "Ladder" )
338352 self .is_private = (self .category == "Private" )
339353
354+ def load_details (self ):
340355 if 'replay.details' in self .raw_data :
341356 details = self .raw_data ['replay.details' ]
357+ elif 'replay.details.backup' in self .raw_data :
358+ details = self .raw_data ['replay.details.backup' ]
359+ else :
360+ return
361+
362+ self .map_name = details ['map_name' ]
363+ self .region = details ['cache_handles' ][0 ].server .lower ()
364+ self .map_hash = details ['cache_handles' ][- 1 ].hash
365+ self .map_file = details ['cache_handles' ][- 1 ]
366+
367+ # Expand this special case mapping
368+ if self .region == 'sg' :
369+ self .region = 'sea'
342370
343- self .map_name = details ['map_name' ]
371+ dependency_hashes = [d .hash for d in details ['cache_handles' ]]
372+ if hashlib .sha256 ('Standard Data: Void.SC2Mod' .encode ('utf8' )).hexdigest () in dependency_hashes :
373+ self .expansion = 'LotV'
374+ elif hashlib .sha256 ('Standard Data: Swarm.SC2Mod' .encode ('utf8' )).hexdigest () in dependency_hashes :
375+ self .expansion = 'HotS'
376+ elif hashlib .sha256 ('Standard Data: Liberty.SC2Mod' .encode ('utf8' )).hexdigest () in dependency_hashes :
377+ self .expansion = 'WoL'
378+ else :
379+ self .expansion = ''
344380
345- self .region = details ['cache_handles' ][ 0 ]. server . lower ()
346- self .map_hash = details [ 'cache_handles' ][ - 1 ]. hash
347- self .map_file = details [ 'cache_handles' ][ - 1 ]
381+ self .windows_timestamp = details ['file_time' ]
382+ self .unix_timestamp = utils . windows_to_unix ( self . windows_timestamp )
383+ self .end_time = datetime . utcfromtimestamp ( self . unix_timestamp )
348384
349- # Expand this special case mapping
350- if self .region == 'sg' :
351- self .region = 'sea'
385+ # The utc_adjustment is either the adjusted windows timestamp OR
386+ # the value required to get the adjusted timestamp. We know the upper
387+ # limit for any adjustment number so use that to distinguish between
388+ # the two cases.
389+ if details ['utc_adjustment' ] < 10 ** 7 * 60 * 60 * 24 :
390+ self .time_zone = details ['utc_adjustment' ]/ (10 ** 7 * 60 * 60 )
391+ else :
392+ self .time_zone = (details ['utc_adjustment' ]- details ['file_time' ])/ (10 ** 7 * 60 * 60 )
352393
353- dependency_hashes = [d .hash for d in details ['cache_handles' ]]
354- if hashlib .sha256 ('Standard Data: Void.SC2Mod' .encode ('utf8' )).hexdigest () in dependency_hashes :
355- self .expansion = 'LotV'
356- elif hashlib .sha256 ('Standard Data: Swarm.SC2Mod' .encode ('utf8' )).hexdigest () in dependency_hashes :
357- self .expansion = 'HotS'
358- elif hashlib .sha256 ('Standard Data: Liberty.SC2Mod' .encode ('utf8' )).hexdigest () in dependency_hashes :
359- self .expansion = 'WoL'
360- else :
361- self .expansion = ''
362-
363- self .windows_timestamp = details ['file_time' ]
364- self .unix_timestamp = utils .windows_to_unix (self .windows_timestamp )
365- self .end_time = datetime .utcfromtimestamp (self .unix_timestamp )
366-
367- # The utc_adjustment is either the adjusted windows timestamp OR
368- # the value required to get the adjusted timestamp. We know the upper
369- # limit for any adjustment number so use that to distinguish between
370- # the two cases.
371- if details ['utc_adjustment' ] < 10 ** 7 * 60 * 60 * 24 :
372- self .time_zone = details ['utc_adjustment' ]/ (10 ** 7 * 60 * 60 )
373- else :
374- self .time_zone = (details ['utc_adjustment' ]- details ['file_time' ])/ (10 ** 7 * 60 * 60 )
394+ self .game_length = self .length
395+ self .real_length = utils .Length (seconds = int (self .length .seconds / GAME_SPEED_FACTOR [self .expansion ][self .speed ]))
396+ self .start_time = datetime .utcfromtimestamp (self .unix_timestamp - self .real_length .seconds )
397+ self .date = self .end_time # backwards compatibility
375398
376- self . game_length = self . length
377- self .real_length = utils . Length ( seconds = int ( self . length . seconds / GAME_SPEED_FACTOR [ self . expansion ][ self . speed ]) )
378- self .start_time = datetime . utcfromtimestamp ( self . unix_timestamp - self . real_length . seconds )
379- self .date = self . end_time # backwards compatibility
399+ def load_all_details ( self ):
400+ self .load_init_data ( )
401+ self .load_attribute_events ( )
402+ self .load_details ()
380403
381404 def load_map (self ):
382405 self .map = self .factory .load_map (self .map_file , ** self .opt )
383406
384407 def load_players (self ):
385408 # If we don't at least have details and attributes_events we can go no further
386- if 'replay.details' not in self .raw_data :
409+ # We can use the backup detail files if the main files have been removed
410+ if 'replay.details' in self .raw_data :
411+ details = self .raw_data ['replay.details' ]
412+ elif 'replay.details.backup' in self .raw_data :
413+ details = self .raw_data ['replay.details.backup' ]
414+ else :
387415 return
388416 if 'replay.attributes.events' not in self .raw_data :
389417 return
390- if 'replay.initData' not in self .raw_data :
418+ if 'replay.initData' in self .raw_data :
419+ initData = self .raw_data ['replay.initData' ]
420+ elif 'replay.initData.backup' in self .raw_data :
421+ initData = self .raw_data ['replay.initData.backup' ]
422+ else :
391423 return
392424
393425 self .clients = list ()
@@ -397,8 +429,6 @@ def load_players(self):
397429 # information. detail_id marks the current index into this data.
398430 detail_id = 0
399431 player_id = 1
400- details = self .raw_data ['replay.details' ]
401- initData = self .raw_data ['replay.initData' ]
402432
403433 # Assume that the first X map slots starting at 1 are player slots
404434 # so that we can assign player ids without the map
@@ -568,6 +598,8 @@ def register_default_readers(self):
568598 """Registers factory default readers."""
569599 self .register_reader ('replay.details' , readers .DetailsReader (), lambda r : True )
570600 self .register_reader ('replay.initData' , readers .InitDataReader (), lambda r : True )
601+ self .register_reader ('replay.details.backup' , readers .DetailsReader (), lambda r : True )
602+ self .register_reader ('replay.initData.backup' , readers .InitDataReader (), lambda r : True )
571603 self .register_reader ('replay.tracker.events' , readers .TrackerEventsReader (), lambda r : True )
572604 self .register_reader ('replay.message.events' , readers .MessageEventsReader (), lambda r : True )
573605 self .register_reader ('replay.attributes.events' , readers .AttributesEventsReader (), lambda r : True )
0 commit comments