#!/usr/bin/env python # -*- coding: utf-8 -*- """ this gets a stream of data more-or-less directly from the bus. If ATN or EOI changes, there'll be PREFIX escape sequences before the item. If the item is ASCII 27 (ESC), it will be encoded as "\033[e". Each item warrants a response to the C64 emulator: either 1) dot, meaning "I heard it, continue", or 2) "\033[T", which means "turn around, I want to talk". This thing being a bus, d1541 will get each and every byte that wanders on the bus. It can ignore things, however, as long as ATN is not set. ATN commands tell the wishes of the computer in that matter: 1) LISTEN means the device with the given ID and secondary address is supposed to listen to the computer. 2) UNLISTEN means the devices can stop listening now. 3) TALK means the device with the given ID and secondary address is supposed to talk to the computer. 4) UNTALK means the devices can stop talking now. In order for ATN commands not to need a complicated state machine, the control codes have marker bits (vgl. UTF-8): $20|device LISTEN $3F UNLISTEN $40|device TALK $5F UNTALK $60|channel OPEN CHANNEL/DATA $E0|channel CLOSE $F0|channel OPEN Example transmission is: /$28 /$F8 filename /$3F /$48 /$60 ... /$5F /$28 /$E0 /$3F Where / means "with ATN". Note that the drive can change its own device numbers by: print #15, "M-W" $77 $0 $2 $20|dev $40|dev """ # ESC[Q for EOI import sys import StringIO import os #sys.path = [os.path.join(os.path.dirname(sys.argv[0]), "..", "c64")] + sys.path from loaders import d64 listen_device_ID = 8 # TODO pass that in from the caller? talk_device_ID = 8 image_name = sys.argv[-1] block_accessor = d64.BlockAccessor(open(image_name, "rb")) file_accessor = d64.FileAccessor(block_accessor) directory_stream = file_accessor.open(d64.block_from_TS(18, 1)) #for entry in d64.parse_directory(directory_stream): # print(entry) C_LISTEN = 0x20 C_OPEN = 0xF0 C_OPEN_DATA = 0x60 C_UNLISTEN = 0x3F C_TALK = 0x40 C_UNTALK = 0x5F C_CLOSE = 0xE0 B_ATN = False B_EOI = False errors = { 0: "OK", 1: "FILES SCRATCHED", 3: "UNIMPLEMENTED", 20: "READ ERROR", 21: "READ ERROR", 22: "READ ERROR", 23: "READ ERROR", 24: "READ ERROR", 25: "WRITE ERROR", 26: "WRITE PROTECT ON", 27: "READ ERROR", 28: "WRITE ERROR", 29: "DISK ID MISMATCH", 30: "SYNTAX ERROR", 31: "SYNTAX ERROR", 32: "SYNTAX ERROR", 33: "SYNTAX ERROR", 34: "SYNTAX ERROR", 60: "WRITE FILE OPEN", 61: "FILE NOT OPEN", 62: "FILE NOT FOUND", 63: "FILE EXISTS", 64: "FILE TYPE MISMATCH", 65: "NO BLOCK", 66: "ILLEGAL TRACK OR SECTOR", 70: "NO CHANNEL", 71: "DIR ERROR", 72: "DISK FULL", 73: "CBM DOS V2.6 1541", 74: "DRIVE NOT READY", } # ,%02d,%02d\x0D def parse_input(): while True: chunk = os.read(0, 1) if chunk == "": break if chunk == chr(27): # ESC brace = os.read(0, 1) if brace != "[": sys.exit(1) arg = os.read(0, 1) if arg == "e": command = arg arg = None else: command = os.read(0, 1) if command == "e": item = chr(27) yield item elif command == "A": B_ATN = arg == "1" yield "\033[%dA" % (1 if B_ATN else 0) elif command == "E": B_EOI = arg == "1" yield "\033[%dE" % (1 if B_EOI else 0) else: item = chunk yield item def turnaround(): sys.stdout.write("\033[T") sys.stdout.flush() def stay(): sys.stdout.write(".") sys.stdout.flush() def transmit_EOF(): sys.stdout.write("\033[Q") def transmit(text): # use once per session sys.stdout.write(text[:-1]) transmit_EOF() sys.stdout.write(text[-1]) sys.stdout.flush() turnaround() class Channel(object): def __init__(self): self.buffer = StringIO.StringIO() self.data_offset = 0 def process(self, item): #print >>sys.stderr, "ITEM PROC", item self.buffer.write(item) def open_data(self): self.data_offset = self.buffer.tell() self.prepare_data() def prepare_data(self): name = self.buffer.getvalue() #self.buffer = StringIO.StringIO() #self.data_offset = 0 print >>sys.stderr, "dum dum dum DUM", name # TODO wait for turnaround """ pointer to next line (2 bytes) - 0 for none line number (2 bytes) $99 poke for the print keyword space and quote characters as literals petascii for the text quote character literal 0 at the end of the line tokens: 128/$80 END 129/$81 FOR 130/$82 NEXT 131/$83 DATA 132/$84 INPUT# 133/$85 INPUT 134/$86 DIM 135/$87 READ 136/$88 LET 137/$89 GOTO 138/$8A RUN 139/$8B IF 140/$8C RESTORE 141/$8D GOSUB 142/$8E RETURN 143/$8F REM 144/$90 STOP 145/$91 ON 146/$92 WAIT 147/$93 LOAD 148/$94 SAVE 149/$95 VERIFY 150/$96 DEF 151/$97 POKE 152/$98 PRINT# 153/$99 PRINT 154/$9A CONT 155/$9B LIST 156/$9C CLR 157/$9D CMD 158/$9E SYS 159/$9F OPEN 160/$A0 CLOSE 161/$A1 GET 162/$A2 NEW 163/$A3 TAB( 164/$A4 TO 165/$A5 FN 166/$A6 SPC( 167/$A7 THEN 168/$A8 NOT 169/$A9 STEP 170/$AA + (Addition) 171/$AB − (Subtraction) 172/$AC * (Multiplication) 173/$AD / (Division) 174/$AE ^ (Power) 175/$AF AND 176/$B0 OR 177/$B1 < (less-than operator) 178/$B2 = (equals operator) 179/$B3 > (greater-than operator) 180/$B4 SGN 181/$B5 INT 182/$B6 ABS 183/$B7 USR 184/$B8 FRE 185/$B9 POS 186/$BA SQR 187/$BB RND 188/$BC LOG 189/$BD EXP 190/$BE COS 191/$BF SIN 192/$C0 TAN 193/$C1 ATN 194/$C2 PEEK 195/$C3 LEN 196/$C4 STR$ 197/$C5 VAL 198/$C6 ASC 199/$C7 CHR$ 200/$C8 LEFT$ 201/$C9 RIGHT$ 202/$CA MID$ 203/$CB GO """ # I'm not sure what the first byte is, the C64 doesn't get it. Bug somewhere. # The other file header is two bytes for the load address (should be $801). Ignored sometimes. transmit("\x00\x01\x08\x0D\x08\x0A\x00\x99\x20\x22\x20\x20\x20\x22\x00\x00\x00\x00\x00") # trailing junk (2) # 801 802 803 804 805 806 807 808 809 80A 80B 80C 80D def close(self): pass def process_command(self): pass def open_channel(secondary_number): channel = Channel() return channel #f = open("/tmp/LOG", "wb") B_ATN = False B_EOI = False NONE_MODE, LISTENING_MODE, TALKING_MODE = range(3) mode = NONE_MODE channel_index = None channels = {} channels[15] = open_channel(15) #channels[0] = open_channel(0) for item in parse_input(): #f.write(item) #f.flush() if len(item) > 1: # ATN or EOI change. if item == "\033[1A": B_ATN = True elif item == "\033[0A": B_ATN = False elif item == "\033[1E": B_EOI = True elif item == "\033[0E": B_EOI = False else: if B_ATN: if item == chr(C_LISTEN | listen_device_ID): mode = LISTENING_MODE elif (ord(item) & 0xF0) == C_OPEN: ix = ord(item) & 0xF channels[ix] = open_channel(ix) channel_index = ix elif (ord(item) & 0xF0) == C_CLOSE: ix = ord(item) & 0xF if ix in channels: channels[ix].close() del channels[ix] if channel_index == ix: channel_index = None elif (ord(item) & 0xF0) == C_OPEN_DATA: ix = ord(item) & 0xF channel_index = ix channels[channel_index].open_data() elif item == chr(C_UNLISTEN): if channel_index is not None: channels[channel_index].process_command() # TODO is it correct that this also does UNTALK implicitly? mode = NONE_MODE elif item == chr(C_TALK | talk_device_ID): mode = TALKING_MODE # TODO notify channel? elif item == chr(C_UNTALK): # TODO is it correct that this also does UNTALK implicitly? mode = NONE_MODE # TODO notify channel? else: if channel_index is not None: channels[channel_index].process(item) else: print >>sys.stderr, "D1541: warning: C64 is saying something to the wall" #print >>sys.stderr, "item %r" % (item, ) #stay() # FIXME remove #stay() # FIXME remove #print(hex(ord(item))), #if ord(item) == 0x60: # FIXME # turnaround() #else: stay()