Exemple #1
0
 def format_status(self, text):
     return text.format(self.isWorking(),
                        self.isWorking() and not self.isAFKing(),
                        utils.convert_millis(self.timeRecorded()),
                        utils.convert_millis(self.timePassed()),
                        self.packet_counter,
                        utils.convert_file_size_MB(len(self.file_buffer)),
                        utils.convert_file_size_MB(self.replay_file.size()),
                        self.file_name)
Exemple #2
0
 def delete_marker(self, index):
     marker = self.replay_file.markers.pop(index - 1)
     self.chat(
         self.translation('OnMarkerDeleted').format(
             utils.convert_millis(marker['realTimestamp'])))
     self.logger.log(
         'Marker deleted: {}, {} markers has been stored'.format(
             marker, len(self.replay_file.markers)))
Exemple #3
0
 def add_marker(self, name=None):
     if self.pos is None:
         self.logger.warn('Fail to add marker, position unknown!')
         return
     time_stamp = self.timeRecorded()
     marker = self.replay_file.add_marker(self.timeRecorded(), self.pos,
                                          name)
     self.chat(
         self.translation('OnMarkerAdded').format(
             utils.convert_millis(time_stamp)))
     self.logger.log('Marker added: {}, {} markers has been stored'.format(
         marker, len(self.replay_file.markers)))
Exemple #4
0
 def print_markers(self):
     if len(self.replay_file.markers) == 0:
         self.chat(self.translation('MarkerNotFound'))
     else:
         self.chat(self.translation('CommandMarkerListTitle'))
         for i in range(len(self.replay_file.markers)):
             name = self.replay_file.markers[i]['value'][
                 'name'] if 'name' in self.replay_file.markers[i][
                     'value'] else ''
             self.chat('{}. {} {}'.format(
                 i + 1,
                 utils.convert_millis(
                     self.replay_file.markers[i]['realTimestamp']), name))
Exemple #5
0
    def __createReplayFile(self, logger):
        self.flush()

        if self.mc_version is None or self.mc_protocol is None:
            logger.log(
                'Not connected to the server yet, abort creating replay recording file'
            )
            return

        if self.replay_file.size() < utils.MinimumLegalFileSize:
            logger.warn(
                'Size of "recording.tmcpr" too small ({}KB < {}KB), abort creating replay file'
                .format(utils.convert_file_size_KB(self.replay_file.size()),
                        utils.convert_file_size_KB(
                            utils.MinimumLegalFileSize)))
            return

        # Creating .mcpr zipfile based on timestamp
        logger.log('Time recorded/passed: {}/{}'.format(
            utils.convert_millis(self.timeRecorded()),
            utils.convert_millis(self.timePassed())))

        # Deciding file name
        if not os.path.exists(utils.RecordingStorageFolder):
            os.makedirs(utils.RecordingStorageFolder)
        file_name_raw = datetime.datetime.today().strftime(
            'PCRC_%Y_%m_%d_%H_%M_%S')
        if self.file_name is not None:
            file_name_raw = self.file_name
        file_name = file_name_raw + '.mcpr'
        counter = 2
        while os.path.isfile(f'{utils.RecordingStorageFolder}{file_name}'):
            file_name = f'{file_name_raw}_{counter}.mcpr'
            counter += 1
        logger.log('File name is set to "{}"'.format(file_name))

        logger.log('Creating "{}"'.format(file_name))
        if self.isOnline():
            self.chat(self.translation('OnCreatingMCPRFile'))

        self.replay_file.meta_data = utils.get_meta_data(
            server_name=self.config.get('server_name'),
            duration=self.timeRecorded(),
            date=utils.getMilliTime(),
            mcversion=self.mc_version,
            protocol=self.mc_protocol,
            player_uuids=self.player_uuids)
        self.replay_file.create(file_name)

        logger.log('Size of replay file "{}": {}MB'.format(
            file_name, utils.convert_file_size_MB(os.path.getsize(file_name))))
        file_path = f'{utils.RecordingStorageFolder}{file_name}'
        shutil.move(file_name, file_path)
        if self.isOnline():
            self.chat(self.translation('OnCreatedMCPRFile').format(file_name),
                      priority=ChatThread.Priority.High)

        if self.config.get('upload_file'):
            if self.isOnline():
                self.chat(self.translation('OnUploadingMCPRFile'))
            logger.log('Uploading "{}" to transfer.sh'.format(file_name))
            try:
                ret, out = subprocess.getstatusoutput(
                    'curl --upload-file {} https://transfer.sh/{}'.format(
                        file_path, file_name))
                url = out.splitlines()[-1]
                self.file_urls.append(url)
                if self.isOnline():
                    self.chat(self.translation('OnUploadedMCPRFile').format(
                        file_name, url),
                              priority=ChatThread.Priority.High)
            except Exception:
                logger.error(
                    'Fail to upload "{}" to transfer.sh'.format(file_name))
                logger.error(traceback.format_exc())
