@@ -268,11 +268,19 @@ def __init__(self, replay_file, filename=None, load_level=4, engine=sc2reader.en
268
268
self .length = self .game_length = self .real_length = utils .Length (seconds = int (self .frames / fps ))
269
269
270
270
# Load basic details if requested
271
+ # .backup files are read in case the main files are missing or removed
271
272
if load_level >= 1 :
272
273
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 :
274
282
self ._read_data (data_file , self ._get_reader (data_file ))
275
- self .load_details ()
283
+ self .load_all_details ()
276
284
self .datapack = self ._get_datapack ()
277
285
278
286
# 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
311
319
312
320
engine .run (self )
313
321
314
- def load_details (self ):
322
+ def load_init_data (self ):
315
323
if 'replay.initData' in self .raw_data :
316
324
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
325
329
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 ):
326
340
if 'replay.attributes.events' in self .raw_data :
327
341
# Organize the attribute data to be useful
328
342
self .attributes = defaultdict (dict )
@@ -337,57 +351,75 @@ def load_details(self):
337
351
self .is_ladder = (self .category == "Ladder" )
338
352
self .is_private = (self .category == "Private" )
339
353
354
+ def load_details (self ):
340
355
if 'replay.details' in self .raw_data :
341
356
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'
342
370
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 = ''
344
380
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 )
348
384
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 )
352
393
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
375
398
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 ()
380
403
381
404
def load_map (self ):
382
405
self .map = self .factory .load_map (self .map_file , ** self .opt )
383
406
384
407
def load_players (self ):
385
408
# 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 :
387
415
return
388
416
if 'replay.attributes.events' not in self .raw_data :
389
417
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 :
391
423
return
392
424
393
425
self .clients = list ()
@@ -397,8 +429,6 @@ def load_players(self):
397
429
# information. detail_id marks the current index into this data.
398
430
detail_id = 0
399
431
player_id = 1
400
- details = self .raw_data ['replay.details' ]
401
- initData = self .raw_data ['replay.initData' ]
402
432
403
433
# Assume that the first X map slots starting at 1 are player slots
404
434
# so that we can assign player ids without the map
@@ -568,6 +598,8 @@ def register_default_readers(self):
568
598
"""Registers factory default readers."""
569
599
self .register_reader ('replay.details' , readers .DetailsReader (), lambda r : True )
570
600
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 )
571
603
self .register_reader ('replay.tracker.events' , readers .TrackerEventsReader (), lambda r : True )
572
604
self .register_reader ('replay.message.events' , readers .MessageEventsReader (), lambda r : True )
573
605
self .register_reader ('replay.attributes.events' , readers .AttributesEventsReader (), lambda r : True )
0 commit comments