1
0
mirror of https://github.com/rzzzwilson/pymlac.git synced 2025-06-10 09:32:41 +00:00
2016-05-13 15:58:47 +07:00

649 lines
21 KiB
Python
Executable File

#!/usr/bin/env python
# coding: utf-8
"""
Load a PTP file handling various formats.
Usage:
import loadptp
result = loadptp.load('test.ptp')
if result:
(format, memory, start, initial_ac) = result
print('test.ptp is %s format' % format)
else:
print('UNKNOWN FORMAT')
"""
import struct
# size of the Imlac memory
DefaultMemSize = 04000
# size of block loader
LoaderSize = 0100
# assumed max length of zero trailer
DefaultRemTape = 40
def octal_words(s):
"""Generate a string of octal words.
s string to convert (16 bytes or less)
(must be an even number of characters)
Returns a string: 'xxxxxx xxxxxx xxxxxx ...".
"""
octs = ''
hi = s[0::2]
lo = s[1::2]
for (h, l) in zip(hi, lo):
ordh = ord(h)
ordl = ord(l)
val = ordh*256 + ordl
octs += '%06o ' % val
return octs
def str2int(self, s):
"""Convert string to numeric value.
s numeric string (decimal or octal)
Returns the numeric value.
"""
base = 10
if s[0] == '0':
base = 8
try:
value = int(s, base=base)
except:
return None
return value
def debug(debug, msg):
"""Debug message, but only if param debug is True."""
if debug:
print(msg)
def pyword(word):
"""Convert a 16bit value to a real python integer.
That is, convert 0xFFFF to -1.
"""
byte = (word >> 8) & 0xff
byte2 = word & 0xff
bstr = chr(byte) + chr(byte2)
return struct.unpack(">h", bstr)[0]
def get_byte(ptp_data, index):
"""Return the next byte from the PTP data.
ptp_data the tape data
index current index into ptp_data for next byte to read
Returns None if off the end of the tape data else a tuple (byte, index)
where 'byte' is the 8bit value and 'index' is the index of the next byte
to read from the data.
"""
try:
result = ptp_data[index]
except IndexError:
return None
return (result, index+1)
def get_word(ptp_data, index):
"""Return the next 16bit word from the PTP data.
ptp_data the tape data
index current index into ptp_data for next byte to read
Returns None if off the end of the tape data else a tuple (word, index)
where 'word' is the 16bit word value and 'index' is the index of the next
byte to read from the data.
Convert 16bit values to python integers
"""
result = get_byte(ptp_data, index)
if result is None:
return None
(first_byte, index) = result
result = get_byte(ptp_data, index)
if result is None:
return None
(second_byte, index) = result
word = (first_byte << 8) + second_byte
return (word, index)
def skipzeros(ptp_data, index):
"""Skip leading zeros in tape data.
ptp_data the tape data
index current index into ptp_data for next byte to read
Returns None if off the end of the tape data. Otherwise returns the index
of the first byte to read after the zeros.
"""
while True:
try:
val = ptp_data[index]
except IndexError:
# off end of tape
return None
if val != 0:
return index
index += 1
return None
def read_blockloader(ptp_data, index, memsize, memory=None):
"""Skip over the block loader.
ptp_data array of PTP data
index index of next byte to read in 'ptp_data'
memory memory object to be populated with the loader code
(if None, we aren't interested in the loader code)
memsize size of Imlac memory
Returns None if off the end of the tape data, else the index of the next
byte to read from 'ptp_data'.
"""
if memory is None:
memory = {}
address = memsize - LoaderSize
numwords = LoaderSize
while numwords > 0:
result = get_word(ptp_data, index)
if result is None:
return None
(data, index) = result
memory[address] = data
address += 1
numwords -= 1
return index
def c8lds_handler(ptp_data, memory, loader=False, body=False,
debugging=False, listing=False, memsize=DefaultMemSize):
"""Sees if the PTP data follows the format in "Loading the PDS-1" document.
ptp_data array of PTP data
memory memory object to be populated with body code
loader True if we put loader code into memory
body True if we put body code into memory
debugging True if we are debugging
listing True if we are to return a listing when recognized
memsize size of Imlac memory - limit addresses to this
Returns None if PTP not recognized as 'c8lds'.
Returns a tuple (None, None, out_list) if recognized. This tuple is really
(start, initialAC) but this format PTP file doesn't cater to those values.
Note that the 'memory' may have been populated in either case.
Characterized as 'c8lds' meaning an 8bit data word [C]ount, then a [L]oad
address, then [D]ata words followed by a check[S]um.
This PTP data has data blocks with the following format:
data count (8 bits)
load address (16 bits)
data word 1 (16 bits)
...
data word 'count' (16 bits)
checksum (16 bits)
Note that a data block may be preceded by a zero leader.
The example on the last page of the doc has a example tape containing:
004 000770 001061 100011 023775 037765 165054
The 'checksum' is not well defined in the documentation which says: the
checksum is the sum of all the contents modulo 077777. Yet the example tape
has a checksum of 0165054. It is assumed the doc is in error and the
checksum is the sum of all the data words incremented by one when
overflowing 177777.
A load address of 0177777 indicates the end of the load.
Note that we DO need an 'end of load' block of three bytes at the end of
the tape:
377 # number of data words (any non-zero value will do)
0177777 # a block load address of 0177777
"""
out_list = None
index = 0
if memory is None:
memory = {}
# skip initial zero leader
index = skipzeros(ptp_data, index)
if index is None:
# empty tape
debug(debugging, 'c8lds: No leader?')
return None
out_list = [' leader: %d bytes' % index]
debug(debugging, 'c8lds: after zero leader, index=%d' % index)
index_start = index
if loader:
index = read_blockloader(ptp_data, index, memsize, memory=memory)
else:
# not interested in saving blockloader code
index = read_blockloader(ptp_data, index, memsize, memory={})
if index is None:
# short block loader?
debug(debugging, 'c8lds: Short leader?')
return None
out_list.append(' loader: %d words' % ((index - index_start)/2))
debug(debugging, 'c8lds: after blockloader, index=%d' % index)
# now read data blocks
got_data_block = False
if not body:
memory = {} # save body code into unused memory
while True:
# with c8lds we can have leading zeros before a data block
index_start = index
index = skipzeros(ptp_data, index)
if index is None:
# empty tape
debug(debugging, 'c8lds: EOT looking for block?')
return None
out_list.append(' skip: %d zero bytes' % (index - index_start))
debug(debugging, 'c8lds: index before block=%d' % index)
# get data word count
index_start = index
result = get_byte(ptp_data, index)
if result is None:
# premature end of tape?
debug(debugging, 'c8lds: EOT at start of block?')
return None
(count, index) = result
debug(debugging, 'c8lds: word count=%03o' % count)
# get block load address
result = get_word(ptp_data, index)
if result is None:
# premature end of tape?
debug(debugging, 'c8lds: EOT getting load address?')
return None
(address, index) = result
debug(debugging, 'c8lds: load address=%06o' % address)
if address == 0177777:
# it's an End-Of-Tape block!
out_list.append(' start: <none>')
if not got_data_block:
# if we have no data blocks, probably NOT c8lds
return None
return (None, None, out_list)
out_list.append(' block: %d (%04o) words loaded at %06o'
% (count, count, address))
# limit addresses to Imlac memory size
address = address & (memsize-1)
debug(debugging, 'c8lds: masked load address=%06o' % address)
# read data words, store in memory and calculate checksum
got_data_block = True # we got at least one data block
checksum = 0
data_string = ''
word_count = 0
for _ in range(count):
result = get_word(ptp_data, index)
if result is None:
# premature end of tape?
debug(debugging, 'c8lds: EOT getting data word?')
return None
(data, index) = result
memory[address] = data
debug(debugging, 'c8lds: data word=%06o' % data)
data_string += '%06o ' % data
word_count += 1
if word_count > 7:
out_list.append(' %s' % data_string)
data_string = ''
word_count = 0
address += 1
checksum += data
if checksum & 0177777 != checksum:
# if overflow, increment before masking
checksum += 1
checksum = checksum & 0177777
if data_string:
out_list.append(' %s' % data_string)
# check block checksum
result = get_word(ptp_data, index)
if result is None:
# premature end of tape?
debug(debugging, 'c8lds: EOT getting checksum?')
return None
(ptp_checksum, index) = result
if ptp_checksum != checksum:
# bad checksum
debug(debugging, 'c8lds: Bad checksum? Read %06o, expected %06o.' % (ptp_checksum, checksum))
return None
# we shouldn't get here
debug(debugging, 'c8lds: Badly formed PTP file')
return None
def lc16sd_add_csum(csum, word):
"""Add 'csum' and 'word', return new checksum."""
result = (csum + word) & 0xffff
return result
def lc16sd_handler(ptp_data, memory, loader=False, body=False,
debugging=False, listing=False, memsize=DefaultMemSize):
"""Decides if PTP data is in 'lc16sd' format.
ptp_data array of PTP data
memory memory object to be populated with body code
loader True if we put loader code into memory
body True if we put body code into memory
debugging True if we are debugging
listing True if a listing is required (and this handler recognizes)
memsize size of Imlac memory - limit addresses to this
Returns None if PTP not recognized as 'lc16sd'.
Returns a tuple (start, initialAC, out_list) if recognized.
Note that the 'memory' may have been populated in either case.
Characterized as 'lc16sd' meaning [L]oad address, then a 16bit data word
[C]ount, then a 16bit check[S]um, followed by one or more [D]ata words.
This PTP data has data blocks with the following format:
one word of load address (16bits)
a word of data word count (16bits, complemented)
a checksum word (16bits)
one or more data words (16bits)
The checksum is computed such that the sum of the load address, complemented
word count, the checksum itself and all data words will be zero, modulo
0177777.
If the load address word is negative the load is finished. The load address
is the value put into the AC just before start.
"""
out_list = None
index = 0
# skip initial zero leader
index = skipzeros(ptp_data, index)
if index is None:
# empty tape
debug(debugging, 'lc16sd: No leader?')
return None
out_list = [' leader: %d bytes' % index]
index_start = index
if loader:
index = read_blockloader(ptp_data, index, memsize, memory=memory)
else:
unused_mem = {}
index = read_blockloader(ptp_data, index, memsize, memory=unused_mem)
if index is None:
# short block loader?
debug(debugging, 'lc16sd: Short blockloader?')
return None
out_list.append(' loader: %d words' % ((index - index_start)/2))
# now read data blocks
if not body:
memory = {}
while True:
# note that we CAN'T have leading zero bytes before lc16sd data blocks!
# we would skip the left-hand zero byte of a small address
# get this block load address
result = get_word(ptp_data, index)
if result is None:
debug(debugging, 'lc16sd: EOT getting load address?')
return None # premature end of tape?
(address, index) = result
if address == 00 and len(ptp_data) - index > DefaultRemTape:
# load address zero is a bad sign, probably c8lds with block zero loader
# especially with lots of PTP data left yet
return None
debug(debugging, 'lc16sd: load address=%06o' % address)
# if block load address has high bit set, we are finished
if address & 0x8000:
# address 0177777 means 'no autostart'
if address != 0177777:
# we have an autostart
result = get_word(ptp_data, index)
if result is None:
debug(debugging, 'lc16sd: EOT getting AC value for autostart?')
return None # premature end of tape?
(initAC, index) = result
out_list.append(' start: %06o initial AC %06o' % (address & (memsize-1), initAC))
return (address & (memsize-1), initAC, out_list)
out_list.append(' start: <none>')
return (None, None, out_list)
# read data block, calculating checksum
csum = address # start checksum with base address
address = address & (memsize-1) # limit loaded data to memory size
debug(debugging, 'lc16sd: masked load address=%06o' % address)
result = get_word(ptp_data, index)
if result is None:
debug(debugging, 'lc16sd: EOT getting word count?')
return None # premature end of tape?
(count, index) = result
neg_count = pyword(count)
debug(debugging, 'lc16sd: count=%06o, neg_count=%d' % (count, neg_count))
csum = lc16sd_add_csum(csum, neg_count)
out_list.append(' block: %d (%04o) words loaded at %06o'
% (-neg_count, -neg_count, address))
result = get_word(ptp_data, index)
if result is None:
debug(debugging, 'lc16sd: EOT getting checksum?')
return None # premature end of tape?
(csum_word, index) = result
debug(debugging, 'lc16sd: csum_word=%06o' % csum_word)
old_csum = csum
csum = lc16sd_add_csum(csum, csum_word)
# read body code, updating memory
data_string = ''
word_count = 0
while neg_count < 0:
result = get_word(ptp_data, index)
if result is None:
debug(debugging, 'lc16sd: EOT getting data word?')
return None # premature end of tape?
(word, index) = result
debug(debugging, 'lc16sd: data word=%06o' % word)
old_csum = csum
csum = lc16sd_add_csum(csum, word)
memory[address] = word
address += 1
neg_count += 1
data_string += '%06o ' % word
word_count += 1
if word_count > 7:
out_list.append(' %s' % data_string)
data_string = ''
word_count = 0
if data_string:
out_list.append(' %s' % data_string)
if csum != 0:
debug(debugging, 'lc16sd: Bad checksum, sum is %06o, expected 0?' % csum)
return None # bad block checksum
# on a well-formed 'lc16sd' file we shouldn't get here
debug(debugging, 'lc16sd: Badly formed file')
return None
# list of recognized loaders and handlers
Handlers = [
('lc16sd', lc16sd_handler), # lc16sd first as c8lds is very easy
('c8lds', c8lds_handler),
]
def load(filename, loader=False, body=False,
debugging=False, listing=False, memsize=DefaultMemSize):
"""Load a PTP file into a memory object after figuring out its format.
filename path of the PTP file to inspect
loader True if the blockloader code is to be loaded
body True if the body code is to be loaded
debugging True if we are debugging
listing True if we list decoded PTP file
memsize the Imlac memory size, limit addresses to this size
Returns None if there was a problem, else returns a tuple
(name, memory, start, initial_ac, list)
where the 'name' string wgich identifies the loader used,
'memory' is a memory object holding the loaded code:
{address: code, ...},
'start' is the start addres, if any
'initial_ac' is the initial contents of AC, if any
'list' is an array of strings holding the listing (or None)
"""
# get all of PTP file into memory
data = None
try:
with open(filename, 'rb') as handle:
data = handle.read()
except IOError as e:
print("Error opening '%s': %s" % (filename, e.strerror))
return None
if data is None:
print("File '%s' is empty" % filename)
return None
# convert to array of integers
ptp_data = [ord(x) for x in data]
# try loaders in order
results = []
handlers = []
for (name, handler) in Handlers:
try:
memory = {}
result = handler(ptp_data, memory, loader=loader,
body=body, debugging=debugging, listing=listing,
memsize=memsize)
debug(debugging, '%s: result=%s' % (name, str(result)))
except IndexError:
result = None
if result is not None:
(start_addr, initial_ac, out_list) = result
results.append((name, memory, start_addr, initial_ac, out_list))
handlers.append(name)
# analyse the results
debug(debugging, 'results=%s' % str(results))
if results:
if len(results) == 1:
return results[0]
else:
names = []
for (name, _, _, out_list) in results:
names.append(name)
names = ','.join(names)
return (names, {}, None, None, out_list)
return None
if __name__ == '__main__':
import sys
def usage(msg=None):
"""Print usage and optional error message."""
if msg:
print('*'*60)
print(msg)
print('*'*60)
print('Usage: loadptp [-d] [-h] [-l] [-m <memsize>] <file> [ <file> ... ]')
sys.exit(10)
if len(sys.argv) < 2:
usage()
# handle -d option
debugging = False
listing = False
memsize = DefaultMemSize
args = sys.argv[1:]
while args and args[0][0] == '-':
if args[0] == '-d':
debugging = True
args = args[1:]
elif args[0] == '-h':
usage()
elif args[0] == '-l':
listing = True
args = args[1:]
elif args[0] == '-m':
if len(args) < 1:
usage()
memsize = str2int(args[1])
args = args[2:]
else:
usage('Unrecognized option: %s' % args[0])
if len(args) < 1:
usage('Missing filename(s)')
# dump each file
for filename in args:
result = load(filename, loader=True, body=True,
debugging=debugging, listing=listing, memsize=memsize)
debug(debugging, 'top: result=%s' % str(result))
if result is not None:
(loader, memory, start, initial_ac, out_list) = result
start_str = ''
if start:
start_str = ', start address is %06o' % start
initial_ac_str = ''
if initial_ac:
initial_ac_str = ', initial AC is %06o' % initial_ac
if listing:
print(filename)
print(' type: %s' % loader)
if out_list:
l = '\n '.join(out_list)
print(' %s' % l)
else:
print('%14s: %s%s%s' % (loader, filename, start_str, initial_ac_str))
else:
print('%+14s: %s' % ('UNRECOGNIZED', filename))