Skip to content

Commit

Permalink
Merge pull request #81 from craigthomas/non-machine-language-files
Browse files Browse the repository at this point in the history
Fix bug where non-machine language files could not be read
  • Loading branch information
craigthomas authored Oct 3, 2022
2 parents 61d9e20 + c3bbd30 commit e8dd5fd
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 32 deletions.
6 changes: 4 additions & 2 deletions cocoasm/virtualfiles/coco_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ def __str__(self):
gaps = "Gaps"
result += "Gap Status: {}\n".format(gaps)

result += "Load Addr: ${}\n".format(self.load_addr.hex(size=4))
result += "Exec Addr: ${}\n".format(self.exec_addr.hex(size=4))
if self.type.hex() == "02":
result += "Load Addr: ${}\n".format(self.load_addr.hex(size=4))
result += "Exec Addr: ${}\n".format(self.exec_addr.hex(size=4))

result += "Data Len: {} bytes".format(len(self.data))
return result

Expand Down
62 changes: 53 additions & 9 deletions cocoasm/virtualfiles/disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,34 +142,78 @@ def list_files(self, filenames=None):
data_type = NumericValue(self.buffer[pointer])
pointer += 1
starting_granule = NumericValue(self.buffer[pointer])
pointer += 19

# TODO: if the type is not binary, then don't look for preamble or postamble
preamble = self.read_preamble(starting_granule.int)
pointer += 1
bytes_in_last_sector = self.read_word(pointer)
pointer += 18

# Set up data definitions
has_preamble = False
load_addr = NoneValue()
exec_addr = NoneValue()

# If the file is binary, read the preamble and postamble, otherwise don't
if file_type.int == 0x02:
preamble = self.read_preamble(starting_granule.int)
has_preamble = True
data_length = preamble.data_length.int
load_addr = preamble.load_addr
else:
data_length = self.calculate_file_length(starting_granule.int, fat, bytes_in_last_sector.int)

file_data, post_pointer = self.read_data(
starting_granule.int,
fat,
has_preamble=True,
data_length=preamble.data_length.int,
has_preamble=has_preamble,
data_length=data_length,
)

postamble = self.read_postamble(post_pointer)
if has_preamble:
postamble = self.read_postamble(post_pointer)
exec_addr = postamble.exec_addr

coco_file = CoCoFile(
name=name,
extension=extension,
type=file_type,
data_type=data_type,
load_addr=preamble.load_addr,
exec_addr=postamble.exec_addr,
load_addr=load_addr,
exec_addr=exec_addr,
data=file_data,
ignore_gaps=True
)
files.append(coco_file)

return files

@staticmethod
def calculate_file_length(granule, fat, bytes_in_last_sector):
"""
Calculates the length of the file without using preamble data. It does so by
calculating how many granules are used as described in the FAT, and then
calculating how many sectors are used in the final granule of the file,
and adding the final number of bytes used in the last sector of the last
granule.
:param granule: the granule where the file starts at
:param fat: the file allocation table data
:param bytes_in_last_sector: the number of bytes stored in the last sector
:return: the total bytes used by the file
"""
is_last_granule = False
total_bytes = 0

while not is_last_granule:
fat_entry = fat[granule]
if (fat_entry & 0xC0) == 0xC0:
is_last_granule = True
total_bytes += ((fat_entry & 0x1F) - 1) * DiskConstants.BYTES_PER_SECTOR
total_bytes += bytes_in_last_sector
else:
total_bytes += DiskConstants.HALF_TRACK_LEN
granule = fat_entry

return total_bytes

