def __init__(self, file: BinaryIO): self.events: Dict[int, List[int]] = {} # Remember the current file position startingPosition = file.tell() # Get file size file.seek(0, os.SEEK_END) size = file.tell() # Take note of the position of each event and its timestamp events = defaultdict(list) currentMessagePosition = 0 file.seek(0) # Register PDUs as they are parsed by the layer def registerEvent(pdu: PlayerPDU): events[pdu.timestamp].append(currentMessagePosition) # The layer will take care of parsing for us player = PlayerLayer() player.createObserver(onPDUReceived = registerEvent) # Parse all events in the file while file.tell() < size: data = file.read(8) player.recv(data) data = file.read(player.getDataLengthRequired()) player.recv(data) currentMessagePosition = file.tell() # Restore original file position file.seek(startingPosition) # Use relative timestamps to simplify things if len(events) == 0: self.duration = 0 else: timestamps = sorted(events.keys()) referenceTime = timestamps[0] for absoluteTimestamp in timestamps: relativeTimestamp = absoluteTimestamp - referenceTime self.events[relativeTimestamp] = events[absoluteTimestamp] self.duration = (timestamps[-1] - referenceTime) / 1000.0
class ReplayReader: def __init__(self, replay: Replay): self.replay = replay self.timestamps = self.replay.getSortedTimestamps() self.eventPositions = self.replay.getSortedEvents() self.player = PlayerLayer() self.observer = self.player.createObserver(onPDUReceived=lambda: None) self.n = 0 """ Class used to simplify reading replays. """ def readEvent(self, position: int) -> PlayerPDU: event: Optional[PlayerPDU] = None # When we feed data to self.player, this function will be called and the event variable will be set def onPDUReceived(pdu: PlayerPDU): nonlocal event event = pdu self.observer.onPDUReceived = onPDUReceived with FilePositionGuard(self.replay.file): self.replay.file.seek(position) data = self.replay.file.read(8) self.player.recv(data) data = self.replay.file.read(self.player.getDataLengthRequired()) # Parse event self.player.recv(data) return event def __len__(self): return len(self.replay) def __next__(self): if self.n >= len(self.replay): raise StopIteration timestamp = self.timestamps[self.n] position = self.eventPositions[self.n] event = self.readEvent(position) self.n += 1 return event, timestamp