Example #1
0
    def read_blocks(file):
        """
        Reads all of the data blocks that exist in the file.

        :param file: the file object to read from
        :return: an array of bytes read from the file
        """
        data = []

        while True:
            if not CassetteFile.seek_sequence(file, ["55", "3C"]):
                print("Data or EOF block not found")
                return []

            block_type = Value.create_from_byte(file.read(1))
            if block_type.hex() == "FF":
                file.read(3)
                return data

            elif block_type.hex() == "01":
                data_len = Value.create_from_byte(file.read(1))
                for _ in range(data_len.int):
                    data.append(Value.create_from_byte(file.read(1)).int)

                file.read(2)
            else:
                print("Unknown block type found: {}".format(block_type.hex()))
                return []
Example #2
0
    def seek_sequence(file, sequence):
        """
        Reads the file until the specified sequence is found. Returns
        True if the sequence is found. The internal file byte pointer will
        point to the first byte beyond the sequence. If an EOF occurs,
        will return False.

        :param file: the file object to read from
        :param sequence: the sequence of values to search for
        :return: True if the sequence is found, False otherwise
        """
        sequence_length = len(sequence)
        while True:
            last_values = []
            try:
                for _ in range(sequence_length):
                    last_values.append(
                        Value.create_from_byte(file.read(1)).hex())
            except ValueError:
                return False

            if last_values == sequence:
                return True

            file.seek(-(sequence_length - 1), 1)
Example #3
0
    def list_files(self, filenames=None):
        files = []

        # Read the File Allocation Table
        self.host_file.seek(DiskFile.FAT_OFFSET, 0)
        fat = self.host_file.read(256)

        # Move through elements in the Directory Table and read them into CoCoFile objects
        self.host_file.seek(DiskFile.DIR_OFFSET, 0)
        for file_number in range(0, 72):
            next_byte = Value.create_from_byte(self.host_file.peek(1)[:1])
            if next_byte.hex() == "00" or next_byte.hex() == "FF":
                self.host_file.seek(32, 1)
            else:
                name = "{}".format(
                    self.host_file.read(8).decode("utf-8").replace(" ", ""))
                extension = "{}".format(self.host_file.read(3).decode("utf-8"))
                file_type = Value.create_from_byte(self.host_file.read(1))
                data_type = Value.create_from_byte(self.host_file.read(1))
                starting_granule = Value.create_from_byte(
                    self.host_file.read(1))
                current_location = self.host_file.tell()
                preamble = DiskFile.read_preamble(self.host_file,
                                                  starting_granule.int)
                file_data = self.read_data(
                    self.host_file,
                    starting_granule.int,
                    has_preamble=True,
                    data_length=preamble.data_length.int,
                    fat=fat,
                )
                postamble = DiskFile.read_postamble(self.host_file)
                self.host_file.seek(current_location, 0)
                coco_file = CoCoFile(name=name,
                                     extension=extension,
                                     type=file_type,
                                     data_type=data_type,
                                     load_addr=preamble.load_addr,
                                     exec_addr=postamble.exec_addr,
                                     data=file_data,
                                     ignore_gaps=True)
                files.append(coco_file)
                self.host_file.seek(19, 1)

        return files
Example #4
0
    def read_preamble(cls, file, starting_granule):
        """
        Reads the preamble data for the file. The preamble is a collection of 5
        bytes at the start of a binary file:

            byte 0 - always $00
            byte 1,2 - the data length of the file
            byte 3,4 - the load address for the file

        :param file: the file object to modify
        :param starting_granule: the granule number that contains the preamble
        :return: a populated Preamble object
        """
        DiskFile.seek_granule(file, starting_granule)
        preamble_flag = Value.create_from_byte(file.read(1))
        if preamble_flag.hex() != "00":
            raise ValueError("Invalid preamble flag {}".format(
                preamble_flag.hex()))
        return Preamble(data_length=Value.create_from_byte(file.read(2)),
                        load_addr=Value.create_from_byte(file.read(2)))
