Jens Gaulke
2023-03-24 22:21:50 UTC
Hi there,
I tried to do Disk II emulation and implemented all softswitches and the functions as far as I understood them from Jim Sather. The only disk image start starts for me is "Apple Galaxian". A standard DOS 3.3 System Master reads some tracks and then crashes with an illegal opcode. What am I missing? Do I have some timing problems in my emulation? Did I not correctly implement the softswitch functions?
I attached my code (I hope the formatting will not be corrupted) and would be really glad to get an answer ...
import random
import pygame
from dataclasses import dataclass
import threading
import math
import re
@dataclass
class DRIVE_STATE:
filename: str
half_track: int
prev_half_track: int
write_mode: bool
current_phase: int
disk_has_changes: bool
motor_running: bool
track_start: list
track_nbits: list
track_location: int
is_write_protected: bool
disk_data: bytearray
status: str
firmware = [0xA2,0x20,0xA0,0x00,0xA2,0x03,0x86,0x3C,0x8A,0x0A,0x24,0x3C,0xF0,0x10,0x05,0x3C,
0x49,0xFF,0x29,0x7E,0xB0,0x08,0x4A,0xD0,0xFB,0x98,0x9D,0x56,0x03,0xC8,0xE8,0x10,
0xE5,0x20,0x58,0xFF,0xBA,0xBD,0x00,0x01,0x0A,0x0A,0x0A,0x0A,0x85,0x2B,0xAA,0xBD,
0x8E,0xC0,0xBD,0x8C,0xC0,0xBD,0x8A,0xC0,0xBD,0x89,0xC0,0xA0,0x50,0xBD,0x80,0xC0,
0x98,0x29,0x03,0x0A,0x05,0x2B,0xAA,0xBD,0x81,0xC0,0xA9,0x56,0xa9,0x00,0xea,0x88,
0x10,0xEB,0x85,0x26,0x85,0x3D,0x85,0x41,0xA9,0x08,0x85,0x27,0x18,0x08,0xBD,0x8C,
0xC0,0x10,0xFB,0x49,0xD5,0xD0,0xF7,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0xAA,0xD0,0xF3,
0xEA,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0x96,0xF0,0x09,0x28,0x90,0xDF,0x49,0xAD,0xF0,
0x25,0xD0,0xD9,0xA0,0x03,0x85,0x40,0xBD,0x8C,0xC0,0x10,0xFB,0x2A,0x85,0x3C,0xBD,
0x8C,0xC0,0x10,0xFB,0x25,0x3C,0x88,0xD0,0xEC,0x28,0xC5,0x3D,0xD0,0xBE,0xA5,0x40,
0xC5,0x41,0xD0,0xB8,0xB0,0xB7,0xA0,0x56,0x84,0x3C,0xBC,0x8C,0xC0,0x10,0xFB,0x59,
0xD6,0x02,0xA4,0x3C,0x88,0x99,0x00,0x03,0xD0,0xEE,0x84,0x3C,0xBC,0x8C,0xC0,0x10,
0xFB,0x59,0xD6,0x02,0xA4,0x3C,0x91,0x26,0xC8,0xD0,0xEF,0xBC,0x8C,0xC0,0x10,0xFB,
0x59,0xD6,0x02,0xD0,0x87,0xA0,0x00,0xA2,0x56,0xCA,0x30,0xFB,0xB1,0x26,0x5E,0x00,
0x03,0x2A,0x5E,0x00,0x03,0x2A,0x91,0x26,0xC8,0xD0,0xEE,0xE6,0x27,0xE6,0x3D,0xA5,
0x3D,0xCD,0x00,0x08,0xA6,0x2B,0x90,0xDB,0x4C,0x01,0x08,0x00,0x00,0x00,0x00,0x00]
DEFAULT_VOLUME = 254
NUM_DRIVES = 2
DOS_NUM_SECTORS = 16
DOS_NUM_TRACKS = 35
DOS_TRACK_BYTES = 256 * DOS_NUM_SECTORS
RAW_TRACK_BYTES = 0x1A00
STANDARD_2IMG_HEADER_SIZE = 64
STANDARD_PRODOS_BLOCKS = 280
# diskdata = [0,1][0,35][1a00]
pickbit = [128, 64, 32, 16, 8, 4, 2, 1]
clearbit = [0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b1111101, 0b11111110]
class FloppyDisk:
def __init__(self, myApple, slot) -> None:
self.drive = 0
self.is_motor_on = False
self.is_write_protected = [0,1]
#self.disk_data = [[0 for i in range(2*DOS_NUM_TRACKS)], [0 for i in range(2*DOS_NUM_TRACKS)]]
self.curr_phys_track = 0
self.curr_nibble = 0
self.drive_curr_phys_track = [0,0]
self.real_track = [0 for i in range(RAW_TRACK_BYTES+1)]
self.latch_data = 0
self.write_mode = False
self.load_mode = False
self.drive_spin = False
self.gcr_nibble_pos = 0
self.gcr_nibbles = []
pygame.mixer.pre_init(frequency=44100, size=-16, channels=1, allowedchanges=0)
pygame.init()
pygame.mixer.init(frequency=44100, size=-16, channels=1, allowedchanges=0)
self.motor_sound = pygame.mixer.Sound(f'./src_audio_driveMotor.mp3')
self.track_off_end = pygame.mixer.Sound(f'./src_audio_driveTrackOffEnd.mp3')
self.track_seek = pygame.mixer.Sound(f'./src_audio_driveTrackSeek.mp3')
self.setup_firmware(myApple, slot)
self.drive_state = [DRIVE_STATE('', 0, 0, False, 0, False, False, [None]*80, [None]*80, 0, False, self.load_wozdisk(), ""),
DRIVE_STATE('', 0, 0, False, 0, False, False, [None]*80, [None]*80, 0, False, self.load_wozdisk(), "")]
self.reset_drives()
self.disk_data = [self.load_wozdisk(), self.load_wozdisk()]
#arraypart = self.disk_data[0][0:12]
#print (' '.join('{:02x}'.format(x) for x in arraypart))
self.init_diskstates(0,'Dung Beetles.woz')
self.init_diskstates(1,'DOS_3.3_System_Master.woz')
self.SLOT = slot << 4
self.DRVPH0_OFF = 0xC080 + self.SLOT
self.DRVPH0_ON = self.DRVPH0_OFF + 1
self.DRVPH1_OFF = 0xC082 + self.SLOT
self.DRVPH1_ON = self.DRVPH1_OFF + 1
self.DRVPH2_OFF = 0xC084 + self.SLOT
self.DRVPH2_ON = self.DRVPH2_OFF + 1
self.DRVPH3_OFF = 0xC086 + self.SLOT
self.DRVPH3_ON = self.DRVPH3_OFF + 1
self.DRVMOTOR_OFF = 0xC088 + self.SLOT
self.DRVMOTOR_ON = self.DRVMOTOR_OFF + 1
self.DRVSEL_0 = 0xC08A + self.SLOT
self.DRVSEL_1 = self.DRVSEL_0 + 1
self.DRVDATA_SHIFT = 0xC08C + self.SLOT
self.DRVDATA_LOAD = self.DRVDATA_SHIFT + 1
self.DRVREAD = 0xC08E + self.SLOT
self.DRVWRITE = 0xC08F + self.SLOT
self.DRVPH0_isSet = False
self.DRVPH1_isSet = False
self.DRVPH2_isSet = False
self.DRVPH3_isSet = False
self.DRVMOTOR_isSet = False
self.DRVDATA_isSet = False
self.DRVREAD_isSet = False
self.current_drive = 0
self.prev_cycle = 0
self.data_register = 0
self.sm = None
def setup_firmware(self, myApple, slot):
for i in range(0x100):
myApple.memory.ram.write_byte(0xC000 + (slot << 8) + i, firmware[i])
def load_wozdisk(self):
filename = 'DOS_3.3_System_Master.woz'
#filename = 'Dung Beetles.woz'
#filename = 'Apple DOS 3.3 August 1980.woz'
#filename = 'Choplifter.dsk'
#filename = 'Apple_Galaxian.woz'
diskdata = []
with open(filename, "rb") as f:
diskdata = bytearray(f.read())
return diskdata
def reset_drives(self):
#self.stop_motor(0)
#self.stop_motor(1)
self.drive_state[0].half_track = 68
self.drive_state[1].half_track = 68
self.drive_state[0].prev_half_track = 68
self.drive_state[1].prev_half_track = 68
def is_dsk(self, filename):
isdsk = filename.endswith(".dsk") or filename.endswith(".do")
ispo = filename.endswith(".po")
return isdsk | ispo
def decode_woz2(self, drivestate, filename):
disk_data = drivestate.disk_data
# Überprüfen, ob die Diskette den richtigen Header hat
woz2 = [0x57, 0x4F, 0x5A, 0x32, 0xFF, 0x0A, 0x0D, 0x0A]
found = True
for i in range (len(woz2)):
if woz2[i] != disk_data[i]:
found = False
if not found:
return
if disk_data[22] == 1:
drivestate.is_write_protected = True
else:
drivestate.is_write_protected = False
# checksum überprüfung fehlt noch
# WOZ 2
for htrack in range(80):
tmap_index = disk_data[88 + htrack*2]
if tmap_index < 255:
tmap_offset = 256 + 8*tmap_index
trk = disk_data[tmap_offset:tmap_offset + 8]
drivestate.track_start[htrack] = 512 * (trk[0] + (trk[1] << 8))
drivestate.track_nbits[htrack] = trk[4] + (trk[5] << 8) + (trk[6] << 16) + (trk[7] << 24)
else:
drivestate.track_start[htrack] = 0
drivestate.track_nbits[htrack] = 51200
return True
def decode_woz1(self, drivestate, filename):
disk_data = drivestate.disk_data
# Überprüfen, ob die Diskette den richtigen Header hat
woz1 = [0x57, 0x4F, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A]
found = True
for i in range (len(woz1)):
if woz1[i] != disk_data[i]:
found = False
if not found:
return
if disk_data[22] == 1:
drivestate.is_write_protected = True
else:
drivestate.is_write_protected = False
# checksum überprüfung fehlt noch
# WOZ 1
for htrack in range(80):
tmap_index = disk_data[88 + htrack*2]
if tmap_index < 255:
drivestate.track_start[htrack] = 256 + tmap_index * 6656
trk = disk_data[(drivestate.track_start[htrack]+6646):(drivestate.track_start[htrack]+6656)]
drivestate.track_nbits[htrack] = trk[2] + (trk[3] << 8)
else:
drivestate.track_start[htrack] = 0
drivestate.track_nbits[htrack] = 51200
return True
def decode_dsk(self, drivestate, filename):
pass
def decode_diskdata(self, drivenum, filename):
self.drive_state[drivenum].filename = filename
if self.is_dsk(filename):
print("dsk image")
self.decode_dsk(self.drive_state[drivenum], filename)
if self.decode_woz2(self.drive_state[drivenum], filename):
print("woz2 image")
if self.decode_woz1(self.drive_state[drivenum], filename):
print("woz1 image")
def init_diskstates(self, drivenum, filename):
self.decode_diskdata(drivenum, filename)
def set_switches(self, address):
if address == self.DRVPH0_OFF:
self.DRVPH0_isSet = False
if address == self.DRVPH0_ON:
self.DRVPH0_isSet = True
if address == self.DRVPH1_OFF:
self.DRVPH1_isSet = False
if address == self.DRVPH1_ON:
self.DRVPH1_isSet = True
if address == self.DRVPH2_OFF:
self.DRVPH2_isSet = False
if address == self.DRVPH2_ON:
self.DRVPH2_isSet = True
if address == self.DRVPH3_OFF:
self.DRVPH3_isSet = False
if address == self.DRVPH3_ON:
self.DRVPH3_isSet = True
if address == self.DRVMOTOR_OFF:
self.DRVMOTOR_isSet = False
if address == self.DRVMOTOR_ON:
self.DRVMOTOR_isSet = True
if address == self.DRVDATA_SHIFT:
self.DRVDATA_isSet = False
if address == self.DRVDATA_LOAD:
self.DRVDATA_isSet = True
if address == self.DRVREAD:
self.DRVREAD_isSet = False
if address == self.DRVWRITE:
self.DRVREAD_isSet = True
def softswitches(self, address, value, cycle):
self.set_switches(address)
disk_drive = self.drive_state[self.current_drive]
delta = cycle - self.prev_cycle
result = 0
# Phasenmotoren
a = address - self.DRVPH0_OFF
phases = [self.DRVPH0_OFF, self.DRVPH1_OFF, self.DRVPH2_OFF, self.DRVPH3_OFF]
phases_isset = [self.DRVPH0_isSet, self.DRVPH1_isSet, self.DRVPH2_isSet, self.DRVPH3_isSet]
if a >= 0 and a <= 7:
ascend = phases_isset[(disk_drive.current_phase + 1) % 4]
descend = phases_isset[(disk_drive.current_phase + 3) % 4]
# Abfrage, ob der phasenmotor aus ist. wie? Wo wird der softswitch gesetzt?
if not phases_isset[disk_drive.current_phase]:
if disk_drive.motor_running:
if ascend:
#if ascend == address:
self.move_head(disk_drive,1)
disk_drive.current_phase = (disk_drive.current_phase + 1) % 4
#print("ascending")
elif descend:
#elif descend == address:
self.move_head(disk_drive,-1)
disk_drive.current_phase = (disk_drive.current_phase + 3) % 4
#print("descending")
# 0xC088
elif address == self.DRVMOTOR_OFF:
#print("Stop Motor:",self.current_drive)
self.stop_motor(disk_drive, self.current_drive)
return result
# 0xC089
elif address == self.DRVMOTOR_ON:
#print("Start Motor:",self.current_drive)
self.start_motor(disk_drive, self.current_drive)
return result
# 0xC08A && 0xC08B
elif address == self.DRVSEL_1 or address == self.DRVSEL_0:
self.current_drive = 0 if address == self.DRVSEL_0 else 1
if self.drive_state[1 - self.current_drive].motor_running:
self.drive_state[1 - self.current_drive].motor_running = False
self.drive_state[self.current_drive].motor_running = True
return result
# 0xC08C SHIFT
elif address == self.DRVDATA_SHIFT:
if disk_drive.motor_running and not disk_drive.write_mode:
return self.get_next_byte(disk_drive)
# 0xC08D LOAD/READ
elif address == self.DRVDATA_LOAD:
if disk_drive.motor_running:
if disk_drive.write_mode:
self.do_write_byte(delta, disk_drive)
self.prev_cycle = cycle
if value >= 0:
self.data_register = value
# 0xC08E READ
elif address == self.DRVREAD:
if disk_drive.motor_running and disk_drive.write_mode:
self.do_write_byte(delta,disk_drive)
self.prev_cycle = cycle
disk_drive.write_mode = False
# if self.DRVDATA.isset:
# result = disk_drive.iswriteprotected ? 0xff : 0
result = 255
# 0xC08F WRITE
elif address == self.DRVWRITE:
disk_drive.write_mode = True
self.prev_cycle = cycle
if value >= 0:
self.data_register = value
return result
def debug_data(self, address):
pass
def move_head(self, disk_drive, direction):
if disk_drive.track_start[disk_drive.half_track] > 0:
disk_drive.prev_half_track = disk_drive.half_track
disk_drive.half_track += direction
if disk_drive.half_track < 0 or disk_drive.half_track > 68:
# sound: drive.trackend
if disk_drive.half_track < 0:
disk_drive.half_track = 0
if disk_drive.half_track > 68:
disk_drive.half_track = 68
else:
# sound: drive.trackseek
pass
disk_drive.status = str(disk_drive.half_track // 2)
if disk_drive.track_start[disk_drive.half_track] > 0 and disk_drive.prev_half_track != disk_drive.half_track:
disk_drive.track_location = math.floor(disk_drive.track_location * (disk_drive.track_nbits[disk_drive.half_track] / disk_drive.track_nbits[disk_drive.prev_half_track]))
if disk_drive.track_location > 3:
disk_drive.track_location -= 4
#print("Drive track:", disk_drive.half_track //2)
def do_write_byte(self, delta):
print("do write byte")
return
def do_nothing(self):
for i in range(10):
i=i+1
#print("NOP")
pass
def get_next_bit(self, disk_drive):
disk_drive.track_location = disk_drive.track_location % disk_drive.track_nbits[disk_drive.half_track]
bit = 0
if disk_drive.track_start[disk_drive.half_track] > 0:
file_offset = disk_drive.track_start[disk_drive.half_track] + (disk_drive.track_location >> 3)
byte = disk_drive.disk_data[file_offset]
b = disk_drive.track_location & 7
bit = (byte & pickbit[b]) >> (7-b)
else:
bit = 1
disk_drive.track_location += 1
return bit
def get_next_byte(self, disk_drive):
if len(disk_drive.disk_data) == 0 or disk_drive.half_track % 2 != 0:
print("No Disk")
return 0
result = 0
if self.data_register == 0:
while(self.get_next_bit(disk_drive) == 0):
self.do_nothing()
self.data_register = 0x40
for i in range(5,-1,-1):
# print("data register:", i)
self.data_register |= self.get_next_bit(disk_drive) << i
else:
bit = self.get_next_bit(disk_drive)
self.data_register = (self.data_register << 1) | bit
result = self.data_register
if (self.data_register > 127):
self.data_register = 0
print("get next byte:", hex(result), ' ', disk_drive.track_location,' ', disk_drive.half_track)
return result
def start_motor(self, disk_drive, current_drive):
disk_drive.motor_running = True
if self.sm:
self.sm.cancel()
pygame.mixer.Channel((current_drive+1)*2-1).play(self.motor_sound)
def stop_motor(self, disk_drive, current_drive):
self.sm = threading.Timer(1,self.stopit,[disk_drive, current_drive])
self.sm.start()
def stopit(self, disk_drive, current_drive):
disk_drive.motor_running = False
pygame.mixer.Channel((current_drive+1)*2-1).stop()
I tried to do Disk II emulation and implemented all softswitches and the functions as far as I understood them from Jim Sather. The only disk image start starts for me is "Apple Galaxian". A standard DOS 3.3 System Master reads some tracks and then crashes with an illegal opcode. What am I missing? Do I have some timing problems in my emulation? Did I not correctly implement the softswitch functions?
I attached my code (I hope the formatting will not be corrupted) and would be really glad to get an answer ...
import random
import pygame
from dataclasses import dataclass
import threading
import math
import re
@dataclass
class DRIVE_STATE:
filename: str
half_track: int
prev_half_track: int
write_mode: bool
current_phase: int
disk_has_changes: bool
motor_running: bool
track_start: list
track_nbits: list
track_location: int
is_write_protected: bool
disk_data: bytearray
status: str
firmware = [0xA2,0x20,0xA0,0x00,0xA2,0x03,0x86,0x3C,0x8A,0x0A,0x24,0x3C,0xF0,0x10,0x05,0x3C,
0x49,0xFF,0x29,0x7E,0xB0,0x08,0x4A,0xD0,0xFB,0x98,0x9D,0x56,0x03,0xC8,0xE8,0x10,
0xE5,0x20,0x58,0xFF,0xBA,0xBD,0x00,0x01,0x0A,0x0A,0x0A,0x0A,0x85,0x2B,0xAA,0xBD,
0x8E,0xC0,0xBD,0x8C,0xC0,0xBD,0x8A,0xC0,0xBD,0x89,0xC0,0xA0,0x50,0xBD,0x80,0xC0,
0x98,0x29,0x03,0x0A,0x05,0x2B,0xAA,0xBD,0x81,0xC0,0xA9,0x56,0xa9,0x00,0xea,0x88,
0x10,0xEB,0x85,0x26,0x85,0x3D,0x85,0x41,0xA9,0x08,0x85,0x27,0x18,0x08,0xBD,0x8C,
0xC0,0x10,0xFB,0x49,0xD5,0xD0,0xF7,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0xAA,0xD0,0xF3,
0xEA,0xBD,0x8C,0xC0,0x10,0xFB,0xC9,0x96,0xF0,0x09,0x28,0x90,0xDF,0x49,0xAD,0xF0,
0x25,0xD0,0xD9,0xA0,0x03,0x85,0x40,0xBD,0x8C,0xC0,0x10,0xFB,0x2A,0x85,0x3C,0xBD,
0x8C,0xC0,0x10,0xFB,0x25,0x3C,0x88,0xD0,0xEC,0x28,0xC5,0x3D,0xD0,0xBE,0xA5,0x40,
0xC5,0x41,0xD0,0xB8,0xB0,0xB7,0xA0,0x56,0x84,0x3C,0xBC,0x8C,0xC0,0x10,0xFB,0x59,
0xD6,0x02,0xA4,0x3C,0x88,0x99,0x00,0x03,0xD0,0xEE,0x84,0x3C,0xBC,0x8C,0xC0,0x10,
0xFB,0x59,0xD6,0x02,0xA4,0x3C,0x91,0x26,0xC8,0xD0,0xEF,0xBC,0x8C,0xC0,0x10,0xFB,
0x59,0xD6,0x02,0xD0,0x87,0xA0,0x00,0xA2,0x56,0xCA,0x30,0xFB,0xB1,0x26,0x5E,0x00,
0x03,0x2A,0x5E,0x00,0x03,0x2A,0x91,0x26,0xC8,0xD0,0xEE,0xE6,0x27,0xE6,0x3D,0xA5,
0x3D,0xCD,0x00,0x08,0xA6,0x2B,0x90,0xDB,0x4C,0x01,0x08,0x00,0x00,0x00,0x00,0x00]
DEFAULT_VOLUME = 254
NUM_DRIVES = 2
DOS_NUM_SECTORS = 16
DOS_NUM_TRACKS = 35
DOS_TRACK_BYTES = 256 * DOS_NUM_SECTORS
RAW_TRACK_BYTES = 0x1A00
STANDARD_2IMG_HEADER_SIZE = 64
STANDARD_PRODOS_BLOCKS = 280
# diskdata = [0,1][0,35][1a00]
pickbit = [128, 64, 32, 16, 8, 4, 2, 1]
clearbit = [0b01111111, 0b10111111, 0b11011111, 0b11101111, 0b11110111, 0b11111011, 0b1111101, 0b11111110]
class FloppyDisk:
def __init__(self, myApple, slot) -> None:
self.drive = 0
self.is_motor_on = False
self.is_write_protected = [0,1]
#self.disk_data = [[0 for i in range(2*DOS_NUM_TRACKS)], [0 for i in range(2*DOS_NUM_TRACKS)]]
self.curr_phys_track = 0
self.curr_nibble = 0
self.drive_curr_phys_track = [0,0]
self.real_track = [0 for i in range(RAW_TRACK_BYTES+1)]
self.latch_data = 0
self.write_mode = False
self.load_mode = False
self.drive_spin = False
self.gcr_nibble_pos = 0
self.gcr_nibbles = []
pygame.mixer.pre_init(frequency=44100, size=-16, channels=1, allowedchanges=0)
pygame.init()
pygame.mixer.init(frequency=44100, size=-16, channels=1, allowedchanges=0)
self.motor_sound = pygame.mixer.Sound(f'./src_audio_driveMotor.mp3')
self.track_off_end = pygame.mixer.Sound(f'./src_audio_driveTrackOffEnd.mp3')
self.track_seek = pygame.mixer.Sound(f'./src_audio_driveTrackSeek.mp3')
self.setup_firmware(myApple, slot)
self.drive_state = [DRIVE_STATE('', 0, 0, False, 0, False, False, [None]*80, [None]*80, 0, False, self.load_wozdisk(), ""),
DRIVE_STATE('', 0, 0, False, 0, False, False, [None]*80, [None]*80, 0, False, self.load_wozdisk(), "")]
self.reset_drives()
self.disk_data = [self.load_wozdisk(), self.load_wozdisk()]
#arraypart = self.disk_data[0][0:12]
#print (' '.join('{:02x}'.format(x) for x in arraypart))
self.init_diskstates(0,'Dung Beetles.woz')
self.init_diskstates(1,'DOS_3.3_System_Master.woz')
self.SLOT = slot << 4
self.DRVPH0_OFF = 0xC080 + self.SLOT
self.DRVPH0_ON = self.DRVPH0_OFF + 1
self.DRVPH1_OFF = 0xC082 + self.SLOT
self.DRVPH1_ON = self.DRVPH1_OFF + 1
self.DRVPH2_OFF = 0xC084 + self.SLOT
self.DRVPH2_ON = self.DRVPH2_OFF + 1
self.DRVPH3_OFF = 0xC086 + self.SLOT
self.DRVPH3_ON = self.DRVPH3_OFF + 1
self.DRVMOTOR_OFF = 0xC088 + self.SLOT
self.DRVMOTOR_ON = self.DRVMOTOR_OFF + 1
self.DRVSEL_0 = 0xC08A + self.SLOT
self.DRVSEL_1 = self.DRVSEL_0 + 1
self.DRVDATA_SHIFT = 0xC08C + self.SLOT
self.DRVDATA_LOAD = self.DRVDATA_SHIFT + 1
self.DRVREAD = 0xC08E + self.SLOT
self.DRVWRITE = 0xC08F + self.SLOT
self.DRVPH0_isSet = False
self.DRVPH1_isSet = False
self.DRVPH2_isSet = False
self.DRVPH3_isSet = False
self.DRVMOTOR_isSet = False
self.DRVDATA_isSet = False
self.DRVREAD_isSet = False
self.current_drive = 0
self.prev_cycle = 0
self.data_register = 0
self.sm = None
def setup_firmware(self, myApple, slot):
for i in range(0x100):
myApple.memory.ram.write_byte(0xC000 + (slot << 8) + i, firmware[i])
def load_wozdisk(self):
filename = 'DOS_3.3_System_Master.woz'
#filename = 'Dung Beetles.woz'
#filename = 'Apple DOS 3.3 August 1980.woz'
#filename = 'Choplifter.dsk'
#filename = 'Apple_Galaxian.woz'
diskdata = []
with open(filename, "rb") as f:
diskdata = bytearray(f.read())
return diskdata
def reset_drives(self):
#self.stop_motor(0)
#self.stop_motor(1)
self.drive_state[0].half_track = 68
self.drive_state[1].half_track = 68
self.drive_state[0].prev_half_track = 68
self.drive_state[1].prev_half_track = 68
def is_dsk(self, filename):
isdsk = filename.endswith(".dsk") or filename.endswith(".do")
ispo = filename.endswith(".po")
return isdsk | ispo
def decode_woz2(self, drivestate, filename):
disk_data = drivestate.disk_data
# Überprüfen, ob die Diskette den richtigen Header hat
woz2 = [0x57, 0x4F, 0x5A, 0x32, 0xFF, 0x0A, 0x0D, 0x0A]
found = True
for i in range (len(woz2)):
if woz2[i] != disk_data[i]:
found = False
if not found:
return
if disk_data[22] == 1:
drivestate.is_write_protected = True
else:
drivestate.is_write_protected = False
# checksum überprüfung fehlt noch
# WOZ 2
for htrack in range(80):
tmap_index = disk_data[88 + htrack*2]
if tmap_index < 255:
tmap_offset = 256 + 8*tmap_index
trk = disk_data[tmap_offset:tmap_offset + 8]
drivestate.track_start[htrack] = 512 * (trk[0] + (trk[1] << 8))
drivestate.track_nbits[htrack] = trk[4] + (trk[5] << 8) + (trk[6] << 16) + (trk[7] << 24)
else:
drivestate.track_start[htrack] = 0
drivestate.track_nbits[htrack] = 51200
return True
def decode_woz1(self, drivestate, filename):
disk_data = drivestate.disk_data
# Überprüfen, ob die Diskette den richtigen Header hat
woz1 = [0x57, 0x4F, 0x5A, 0x31, 0xFF, 0x0A, 0x0D, 0x0A]
found = True
for i in range (len(woz1)):
if woz1[i] != disk_data[i]:
found = False
if not found:
return
if disk_data[22] == 1:
drivestate.is_write_protected = True
else:
drivestate.is_write_protected = False
# checksum überprüfung fehlt noch
# WOZ 1
for htrack in range(80):
tmap_index = disk_data[88 + htrack*2]
if tmap_index < 255:
drivestate.track_start[htrack] = 256 + tmap_index * 6656
trk = disk_data[(drivestate.track_start[htrack]+6646):(drivestate.track_start[htrack]+6656)]
drivestate.track_nbits[htrack] = trk[2] + (trk[3] << 8)
else:
drivestate.track_start[htrack] = 0
drivestate.track_nbits[htrack] = 51200
return True
def decode_dsk(self, drivestate, filename):
pass
def decode_diskdata(self, drivenum, filename):
self.drive_state[drivenum].filename = filename
if self.is_dsk(filename):
print("dsk image")
self.decode_dsk(self.drive_state[drivenum], filename)
if self.decode_woz2(self.drive_state[drivenum], filename):
print("woz2 image")
if self.decode_woz1(self.drive_state[drivenum], filename):
print("woz1 image")
def init_diskstates(self, drivenum, filename):
self.decode_diskdata(drivenum, filename)
def set_switches(self, address):
if address == self.DRVPH0_OFF:
self.DRVPH0_isSet = False
if address == self.DRVPH0_ON:
self.DRVPH0_isSet = True
if address == self.DRVPH1_OFF:
self.DRVPH1_isSet = False
if address == self.DRVPH1_ON:
self.DRVPH1_isSet = True
if address == self.DRVPH2_OFF:
self.DRVPH2_isSet = False
if address == self.DRVPH2_ON:
self.DRVPH2_isSet = True
if address == self.DRVPH3_OFF:
self.DRVPH3_isSet = False
if address == self.DRVPH3_ON:
self.DRVPH3_isSet = True
if address == self.DRVMOTOR_OFF:
self.DRVMOTOR_isSet = False
if address == self.DRVMOTOR_ON:
self.DRVMOTOR_isSet = True
if address == self.DRVDATA_SHIFT:
self.DRVDATA_isSet = False
if address == self.DRVDATA_LOAD:
self.DRVDATA_isSet = True
if address == self.DRVREAD:
self.DRVREAD_isSet = False
if address == self.DRVWRITE:
self.DRVREAD_isSet = True
def softswitches(self, address, value, cycle):
self.set_switches(address)
disk_drive = self.drive_state[self.current_drive]
delta = cycle - self.prev_cycle
result = 0
# Phasenmotoren
a = address - self.DRVPH0_OFF
phases = [self.DRVPH0_OFF, self.DRVPH1_OFF, self.DRVPH2_OFF, self.DRVPH3_OFF]
phases_isset = [self.DRVPH0_isSet, self.DRVPH1_isSet, self.DRVPH2_isSet, self.DRVPH3_isSet]
if a >= 0 and a <= 7:
ascend = phases_isset[(disk_drive.current_phase + 1) % 4]
descend = phases_isset[(disk_drive.current_phase + 3) % 4]
# Abfrage, ob der phasenmotor aus ist. wie? Wo wird der softswitch gesetzt?
if not phases_isset[disk_drive.current_phase]:
if disk_drive.motor_running:
if ascend:
#if ascend == address:
self.move_head(disk_drive,1)
disk_drive.current_phase = (disk_drive.current_phase + 1) % 4
#print("ascending")
elif descend:
#elif descend == address:
self.move_head(disk_drive,-1)
disk_drive.current_phase = (disk_drive.current_phase + 3) % 4
#print("descending")
# 0xC088
elif address == self.DRVMOTOR_OFF:
#print("Stop Motor:",self.current_drive)
self.stop_motor(disk_drive, self.current_drive)
return result
# 0xC089
elif address == self.DRVMOTOR_ON:
#print("Start Motor:",self.current_drive)
self.start_motor(disk_drive, self.current_drive)
return result
# 0xC08A && 0xC08B
elif address == self.DRVSEL_1 or address == self.DRVSEL_0:
self.current_drive = 0 if address == self.DRVSEL_0 else 1
if self.drive_state[1 - self.current_drive].motor_running:
self.drive_state[1 - self.current_drive].motor_running = False
self.drive_state[self.current_drive].motor_running = True
return result
# 0xC08C SHIFT
elif address == self.DRVDATA_SHIFT:
if disk_drive.motor_running and not disk_drive.write_mode:
return self.get_next_byte(disk_drive)
# 0xC08D LOAD/READ
elif address == self.DRVDATA_LOAD:
if disk_drive.motor_running:
if disk_drive.write_mode:
self.do_write_byte(delta, disk_drive)
self.prev_cycle = cycle
if value >= 0:
self.data_register = value
# 0xC08E READ
elif address == self.DRVREAD:
if disk_drive.motor_running and disk_drive.write_mode:
self.do_write_byte(delta,disk_drive)
self.prev_cycle = cycle
disk_drive.write_mode = False
# if self.DRVDATA.isset:
# result = disk_drive.iswriteprotected ? 0xff : 0
result = 255
# 0xC08F WRITE
elif address == self.DRVWRITE:
disk_drive.write_mode = True
self.prev_cycle = cycle
if value >= 0:
self.data_register = value
return result
def debug_data(self, address):
pass
def move_head(self, disk_drive, direction):
if disk_drive.track_start[disk_drive.half_track] > 0:
disk_drive.prev_half_track = disk_drive.half_track
disk_drive.half_track += direction
if disk_drive.half_track < 0 or disk_drive.half_track > 68:
# sound: drive.trackend
if disk_drive.half_track < 0:
disk_drive.half_track = 0
if disk_drive.half_track > 68:
disk_drive.half_track = 68
else:
# sound: drive.trackseek
pass
disk_drive.status = str(disk_drive.half_track // 2)
if disk_drive.track_start[disk_drive.half_track] > 0 and disk_drive.prev_half_track != disk_drive.half_track:
disk_drive.track_location = math.floor(disk_drive.track_location * (disk_drive.track_nbits[disk_drive.half_track] / disk_drive.track_nbits[disk_drive.prev_half_track]))
if disk_drive.track_location > 3:
disk_drive.track_location -= 4
#print("Drive track:", disk_drive.half_track //2)
def do_write_byte(self, delta):
print("do write byte")
return
def do_nothing(self):
for i in range(10):
i=i+1
#print("NOP")
pass
def get_next_bit(self, disk_drive):
disk_drive.track_location = disk_drive.track_location % disk_drive.track_nbits[disk_drive.half_track]
bit = 0
if disk_drive.track_start[disk_drive.half_track] > 0:
file_offset = disk_drive.track_start[disk_drive.half_track] + (disk_drive.track_location >> 3)
byte = disk_drive.disk_data[file_offset]
b = disk_drive.track_location & 7
bit = (byte & pickbit[b]) >> (7-b)
else:
bit = 1
disk_drive.track_location += 1
return bit
def get_next_byte(self, disk_drive):
if len(disk_drive.disk_data) == 0 or disk_drive.half_track % 2 != 0:
print("No Disk")
return 0
result = 0
if self.data_register == 0:
while(self.get_next_bit(disk_drive) == 0):
self.do_nothing()
self.data_register = 0x40
for i in range(5,-1,-1):
# print("data register:", i)
self.data_register |= self.get_next_bit(disk_drive) << i
else:
bit = self.get_next_bit(disk_drive)
self.data_register = (self.data_register << 1) | bit
result = self.data_register
if (self.data_register > 127):
self.data_register = 0
print("get next byte:", hex(result), ' ', disk_drive.track_location,' ', disk_drive.half_track)
return result
def start_motor(self, disk_drive, current_drive):
disk_drive.motor_running = True
if self.sm:
self.sm.cancel()
pygame.mixer.Channel((current_drive+1)*2-1).play(self.motor_sound)
def stop_motor(self, disk_drive, current_drive):
self.sm = threading.Timer(1,self.stopit,[disk_drive, current_drive])
self.sm.start()
def stopit(self, disk_drive, current_drive):
disk_drive.motor_running = False
pygame.mixer.Channel((current_drive+1)*2-1).stop()