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
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 ReplayTab(BaseTab): """ Tab that displays a RDP Connection that is being replayed from a file. """ def __init__(self, fileName: str, parent: QWidget): """ :param fileName: name of the file to read. :param parent: parent widget. """ self.viewer = QRemoteDesktop(800, 600, parent) super().__init__(self.viewer, parent) QApplication.instance().aboutToQuit.connect(self.onClose) self.fileName = fileName self.file = open(self.fileName, "rb") self.eventHandler = PlayerEventHandler(self.widget, self.text) replay = Replay(self.file) self.thread = ReplayThread(replay) self.thread.eventReached.connect(self.readEvent) self.thread.timeUpdated.connect(self.onTimeUpdated) self.thread.clearNeeded.connect(self.clear) self.thread.start() self.controlBar = ReplayBar(replay.duration) self.controlBar.play.connect(self.thread.play) self.controlBar.pause.connect(self.thread.pause) self.controlBar.seek.connect(self.thread.seek) self.controlBar.speedChanged.connect(self.thread.setSpeed) self.controlBar.scaleCheckbox.stateChanged.connect( self.setScaleToWindow) self.controlBar.button.setDefault(True) self.tabLayout.insertWidget(0, self.controlBar) self.player = PlayerLayer() self.player.addObserver(self.eventHandler) def play(self): self.controlBar.button.setPlaying(True) self.controlBar.play.emit() def readEvent(self, position: int): """ Read an event from the file at the given position. :param position: the position of the event in the file. """ self.file.seek(position) data = self.file.read(8) self.player.recv(data) length = self.player.getDataLengthRequired() data = self.file.read(length) self.player.recv(data) def onTimeUpdated(self, currentTime: float): """ Called everytime the thread ticks. :param currentTime: the current time. """ self.controlBar.timeSlider.blockSignals(True) self.controlBar.timeSlider.setValue(int(currentTime * 1000)) self.controlBar.timeSlider.blockSignals(False) def clear(self): """ Clear the UI. """ self.viewer.clear() self.text.setText("") def onClose(self): self.thread.close() self.thread.wait() def setScaleToWindow(self, status: int): """ Called when the scale to window checkbox is checked or unchecked, refresh the scaling calculation. :param status: state of the checkbox """ self.widget.setScaleToWindow(status) self.parentResized(None) def parentResized(self, event: QResizeEvent): """ Called when the main PyRDP window is resized to allow to scale the current RDP session being displayed. :param event: The event of the parent that has been resized """ newScale = self.scrollViewer.viewport().height( ) / self.widget.sessionHeight self.widget.scale(newScale)
class ReplayTab(BaseTab): """ Tab that displays a RDP Connection that is being replayed from a file. """ def __init__(self, fileName: str, parent: QWidget = None): """ :param fileName: name of the file to read. :param parent: parent widget. """ self.viewer = QRemoteDesktop(800, 600, parent) super().__init__(self.viewer, parent) QApplication.instance().aboutToQuit.connect(self.onClose) self.fileName = fileName self.file = open(self.fileName, "rb") self.eventHandler = PlayerEventHandler(self.widget, self.text) replay = Replay(self.file) self.thread = ReplayThread(replay) self.thread.eventReached.connect(self.readEvent) self.thread.timeUpdated.connect(self.onTimeUpdated) self.thread.clearNeeded.connect(self.clear) self.thread.start() self.controlBar = ReplayBar(replay.duration) self.controlBar.play.connect(self.thread.play) self.controlBar.pause.connect(self.thread.pause) self.controlBar.seek.connect(self.thread.seek) self.controlBar.speedChanged.connect(self.thread.setSpeed) self.controlBar.button.setDefault(True) self.tabLayout.insertWidget(0, self.controlBar) self.player = PlayerLayer() self.player.addObserver(self.eventHandler) def readEvent(self, position: int): """ Read an event from the file at the given position. :param position: the position of the event in the file. """ self.file.seek(position) data = self.file.read(8) self.player.recv(data) length = self.player.getDataLengthRequired() data = self.file.read(length) self.player.recv(data) def onTimeUpdated(self, currentTime: float): """ Called everytime the thread ticks. :param currentTime: the current time. """ self.controlBar.timeSlider.blockSignals(True) self.controlBar.timeSlider.setValue(int(currentTime * 1000)) self.controlBar.timeSlider.blockSignals(False) def clear(self): """ Clear the UI. """ self.viewer.clear() self.text.setText("") def onClose(self): self.thread.close() self.thread.wait()