Example #5
0
    def read_postamble(cls, file):
        """
        Reads the postamble of a binary file. The postamble is a collection of
        5 bytes as follows:

            byte 0 - always $FF
            byte 1,2 - always $00, $00
            byte 3,4 - the exec address of the binary file

        :param file: the file object to modify
        :return: a populated Postamble object
        """
        postamble_flag = Value.create_from_byte(file.read(1))
        if postamble_flag.hex() != "FF":
            raise ValueError("Invalid first postamble flag {}".format(
                postamble_flag.hex()))
        postamble_flag = Value.create_from_byte(file.read(2))
        if postamble_flag.hex() != "00":
            raise ValueError("Invalid second postamble flag {}".format(
                postamble_flag.hex()))
        return Postamble(exec_addr=Value.create_from_byte(file.read(2)), )
Example #6
0
    def read_leader(file):
        """
        Reads a cassette leader. Should consist of 128 bytes of the value $55.
        Raises a ValueError if there is a problem.

        :param file: the file object to read from
        """
        for _ in range(128):
            value = Value.create_from_byte(file.read(1))
            if value.hex() != "55":
                raise ValueError("[{}] invalid leader byte".format(
                    value.hex()))
Example #7
0
    def read_data(cls,
                  file,
                  starting_granule,
                  has_preamble=False,
                  data_length=0,
                  fat=[]):
        """
        Reads a collection of data from a disk image.

        :param file: the file object containing data to read from
        :param starting_granule: the starting granule for the file
        :param has_preamble: whether there is a preamble to be read
        :param data_length: the length of data to read
        :param fat: the File Allocation Table data for the disk
        :return: the raw data from the specified file
        """
        DiskFile.seek_granule(file, starting_granule)
        file_data = []
        chunk_size = DiskFile.HALF_TRACK_LEN

        # Skip over preamble if it exists
        if has_preamble:
            file.read(5)
            chunk_size -= 5

        # Check to see if we are reading more than one granule
        if data_length > chunk_size:
            for _ in range(chunk_size):
                file_data.append(Value.create_from_byte(file.read(1)).int)
                data_length -= 1
            next_granule = fat[starting_granule]
            file_data.extend(
                DiskFile.read_data(file,
                                   next_granule,
                                   data_length=data_length,
                                   fat=fat))
        else:
            for _ in range(data_length):
                file_data.append(Value.create_from_byte(file.read(1)).int)
        return file_data
Example #8
0
    def read_file(file):
        """
        Reads a cassette file, and returns a CoCoFile object with the
        information for the next file contained on the cassette file.

        :param file: the file object to read from
        :return: a CoCoFile with header information
        """
        if not CassetteFile.seek_sequence(file, ["55", "3C", "00"]):
            return None

        # Skip length byte
        file.read(1)

        # Extract file meta-data
        name = file.read(8).decode("utf-8")
        file_type = Value.create_from_byte(file.read(1))
        data_type = Value.create_from_byte(file.read(1))
        gaps = Value.create_from_byte(file.read(1))
        load_addr = Value.create_from_byte(file.read(2))
        exec_addr = Value.create_from_byte(file.read(2))

        # Advance two spaces to move past the header
        file.read(2)
        file.read(2)

        data = CassetteFile.read_blocks(file)
        if not data:
            print("Data blocks not found for file {}".format(name))
            return None

        return CoCoFile(name=name,
                        type=file_type,
                        data_type=data_type,
                        gaps=gaps,
                        load_addr=load_addr,
                        exec_addr=exec_addr,
                        data=data)
Example #9
0
 def test_create_from_byte_works_correctly(self):
     result = Value.create_from_byte(b"\xDE\xAD")
     self.assertEqual(result.int, 0xDEAD)
Example #10
0
 def test_create_from_byte_raises_on_empty_byte(self):
     with self.assertRaises(ValueTypeError) as context:
         Value.create_from_byte(b"")
     self.assertEqual("No byte available for reading", str(context.exception))