Exemple #6
0
    def processPacketData(self, packet_raw):
        if not self.isWorking():
            return
        bytes = packet_raw.data
        if bytes[0] == 0x00:
            bytes = bytes[1:]
        t = utils.getMilliTime()
        packet_length = len(bytes)

        packet = SARCPacket()
        packet.receive(bytes)
        packet_id, packet_name = self.packet_processor.analyze(packet)
        packet_recorded = self.packet_processor.process(packet)

        # Increase afk timer when recording stopped, afk timer prevents afk time in replays
        if self.config.get('with_player_only'):
            noPlayerMovement = self.noPlayerMovement(t)
            if noPlayerMovement:
                self.afk_time += t - self.last_t
            if self.last_no_player_movement != noPlayerMovement:
                msg = self.translation('RecordingPause') if self.isAFKing(
                ) else self.translation('RecordingContinue')
                self.chat(msg)
            self.last_no_player_movement = noPlayerMovement
        self.last_t = t

        # Recording
        if self.isWorking() and packet_recorded is not None:
            if not self.isAFKing(
            ) or packet_name in utils.IMPORTANT_PACKETS or self.config.get(
                    'record_packets_when_afk'):
                bytes_recorded = packet_recorded.read(
                    packet_recorded.remaining())
                data = self.timeRecorded().to_bytes(4,
                                                    byteorder='big',
                                                    signed=True)
                data += len(bytes_recorded).to_bytes(4,
                                                     byteorder='big',
                                                     signed=True)
                data += bytes_recorded
                self.write(data)
                self.packet_counter += 1
                if self.isAFKing() and packet_name in utils.IMPORTANT_PACKETS:
                    self.logger.debug(
                        'PCRC is afking but {} is an important packet so PCRC recorded it'
                        .format(packet_name))
                else:
                    self.logger.debug('{} packet recorded'.format(packet_name))
            else:
                self.logger.debug(
                    '{} packet ignore due to being afk'.format(packet_name))
        else:
            self.logger.debug('{} packet ignore'.format(packet_name))
            pass

        if self.isWorking(
        ) and self.replay_file.size() > self.file_size_limit():
            self.logger.log(
                'tmcpr file size limit {}MB reached! Restarting'.format(
                    utils.convert_file_size_MB(self.file_size_limit())))
            self.chat(
                self.translation('OnReachFileSizeLimit').format(
                    utils.convert_file_size_MB(self.file_size_limit())))
            self.restart()

        if self.isWorking(
        ) and self.timeRecorded(t) > self.time_recorded_limit():
            self.logger.log('{} actual recording time reached!'.format(
                utils.convert_millis(self.time_recorded_limit())))
            self.chat(
                self.translation('OnReachTimeLimit').format(
                    utils.convert_millis(self.time_recorded_limit())))
            self.restart()

        def get_showinfo_time():
            return int(self.timePassed(t) / (5 * 60 * 1000))

        # Log information in console
        if get_showinfo_time(
        ) != self.last_showinfo_time or self.packet_counter - self.last_showinfo_packetcounter >= 100000:
            self.last_showinfo_time = get_showinfo_time()
            self.last_showinfo_packetcounter = self.packet_counter
            self.logger.log('Recorded/Passed: {}/{}; Packet count: {}'.format(
                utils.convert_millis(self.timeRecorded(t)),
                utils.convert_millis(self.timePassed(t)), self.packet_counter))
