mirror of
https://github.com/KrisKennaway/pyapple2disk.git
synced 2024-11-29 14:50:00 +00:00
- Rename File class to FileType and add support for a filetype parser
- Add support for parsing Applesoft basic files - Add a new File class that receives the contents of a DOS 3.3 file (also parsed, if applicable) - Some minor bugfixes
This commit is contained in:
parent
f6581c9078
commit
b97c404514
156
src/apple2disk/applesoft.py
Normal file
156
src/apple2disk/applesoft.py
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
import bitstring
|
||||||
|
|
||||||
|
TOKENS = {
|
||||||
|
0x80: 'END',
|
||||||
|
0x81: 'FOR',
|
||||||
|
0x82: 'NEXT',
|
||||||
|
0x83: 'DATA',
|
||||||
|
0x84: 'INPUT',
|
||||||
|
0x85: 'DEL',
|
||||||
|
0x86: 'DIM',
|
||||||
|
0x87: 'READ',
|
||||||
|
0x88: 'GR',
|
||||||
|
0x89: 'TEXT',
|
||||||
|
0x8A: 'PR #',
|
||||||
|
0x8B: 'IN #',
|
||||||
|
0x8C: 'CALL',
|
||||||
|
0x8D: 'PLOT',
|
||||||
|
0x8E: 'HLIN',
|
||||||
|
0x8F: 'VLIN',
|
||||||
|
0x90: 'HGR2',
|
||||||
|
0x91: 'HGR',
|
||||||
|
0x92: 'HCOLOR=',
|
||||||
|
0x93: 'HPLOT',
|
||||||
|
0x94: 'DRAW',
|
||||||
|
0x95: 'XDRAW',
|
||||||
|
0x96: 'HTAB',
|
||||||
|
0x97: 'HOME',
|
||||||
|
0x98: 'ROT=',
|
||||||
|
0x99: 'SCALE=',
|
||||||
|
0x9A: 'SHLOAD',
|
||||||
|
0x9B: 'TRACE',
|
||||||
|
0x9C: 'NOTRACE',
|
||||||
|
0x9D: 'NORMAL',
|
||||||
|
0x9E: 'INVERSE',
|
||||||
|
0x9F: 'FLASH',
|
||||||
|
0xA0: 'COLOR=',
|
||||||
|
0xA1: 'POP',
|
||||||
|
0xA2: 'VTAB',
|
||||||
|
0xA3: 'HIMEM:',
|
||||||
|
0xA4: 'LOMEM:',
|
||||||
|
0xA5: 'ONERR',
|
||||||
|
0xA6: 'RESUME',
|
||||||
|
0xA7: 'RECALL',
|
||||||
|
0xA8: 'STORE',
|
||||||
|
0xA9: 'SPEED=',
|
||||||
|
0xAA: 'LET',
|
||||||
|
0xAB: 'GOTO',
|
||||||
|
0xAC: 'RUN',
|
||||||
|
0xAD: 'IF',
|
||||||
|
0xAE: 'RESTORE',
|
||||||
|
0xAF: '&',
|
||||||
|
0xB0: 'GOSUB',
|
||||||
|
0xB1: 'RETURN',
|
||||||
|
0xB2: 'REM',
|
||||||
|
0xB3: 'STOP',
|
||||||
|
0xB4: 'ON',
|
||||||
|
0xB5: 'WAIT',
|
||||||
|
0xB6: 'LOAD',
|
||||||
|
0xB7: 'SAVE',
|
||||||
|
0xB8: 'DEF FN',
|
||||||
|
0xB9: 'POKE',
|
||||||
|
0xBA: 'PRINT',
|
||||||
|
0xBB: 'CONT',
|
||||||
|
0xBC: 'LIST',
|
||||||
|
0xBD: 'CLEAR',
|
||||||
|
0xBE: 'GET',
|
||||||
|
0xBF: 'NEW',
|
||||||
|
0xC0: 'TAB',
|
||||||
|
0xC1: 'TO',
|
||||||
|
0xC2: 'FN',
|
||||||
|
0xC3: 'SPC(',
|
||||||
|
0xC4: 'THEN',
|
||||||
|
0xC5: 'AT',
|
||||||
|
0xC6: 'NOT',
|
||||||
|
0xC7: 'STEP',
|
||||||
|
0xC8: '+',
|
||||||
|
0xC9: '-',
|
||||||
|
0xCA: '*',
|
||||||
|
0xCB: '/',
|
||||||
|
0xCC: ';',
|
||||||
|
0xCD: 'AND',
|
||||||
|
0xCE: 'OR',
|
||||||
|
0xCF: '>',
|
||||||
|
0xD0: '=',
|
||||||
|
0xD1: '<',
|
||||||
|
0xD2: 'SGN',
|
||||||
|
0xD3: 'INT',
|
||||||
|
0xD4: 'ABS',
|
||||||
|
0xD5: 'USR',
|
||||||
|
0xD6: 'FRE',
|
||||||
|
0xD7: 'SCRN (',
|
||||||
|
0xD8: 'PDL',
|
||||||
|
0xD9: 'POS',
|
||||||
|
0xDA: 'SQR',
|
||||||
|
0xDB: 'RND',
|
||||||
|
0xDC: 'LOG',
|
||||||
|
0xDD: 'EXP',
|
||||||
|
0xDE: 'COS',
|
||||||
|
0xDF: 'SIN',
|
||||||
|
0xE0: 'TAN',
|
||||||
|
0xE1: 'ATN',
|
||||||
|
0xE2: 'PEEK',
|
||||||
|
0xE3: 'LEN',
|
||||||
|
0xE4: 'STR$',
|
||||||
|
0xE5: 'VAL',
|
||||||
|
0xE6: 'ASC',
|
||||||
|
0xE7: 'CHR$',
|
||||||
|
0xE8: 'LEFT$',
|
||||||
|
0xE9: 'RIGHT$',
|
||||||
|
0xEA: 'MID$'
|
||||||
|
}
|
||||||
|
|
||||||
|
# TODO: report anomaly if an unknown token is used
|
||||||
|
|
||||||
|
class AppleSoft(object):
|
||||||
|
def __init__(self, data):
|
||||||
|
data = bitstring.ConstBitStream(data)
|
||||||
|
|
||||||
|
self.length = data.read('uintle:16')
|
||||||
|
|
||||||
|
self.lines = {}
|
||||||
|
last_line_number = -1
|
||||||
|
last_memory = 0x801
|
||||||
|
while data:
|
||||||
|
next_memory, line_number = data.readlist('uintle:16, uintle:16')
|
||||||
|
if not next_memory:
|
||||||
|
break
|
||||||
|
|
||||||
|
line = []
|
||||||
|
bytes_read = 4
|
||||||
|
while True:
|
||||||
|
token = data.read('uint:8')
|
||||||
|
bytes_read += 1
|
||||||
|
if token == 0:
|
||||||
|
self.lines[line_number] = ''.join(line)
|
||||||
|
break
|
||||||
|
|
||||||
|
if token in TOKENS:
|
||||||
|
line.append(' ' + TOKENS[token] + ' ')
|
||||||
|
else:
|
||||||
|
line.append(chr(token))
|
||||||
|
|
||||||
|
if last_memory + bytes_read != next_memory:
|
||||||
|
print "%x + %x == %x != %x (gap %d)" % (last_memory, bytes_read, last_memory + bytes_read, next_memory, next_memory - last_memory - bytes_read)
|
||||||
|
|
||||||
|
if line_number <= last_line_number:
|
||||||
|
print "%d <= %d: %s" % (line_number, last_line_number, ''.join(line))
|
||||||
|
|
||||||
|
print "%d %s" % (line_number, ''.join(line))
|
||||||
|
last_line_number = line_number
|
||||||
|
last_memory = next_memory
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '\n'.join('%s %s' % (num, line) for (num, line) in sorted(self.lines.items()))
|
||||||
|
|
||||||
|
|
@ -1,20 +1,22 @@
|
|||||||
|
import applesoft
|
||||||
import bitstring
|
import bitstring
|
||||||
import disk as disklib
|
import disk as disklib
|
||||||
import string
|
import string
|
||||||
|
|
||||||
PRINTABLE = set(string.letters + string.digits + string.punctuation + ' ')
|
PRINTABLE = set(string.letters + string.digits + string.punctuation + ' ')
|
||||||
|
|
||||||
class File(object):
|
class FileType(object):
|
||||||
def __init__(self, short_type, long_type):
|
def __init__(self, short_type, long_type, parser=None):
|
||||||
self.short_type = short_type
|
self.short_type = short_type
|
||||||
self.long_type = long_type
|
self.long_type = long_type
|
||||||
|
self.parser = parser
|
||||||
|
|
||||||
FILE_TYPES = {
|
FILE_TYPES = {
|
||||||
0x00: File('T', 'TEXT'),
|
0x00: FileType('T', 'TEXT'),
|
||||||
0x01: File('I', 'INTEGER BASIC'),
|
0x01: FileType('I', 'INTEGER BASIC'),
|
||||||
# TODO: add handler for parsing file content
|
# TODO: add handler for parsing file content
|
||||||
0x02: File('A', 'APPLESOFT BASIC'),
|
0x02: FileType('A', 'APPLESOFT BASIC', applesoft.AppleSoft),
|
||||||
0x04: File('B', 'BINARY'),
|
0x04: FileType('B', 'BINARY'),
|
||||||
# TODO: others
|
# TODO: others
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,9 +154,19 @@ class Dos33Disk(disklib.Disk):
|
|||||||
# TODO: why does DOS 3.3 sometimes display e.g. volume 254 when the VTOC says 178
|
# TODO: why does DOS 3.3 sometimes display e.g. volume 254 when the VTOC says 178
|
||||||
self.volume = self.vtoc.volume
|
self.volume = self.vtoc.volume
|
||||||
|
|
||||||
|
# List of stripped filenames in catalog order
|
||||||
|
self.filenames = []
|
||||||
|
|
||||||
|
# Maps stripped filenames to CatalogEntry objects
|
||||||
|
self.catalog = {}
|
||||||
|
|
||||||
self.ReadCatalog()
|
self.ReadCatalog()
|
||||||
|
|
||||||
|
# Maps stripped filename to File() object
|
||||||
|
self.files = {}
|
||||||
for catalog_entry in self.catalog.itervalues():
|
for catalog_entry in self.catalog.itervalues():
|
||||||
self.ReadCatalogEntry(catalog_entry)
|
# TODO: last character has special meaning for deleted files and may legitimately be whitespace. Could collide with a non-deleted file of the same stripped name
|
||||||
|
self.files[catalog_entry.FileName().rstrip()] = self.ReadCatalogEntry(catalog_entry)
|
||||||
|
|
||||||
def _ReadVTOC(self):
|
def _ReadVTOC(self):
|
||||||
return VTOCSector.fromSector(self.ReadSector(0x11, 0x0))
|
return VTOCSector.fromSector(self.ReadSector(0x11, 0x0))
|
||||||
@ -218,14 +230,14 @@ class Dos33Disk(disklib.Disk):
|
|||||||
fds = FileDataSector.fromSector(self.ReadSector(t, s), entry.FileName())
|
fds = FileDataSector.fromSector(self.ReadSector(t, s), entry.FileName())
|
||||||
contents.append(fds.data)
|
contents.append(fds.data)
|
||||||
|
|
||||||
return contents
|
return File(entry, contents)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
catalog = ['DISK VOLUME %d\n' % self.volume]
|
catalog = ['DISK VOLUME %d\n' % self.volume]
|
||||||
for filename in self.catalog:
|
for filename in self.catalog:
|
||||||
entry = self.files[filename]
|
entry = self.files[filename]
|
||||||
try:
|
try:
|
||||||
file_type = FILE_TYPES[entry.file_type][0]
|
file_type = FILE_TYPES[entry.file_type].short_type
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print "%s has unknown file type %02x" % (entry.FileName(), entry.file_type)
|
print "%s has unknown file type %02x" % (entry.FileName(), entry.file_type)
|
||||||
file_type = '?'
|
file_type = '?'
|
||||||
@ -243,8 +255,8 @@ class CatalogEntry(object):
|
|||||||
def __init__(self, track, sector, file_type, file_name, length):
|
def __init__(self, track, sector, file_type, file_name, length):
|
||||||
self.track = track
|
self.track = track
|
||||||
self.sector = sector
|
self.sector = sector
|
||||||
self.file_type = file_type & 0x7f
|
self.file_type = FILE_TYPES[file_type & 0x7f]
|
||||||
self.locked = file_type & 0x80
|
self.locked = bool(file_type & 0x80)
|
||||||
self.file_name = file_name
|
self.file_name = file_name
|
||||||
self.length = length
|
self.length = length
|
||||||
# TODO: handle deleted files (track = 0xff, original track in file_name[0x20])
|
# TODO: handle deleted files (track = 0xff, original track in file_name[0x20])
|
||||||
@ -253,4 +265,19 @@ class CatalogEntry(object):
|
|||||||
return '%s' % ''.join([chr(ord(b) & 0x7f) for b in self.file_name])
|
return '%s' % ''.join([chr(ord(b) & 0x7f) for b in self.file_name])
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Track $%02x Sector $%02x Type %s Name: %s Length: %d" % (self.track, self.sector, FILE_TYPES[self.file_type], self.FileName(), self.length)
|
type_string = self.file_type.long_type
|
||||||
|
if self.locked:
|
||||||
|
type_string += ' (LOCKED)'
|
||||||
|
return "Track $%02x Sector $%02x Type %s Name: %s Length: %d" % (self.track, self.sector, type_string, self.FileName(), self.length)
|
||||||
|
|
||||||
|
|
||||||
|
class File(object):
|
||||||
|
def __init__(self, catalog_entry, contents):
|
||||||
|
self.catalog_entry = catalog_entry
|
||||||
|
|
||||||
|
self.contents = contents
|
||||||
|
parser = catalog_entry.file_type.parser
|
||||||
|
if parser:
|
||||||
|
self.parsed_contents = parser(contents)
|
||||||
|
else:
|
||||||
|
self.parsed_contents = None
|
@ -24,6 +24,14 @@ def main():
|
|||||||
try:
|
try:
|
||||||
img = dos33disk.Dos33Disk.Taste(img)
|
img = dos33disk.Dos33Disk.Taste(img)
|
||||||
print "%s is a DOS 3.3 disk, volume %d" % (f, img.volume)
|
print "%s is a DOS 3.3 disk, volume %d" % (f, img.volume)
|
||||||
|
|
||||||
|
for fn in img.filenames:
|
||||||
|
f = img.files[fn]
|
||||||
|
|
||||||
|
print f.catalog_entry
|
||||||
|
if f.parsed_contents:
|
||||||
|
print f.parsed_contents
|
||||||
|
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
@ -34,6 +42,7 @@ def main():
|
|||||||
for ts, data in sorted(img.sectors.iteritems()):
|
for ts, data in sorted(img.sectors.iteritems()):
|
||||||
print data
|
print data
|
||||||
|
|
||||||
|
|
||||||
# Group disks by hash of RWTS sector
|
# Group disks by hash of RWTS sector
|
||||||
rwts_hashes = {}
|
rwts_hashes = {}
|
||||||
for f, d in disks.iteritems():
|
for f, d in disks.iteritems():
|
||||||
|
Loading…
Reference in New Issue
Block a user