mirror of
https://github.com/KrisKennaway/pyapple2disk.git
synced 2024-11-25 18:30:59 +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 disk as disklib
|
||||
import string
|
||||
|
||||
PRINTABLE = set(string.letters + string.digits + string.punctuation + ' ')
|
||||
|
||||
class File(object):
|
||||
def __init__(self, short_type, long_type):
|
||||
class FileType(object):
|
||||
def __init__(self, short_type, long_type, parser=None):
|
||||
self.short_type = short_type
|
||||
self.long_type = long_type
|
||||
self.parser = parser
|
||||
|
||||
FILE_TYPES = {
|
||||
0x00: File('T', 'TEXT'),
|
||||
0x01: File('I', 'INTEGER BASIC'),
|
||||
0x00: FileType('T', 'TEXT'),
|
||||
0x01: FileType('I', 'INTEGER BASIC'),
|
||||
# TODO: add handler for parsing file content
|
||||
0x02: File('A', 'APPLESOFT BASIC'),
|
||||
0x04: File('B', 'BINARY'),
|
||||
0x02: FileType('A', 'APPLESOFT BASIC', applesoft.AppleSoft),
|
||||
0x04: FileType('B', 'BINARY'),
|
||||
# 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
|
||||
self.volume = self.vtoc.volume
|
||||
|
||||
# List of stripped filenames in catalog order
|
||||
self.filenames = []
|
||||
|
||||
# Maps stripped filenames to CatalogEntry objects
|
||||
self.catalog = {}
|
||||
|
||||
self.ReadCatalog()
|
||||
|
||||
# Maps stripped filename to File() object
|
||||
self.files = {}
|
||||
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):
|
||||
return VTOCSector.fromSector(self.ReadSector(0x11, 0x0))
|
||||
@ -218,14 +230,14 @@ class Dos33Disk(disklib.Disk):
|
||||
fds = FileDataSector.fromSector(self.ReadSector(t, s), entry.FileName())
|
||||
contents.append(fds.data)
|
||||
|
||||
return contents
|
||||
return File(entry, contents)
|
||||
|
||||
def __str__(self):
|
||||
catalog = ['DISK VOLUME %d\n' % self.volume]
|
||||
for filename in self.catalog:
|
||||
entry = self.files[filename]
|
||||
try:
|
||||
file_type = FILE_TYPES[entry.file_type][0]
|
||||
file_type = FILE_TYPES[entry.file_type].short_type
|
||||
except KeyError:
|
||||
print "%s has unknown file type %02x" % (entry.FileName(), entry.file_type)
|
||||
file_type = '?'
|
||||
@ -243,8 +255,8 @@ class CatalogEntry(object):
|
||||
def __init__(self, track, sector, file_type, file_name, length):
|
||||
self.track = track
|
||||
self.sector = sector
|
||||
self.file_type = file_type & 0x7f
|
||||
self.locked = file_type & 0x80
|
||||
self.file_type = FILE_TYPES[file_type & 0x7f]
|
||||
self.locked = bool(file_type & 0x80)
|
||||
self.file_name = file_name
|
||||
self.length = length
|
||||
# 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])
|
||||
|
||||
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:
|
||||
img = dos33disk.Dos33Disk.Taste(img)
|
||||
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:
|
||||
pass
|
||||
except AssertionError:
|
||||
@ -34,6 +42,7 @@ def main():
|
||||
for ts, data in sorted(img.sectors.iteritems()):
|
||||
print data
|
||||
|
||||
|
||||
# Group disks by hash of RWTS sector
|
||||
rwts_hashes = {}
|
||||
for f, d in disks.iteritems():
|
||||
|
Loading…
Reference in New Issue
Block a user