Exemple #7
0
def run(config, email, password, debug, address):
    access_token, uuid, user_name = utils.get_token(email, password)
    clientbound, serverbound, protocol_version, mc_version = utils.generate_protocol_table(address)
    connection = utils.login(address, protocol_version, debug, access_token, uuid, user_name)
    print(clientbound)
    start_time = int(time.time() * 1000)
    last_player_movement = start_time
    entity_packets = ['Entity', 'Entity Relative Move', 'Entity Look And Relative Move', 'Entity Look',
                      'Entity Teleport']
    player_uuids = []
    player_ids = []
    blocked_entity_ids = []
    opped_players = []
    write_buffer = bytearray()
    file_size = 0
    afk_time = 0
    last_t = 0
    open('recording.tmcpr', 'w').close()  # Cleans recording file
    time.sleep(0.5)
    utils.request_ops(connection, serverbound, protocol_version)  # Request op list once
    if 'Time Update' in utils.BAD_PACKETS:
        utils.BAD_PACKETS.remove('Time Update')

    # Main processing loop for incoming data.
    while True:
        ready_to_read = select.select([connection.socket], [], [], 0)[0]
        if ready_to_read:
            t = int(time.time() * 1000)
            try:
                packet_in = connection.receive_packet()
            except IOError:
                break
            packet_recorded = int(t - start_time - afk_time).to_bytes(4, byteorder='big', signed=True)
            packet_recorded += len(packet_in.received).to_bytes(4, byteorder='big', signed=True)
            packet_recorded += packet_in.received
            packet_id = packet_in.read_varint()
            packet_name = clientbound[str(packet_id)]
            if debug:
                print('P Packet ' + hex(packet_id) + ': ' + packet_name)

            # Answer keep aLive
            if packet_name == 'Keep Alive (clientbound)':
                packet_out = Packet()
                packet_out.write_varint(serverbound['Keep Alive (serverbound)'])
                if protocol_version > 338:  # For some unnecessary reason keepalive changes to long after 1.12.1
                    id = packet_in.read_long()
                    packet_out.write_long(id)
                else:
                    id = packet_in.read_varint()
                    packet_out.write_varint(id)
                connection.send_packet(packet_out)

            # Respawn when dead
            if packet_name == 'Update Health':
                health = packet_in.read_float()
                food = packet_in.read_varint()
                food_sat = packet_in.read_float()
                if health == 0.0:
                    packet_out = Packet()
                    packet_out.write_varint(serverbound['Client Status'])
                    packet_out.write_varint(0)
                    connection.send_packet(packet_out)

            # If configured set daytime once and ignore all further time updates
            if (24000 > config['daytime'] > 0 and packet_name == 'Time Update' and not
            utils.is_bad_packet(packet_name, config['minimal_packets'])):
                print('Set daytime to: ' + str(config['daytime']))
                packet_daytime = Packet()
                packet_daytime.write_varint(
                    int(list(clientbound.keys())[list(clientbound.values()).index('Time Update')]))
                world_age = packet_in.read_long()
                packet_daytime.write_long(world_age)
                packet_daytime.write_long(
                    -config['daytime'])  # If negative sun will stop moving at the Math.abs of the time
                packet_recorded = int(t - start_time).to_bytes(4, byteorder='big', signed=True)
                packet_recorded += len(packet_daytime.received).to_bytes(4, byteorder='big', signed=True)
                packet_recorded += packet_daytime.received
                write_buffer += packet_recorded
                utils.BAD_PACKETS.append('Time Update')  # Ignore all further updates

            # Remove weather if configured
            if not config['weather'] and packet_name == 'Change Game State':
                reason = packet_in.read_ubyte()
                if reason == 1 or reason == 2:
                    packet_recorded = ''

            # Teleport confirm
            if packet_name == 'Player Position And Look (clientbound)':
                x = packet_in.read_double()
                y = packet_in.read_double()
                z = packet_in.read_double()
                yaw = packet_in.read_float()
                pitch = packet_in.read_float()
                flag = packet_in.read_byte()
                teleport_id = packet_in.read_varint()
                packet_out = Packet()
                packet_out.write_varint(serverbound['Teleport Confirm'])
                packet_out.write_varint(teleport_id)
                connection.send_packet(packet_out)

            # Update player list for metadata and player tracking
            if packet_name == 'Spawn Player':
                entity_id = packet_in.read_varint()
                if entity_id not in player_ids:
                    player_ids.append(entity_id)
                uuid = packet_in.read_uuid()
                if uuid not in player_uuids:
                    player_uuids.append(uuid)
                last_player_movement = int(time.time() * 1000)

            # Keep track of spawned items and their ids
            if ((config['remove_items'] or config['remove_bats']) and
                    (packet_name == 'Spawn Object' or packet_name == 'Spawn Mob')):
                entity_id = packet_in.read_varint()
                uuid = packet_in.read_uuid()
                type = packet_in.read_byte()
                if ((packet_name == 'Spawn Object' and type == 2 and protocol_version > 340) or
                        (packet_name == 'Spawn Mob' and ((type == 65 and protocol_version <= 340) or (type == 3 and protocol_version > 340)))):
                    blocked_entity_ids.append(entity_id)
                    packet_recorded = ''

            # Remove item pickup animation packet
            if config['remove_items'] and packet_name == 'Collect Item':
                packet_recorded = ''

            # Detecting player activity to continue recording and remove items or bats
            if packet_name in entity_packets:
                entity_id = packet_in.read_varint()
                if config['recording'] and entity_id in player_ids:
                    last_player_movement = t
                if entity_id in blocked_entity_ids:
                    recorded_packet = ''

            # Record all "joining" or "leaving" tab updates to properly start recording players
            # In 1.14.4 Player List Item changes to Player Info
            if (protocol_version < 498 and packet_name == 'Player List Item') or (protocol_version >= 498 and packet_name == 'Player Info'):
                action = packet_in.read_varint()
                if config['recording'] and action == 0:  # int(time.time() * 1000) - last_player_movement <= 5000 and
                    write_buffer += packet_recorded
                    player_number = packet_in.read_varint()
                    uuid = packet_in.read_uuid()
                    name = packet_in.read_utf()

            # Handle chat and process ingame commands
            if packet_name == 'Chat Message (clientbound)':
                try:  # For whatever reason there sometimes exists an empty chat packet..
                    chat = packet_in.read_utf()
                    chat = json.loads(chat)
                    if chat['translate'] == 'chat.type.text':
                        name = chat['with'][0]['hoverEvent']['value']['text'].split(':"')[1].split('"', 1)[0]
                        uuid = chat['with'][0]['hoverEvent']['value']['text'].split(':"')[2].split('"', 1)[0]
                        message = chat['with'][1]
                        if message == '!updateops':
                            utils.request_ops(connection, serverbound, protocol_version)
                            utils.send_chat_message(connection, serverbound, 'Updating OP list')
                        if name in opped_players:
                            print('<' + name + '(OP)> ' + message)
                        else:
                            print('<' + name + '> ' + message)
                        if (config['require_op'] and name in opped_players) or not config['require_op']:
                            if message == '!relog':
                                should_restart = True
                                print('Relogging...')
                                break
                            if message == '!stop':
                                should_restart = False
                                print('Stopping...')
                                break
                            if message == '!ping':
                                utils.send_chat_message(connection, serverbound, 'pong!')
                            if message == '!filesize':
                                utils.send_chat_message(connection, serverbound,
                                                        str(round(file_size / 1000000, 1)) + 'MB')
                            if message == '!time':
                                utils.send_chat_message(connection, serverbound,
                                                        'Recorded time: ' + utils.convert_millis(
                                                            t - start_time - afk_time))
                            if message == '!timeonline':
                                utils.send_chat_message(connection, serverbound,
                                                        'Time client was online: ' + utils.convert_millis(
                                                            t - start_time))
                            if message == '!move':
                                packet_out = Packet()
                                packet_out.write_varint(serverbound['Spectate'])
                                packet_out.write_uuid(uuid)
                                connection.send_packet(packet_out)
                                utils.send_chat_message(connection, serverbound, 'moved to ' + name)
                            if message == '!glow':
                                # Chat system got updated in 1.13
                                if protocol_version > 340:
                                    utils.send_chat_message(connection, serverbound,
                                                            '/effect give @s minecraft:glowing 1000000 0 true')
                                else:
                                    utils.send_chat_message(connection, serverbound,
                                                            '/effect @p minecraft:glowing 1000000 0 true')
                except:
                    pass

            # Process the requested list of opped players
            if packet_name == 'Tab-Complete (clientbound)':
                matches = []
                if protocol_version <= 404:
                    count = packet_in.read_varint()
                    for i in range(count):
                        matches.append(packet_in.read_utf())
                else:
                    id = packet_in.read_varint()
                    start = packet_in.read_varint()
                    length = packet_in.read_varint()
                    count = packet_in.read_varint()
                    for i in range(count):
                        matches.append(packet_in.read_utf())
                        has_tooltip = packet_in.read_bool()
                        if has_tooltip:
                            tooltip = packet_in.read_utf()
                for match in matches:
                    if match not in opped_players:
                        opped_players.append(match)

            # Actual recording
            if (config['recording'] and t - last_player_movement <= 5000 and not
            utils.is_bad_packet(packet_name, config['minimal_packets'])):
                # To prevent constant writing to the disk a buffer of 8kb is used
                if packet_recorded != '':
                    write_buffer += packet_recorded
                if len(write_buffer) > 8192:
                    with open('recording.tmcpr', 'ab+') as replay_recording:
                        replay_recording.write(write_buffer)
                        if debug:
                            print('Recorded:' + str(write_buffer)[:80] + '...')
                        file_size += len(write_buffer)
                        write_buffer = bytearray()

            # Prevent any kind of size increase due to packets beeing added to the buffer it gets cleared when not needed
            if not config['recording'] and len(write_buffer) > 0:
                write_buffer = bytearray()

            # Increase afk timer when recording stopped, afk timer prevents afk time in replays
            if config['recording'] and t - last_player_movement > 5000:
                afk_time += t - last_t

            last_t = t  # Save last packet timestamp for afk delta

            # Every 150mb the client restarts
            if config['recording'] and file_size > 150000000:
                print('Filesize limit reached!')
                utils.send_chat_message(connection, serverbound, 'Filesize limit reached, restarting...')
                should_restart = True
                time.sleep(1)
                break
                # Every 5h recording the the client restarts
            elif config['recording'] and t - start_time - afk_time > 1000 * 60 * 60 * 5:
                print('5h recording reached!')
                utils.send_chat_message(connection, serverbound, '5h recording reached, restarting...')
                should_restart = True
                time.sleep(1)
                break

        else:
            time.sleep(0.0001)  # Release to prevent 100% cpu usage

    # Handling the disconnect
    print('Disconnected')
    if config['recording'] and len(write_buffer) > 0:  # Finish writing if buffer not empty
        with open('recording.tmcpr', 'ab+') as replay_recording:
            replay_recording.write(write_buffer)
            write_buffer = bytearray()
    print('Time client was online: ' + utils.convert_millis(t - start_time))

    if config['recording']:
        print('Recorded time: ' + utils.convert_millis(t - start_time - afk_time))

        # Create metadata file
        with open('metaData.json', 'w') as json_file:
            meta_data = {'singleplayer': 'false', 'serverName': address[0],
                         'duration': int(time.time() * 1000) - start_time - afk_time, 'date': int(time.time() * 1000),
                         'mcversion': mc_version, 'fileFormat': 'MCPR', 'generator': 'SARC',
                         'fileFormatVersion': utils.get_metadata_file_format(protocol_version),
                         'protocol': protocol_version, 'selfId': -1, 'players': player_uuids}
            json.dump(meta_data, json_file)

        # Creating .mcpr zipfile based on timestamp
        print('Creating .mcpr file...')
        date = datetime.datetime.today().strftime('SARC_%Y%m%d_%H_%S')
        zipf = zipfile.ZipFile(date + '.mcpr', 'w', zipfile.ZIP_DEFLATED)
        zipf.write('metaData.json')
        zipf.write('recording.tmcpr')
        os.remove('metaData.json')
        os.remove('recording.tmcpr')
        print('Finished!')
    connection.close()
    return should_restart