def directory_entry_in_use(self, entry_number):
"""
Returns True if the directory entry specified is in use, False otherwise.
Expand Down
52 changes: 35 additions & 17 deletions test/virtualfiles/test_coco_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ def setUp(self):
'Filename: {}\nExtension: {}\nFile Type: {}\nData Type: {}\n'
'Load Addr: ${}\nExec Addr: ${}\nData Len: {} bytes'
)
self.str_format_no_exec = (
'Filename: {}\nExtension: {}\nFile Type: {}\nData Type: {}\nGap Status: {}\n'
'Data Len: {} bytes'
)
self.str_format_no_gaps_no_exec = (
'Filename: {}\nExtension: {}\nFile Type: {}\nData Type: {}\n'
'Data Len: {} bytes'
)
self.name = "testfile"
self.extension = "bas"
self.file_type = NumericValue(0)
Expand All @@ -53,32 +61,42 @@ def compose_coco_file(self):
gaps=self.gaps,
)

def compose_expected_string(self):
return self.str_format.format(
self.name, self.extension, self.file_type, self.data_type, self.ignore_gaps,
self.load_addr, self.exec_addr, len(self.data)
)

def compose_expected_string_ignore_gaps(self):
return self.str_format_no_gaps.format(
self.name, self.extension, self.file_type, self.data_type,
self.load_addr, self.exec_addr, len(self.data)
)
def compose_expected_string(self, ignore_gaps=False, is_basic_file=False):
if not ignore_gaps and not is_basic_file:
return self.str_format.format(
self.name, self.extension, self.file_type, self.data_type, self.ignore_gaps,
self.load_addr, self.exec_addr, len(self.data)
)
if ignore_gaps and not is_basic_file:
return self.str_format_no_gaps.format(
self.name, self.extension, self.file_type, self.data_type,
self.load_addr, self.exec_addr, len(self.data)
)
if not ignore_gaps and is_basic_file:
return self.str_format_no_exec.format(
self.name, self.extension, self.file_type, self.data_type, self.ignore_gaps,
len(self.data)
)
if ignore_gaps and is_basic_file:
return self.str_format_no_gaps_no_exec.format(
self.name, self.extension, self.file_type, self.data_type,
len(self.data)
)

def test_str_is_correct_default_branches(self):
result = self.compose_coco_file()
self.ignore_gaps = "No Gaps"
self.file_type = "BASIC"
self.data_type = "Binary"
self.assertEqual(self.compose_expected_string(), str(result))
self.assertEqual(self.compose_expected_string(is_basic_file=True), str(result))

def test_str_is_correct_data_filetype(self):
self.file_type = NumericValue(1)
result = self.compose_coco_file()
self.ignore_gaps = "No Gaps"
self.file_type = "Data"
self.data_type = "Binary"
self.assertEqual(self.compose_expected_string(), str(result))
self.assertEqual(self.compose_expected_string(is_basic_file=True), str(result))

def test_str_is_correct_object_filetype(self):
self.file_type = NumericValue(2)
Expand All @@ -94,30 +112,30 @@ def test_str_is_correct_text_filetype(self):
self.ignore_gaps = "No Gaps"
self.file_type = "Text"
self.data_type = "Binary"
self.assertEqual(self.compose_expected_string(), str(result))
self.assertEqual(self.compose_expected_string(is_basic_file=True), str(result))

def test_str_is_correct_ascii_datatype(self):
self.data_type = NumericValue(0xFF)
result = self.compose_coco_file()
self.ignore_gaps = "No Gaps"
self.file_type = "BASIC"
self.data_type = "ASCII"
self.assertEqual(self.compose_expected_string(), str(result))
self.assertEqual(self.compose_expected_string(is_basic_file=True), str(result))

def test_str_is_correct_gaps(self):
self.gaps = NumericValue(0xFF)
result = self.compose_coco_file()
self.ignore_gaps = "Gaps"
self.file_type = "BASIC"
self.data_type = "Binary"
self.assertEqual(self.compose_expected_string(), str(result))
self.assertEqual(self.compose_expected_string(is_basic_file=True), str(result))

def test_str_is_correct_ignore_gaps(self):
self.ignore_gaps = True
result = self.compose_coco_file()
self.file_type = "BASIC"
self.data_type = "Binary"
self.assertEqual(self.compose_expected_string_ignore_gaps(), str(result))
self.assertEqual(self.compose_expected_string(ignore_gaps=True, is_basic_file=True), str(result))


# M A I N #####################################################################
Expand Down
24 changes: 20 additions & 4 deletions test/virtualfiles/test_disk.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,7 @@ def test_list_files_multiple_entries_correct(self):
DiskConstants.DIR_OFFSET,
[0x54, 0x45, 0x53, 0x54, 0x20, 0x20, 0x20, 0x20, 0x42, 0x49, 0x4E, 0x02, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x53, 0x45, 0x43, 0x4F, 0x4E, 0x44, 0x20, 0x20, 0x42, 0x41, 0x53, 0x00, 0xFF, 0x01, 0x00, 0x00,
0x53, 0x45, 0x43, 0x4F, 0x4E, 0x44, 0x20, 0x20, 0x42, 0x41, 0x53, 0x00, 0xFF, 0x01, 0x00, 0x0C,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
)
disk_file.write_bytes_to_buffer(
Expand All @@ -388,6 +388,10 @@ def test_list_files_multiple_entries_correct(self):
DiskFile.seek_granule(1),
[0x00, 0x00, 0x02, 0xCA, 0xFE, 0xBB, 0xBB, 0xFF, 0x00, 0x00, 0xBA, 0xBE]
)
disk_file.write_bytes_to_buffer(
DiskConstants.FAT_OFFSET,
[0x00, 0xC1]
)
coco_files = disk_file.list_files()
self.assertEqual(2, len(coco_files))
coco_file = coco_files[0]
Expand All @@ -404,9 +408,9 @@ def test_list_files_multiple_entries_correct(self):
self.assertEqual("BAS", coco_file.extension)
self.assertEqual(0x00, coco_file.type.int)
self.assertEqual(0xFF, coco_file.data_type.int)
self.assertEqual("CAFE", coco_file.load_addr.hex())
self.assertEqual("BABE", coco_file.exec_addr.hex())
self.assertEqual([0xBB, 0xBB], coco_file.data)
self.assertEqual("", coco_file.load_addr.hex())
self.assertEqual("", coco_file.exec_addr.hex())
self.assertEqual([0x00, 0x00, 0x02, 0xCA, 0xFE, 0xBB, 0xBB, 0xFF, 0x00, 0x00, 0xBA, 0xBE], coco_file.data)

def test_write_to_granules_does_nothing_on_empty_allocated_granules(self):
disk_file = DiskFile(buffer=[0xFF] * 161280)
Expand Down Expand Up @@ -484,6 +488,18 @@ def test_add_file_raises_if_no_directory_entry_available(self):
)
)

def test_calculate_file_length_single_granule_single_sector_correct(self):
result = DiskFile.calculate_file_length(0, [0xC1], 256)
self.assertEqual(256, result)

def test_calculate_file_length_single_granule_multiple_sectors_correct(self):
result = DiskFile.calculate_file_length(0, [0xC2], 256)
self.assertEqual(512, result)

def test_calculate_file_length_mutiple_granules_multiple_sectors_correct(self):
result = DiskFile.calculate_file_length(0, [0x01, 0x02, 0xC2], 256)
self.assertEqual(4608 + 512, result)

# M A I N #####################################################################


Expand Down

0 comments on commit e8dd5fd

Please sign in to comment.