2017-08-04 02:53:36 +00:00
//
// D i s k I I . s w i f t
// F r u i t M a c h i n e
//
// C r e a t e d b y C h r i s t o p h e r R o h l o n 8 / 2 / 1 7 .
// C o p y r i g h t © 2 0 1 7 C h r i s t o p h e r R o h l . A l l r i g h t s r e s e r v e d .
//
import Cocoa
/*
Commands :
0 = PHASE 0 OFF
1 = PHASE 0 ON
2 = PHASE 1 OFF
3 = PHASE 1 ON
4 = PHASE 2 OFF
5 = PHASE 2 ON
6 = PHASE 3 OFF
7 = PHASE 3 ON
8 = TURN MOTOR OFF
9 = TURN MOTOR ON
A = SELECT DRIVE 1
B = SELECT DRIVE 2
C = Q6 -> L
D = Q6 -> H
E = Q7 -> L
F = Q7 -> H
Q6 Q7
L L READ
H L SENSE WRITE PROTECT OR PREWRITE STATE
L H WRITE
H H WRITE LOAD
*/
2017-08-07 00:16:25 +00:00
class DiskII : NSObject , Peripheral , HasROM {
2017-08-13 04:20:45 +00:00
let debug = false
2017-08-07 00:16:25 +00:00
2017-08-04 02:53:36 +00:00
enum MotorPhase {
case Phase0
case Phase1
case Phase2
case Phase3
}
/* N o t i f i c a t i o n s */
static let N_Drive1MotorOn = NSNotification . Name ( rawValue : " Drive1MotorOn " )
static let N_Drive2MotorOn = NSNotification . Name ( rawValue : " Drive2MotorOn " )
static let N_Drive1MotorOff = NSNotification . Name ( rawValue : " Drive1MotorOff " )
static let N_Drive2MotorOff = NSNotification . Name ( rawValue : " Drive2MotorOff " )
2017-08-04 07:20:17 +00:00
static let N_Drive1TrackChanged = NSNotification . Name ( rawValue : " Drive1TrackChanged " )
static let N_Drive2TrackChanged = NSNotification . Name ( rawValue : " Drive2TrackChanged " )
2017-08-04 21:06:03 +00:00
var motor1OffTimer : Timer ?
var motor2OffTimer : Timer ?
2017-08-04 02:53:36 +00:00
/* S o f t s w i t c h e s */
struct Softswitches {
var Phase0 = false
var Phase1 = false
var Phase2 = false
var Phase3 = false
var MotorPowered = false
var DriveSelect = false // f a l s e = 1 , t r u e = 2
var Q6 = false
var Q7 = false
}
let slotNumber : Int
let romManager : ROMManager
var readMemoryOverride : ReadOverride ? = nil
var readIOOverride : ReadOverride ? = nil
var writeIOOverride : WriteOverride ? = nil
var softswitches = Softswitches ( )
var currentTrack : Int = 0
2017-08-04 07:20:17 +00:00
var mediaPosition : Int = 0
2017-08-04 02:53:36 +00:00
var motorPhase : MotorPhase = . Phase0
2017-08-07 00:16:25 +00:00
var preloadedByte : UInt8 = 0x00
2017-08-06 07:38:41 +00:00
var diskImage : DiskImage ?
2017-08-04 07:20:17 +00:00
2017-08-04 02:53:36 +00:00
init ( slot : Int , romPath : String ) {
slotNumber = slot
romManager = ROMManager ( path : romPath , atAddress : 0x0 , size : 256 )
2017-08-13 00:24:30 +00:00
diskImage = nil
2017-08-04 02:53:36 +00:00
super . init ( )
readMemoryOverride = ReadOverride ( start : UInt16 ( 0xC000 + ( 0x100 * slotNumber ) ) ,
end : UInt16 ( 0xC0FF + ( 0x100 * slotNumber ) ) ,
readAnyway : false ,
action : actionReadMemory )
readIOOverride = ReadOverride ( start : UInt16 ( 0xC080 + ( 0x10 * slotNumber ) ) ,
end : UInt16 ( 0xC08F + ( 0x10 * slotNumber ) ) ,
readAnyway : false ,
action : actionDispatchOperation )
writeIOOverride = WriteOverride ( start : UInt16 ( 0xC080 + ( 0x10 * slotNumber ) ) ,
end : UInt16 ( 0xC08F + ( 0x10 * slotNumber ) ) ,
writeAnyway : false ,
action : actionDispatchOperation )
}
2017-08-06 07:38:41 +00:00
func attachDiskImage ( imagePath : String ) {
2017-08-13 00:24:30 +00:00
let image = DiskImage ( diskPath : imagePath )
self . diskImage = image
print ( " attached image to DiskII \( self ) " )
2017-08-06 07:38:41 +00:00
}
2017-08-04 02:53:36 +00:00
// h t t p : / / f t p . t w a r e n . n e t / N e t B S D / m i s c / w r s t u d e n / A p p l e _ P D F s / S o f t w a r e % 2 0 c o n t r o l % 2 0 o f % 2 0 I W M . p d f
2017-08-07 00:16:25 +00:00
private func actionDispatchOperation ( something : AnyObject , address : UInt16 , byte : UInt8 ? ) -> UInt8 ?
{
2017-08-04 02:53:36 +00:00
let operationNumber = UInt8 ( address & 0xFF ) - UInt8 ( 0x80 & 0xFF ) - UInt8 ( 0x10 * slotNumber )
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Disk II command: \( operationNumber ) " ) }
2017-08-04 02:53:36 +00:00
// U p d a t e t h e s o f t s w i t c h e s .
switch ( operationNumber ) {
case 0 :
softswitches . Phase0 = false
case 1 :
softswitches . Phase0 = true
if ( motorPhase = = . Phase1 ) {
motorPhase = . Phase0
2017-08-08 22:48:03 +00:00
// i f ( c u r r e n t T r a c k % 2 = = 0 & & c u r r e n t T r a c k > 0 )
if ( currentTrack > 0 )
2017-08-04 07:20:17 +00:00
{
currentTrack -= 1
2017-08-04 02:53:36 +00:00
}
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive now on track \( currentTrack ) " ) }
2017-08-04 02:53:36 +00:00
} else if ( motorPhase = = . Phase3 ) {
motorPhase = . Phase0
2017-08-08 22:48:03 +00:00
// i f ( c u r r e n t T r a c k % 2 = = 1 & & c u r r e n t T r a c k < 3 4 ) {
if ( currentTrack < 34 ) {
2017-08-04 07:20:17 +00:00
currentTrack += 1
2017-08-04 02:53:36 +00:00
}
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive now on track \( currentTrack ) " ) }
2017-08-04 02:53:36 +00:00
}
case 2 :
softswitches . Phase1 = false
case 3 :
softswitches . Phase1 = true
if ( motorPhase = = . Phase0 || motorPhase = = . Phase2 ) {
motorPhase = . Phase1
}
case 4 :
softswitches . Phase2 = false
case 5 :
softswitches . Phase2 = true
if ( motorPhase = = . Phase3 ) {
motorPhase = . Phase2
2017-08-05 02:59:15 +00:00
if ( currentTrack % 2 = = 1 && currentTrack > 0 ) {
2017-08-04 07:20:17 +00:00
currentTrack -= 1
2017-08-04 02:53:36 +00:00
}
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive now on track \( currentTrack ) " ) }
2017-08-04 02:53:36 +00:00
} else if ( motorPhase = = . Phase1 ) {
motorPhase = . Phase2
2017-08-04 07:20:17 +00:00
if ( currentTrack % 2 = = 0 && currentTrack < 34 ) {
currentTrack += 1 ;
2017-08-04 02:53:36 +00:00
}
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive now on track \( currentTrack ) " ) }
2017-08-04 02:53:36 +00:00
}
case 6 :
softswitches . Phase3 = false
case 7 :
softswitches . Phase3 = true
if ( motorPhase = = . Phase0 || motorPhase = = . Phase2 ) {
motorPhase = . Phase3
}
case 8 :
2017-08-05 02:59:15 +00:00
// s o f t s w i t c h e s . M o t o r P o w e r e d = f a l s e
2017-08-04 02:53:36 +00:00
if ( softswitches . DriveSelect = = false ) {
NotificationCenter . default . post ( name : DiskII . N_Drive1MotorOff , object : nil )
2017-08-04 21:06:03 +00:00
motor1OffTimer = Timer . scheduledTimer ( timeInterval : 1.0 ,
target : self ,
2017-08-05 07:29:07 +00:00
selector : #selector ( disableDrive1Motor ) ,
2017-08-04 21:06:03 +00:00
userInfo : nil ,
repeats : false )
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive 1 Motor will turn off in 1 second " ) }
2017-08-04 02:53:36 +00:00
} else {
NotificationCenter . default . post ( name : DiskII . N_Drive2MotorOff , object : nil )
2017-08-04 21:06:03 +00:00
motor2OffTimer = Timer . scheduledTimer ( timeInterval : 1.0 ,
target : self ,
selector : #selector ( disableDrive2Motor ) ,
userInfo : nil ,
repeats : false )
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive 2 Motor will turn off in 1 second " ) }
2017-08-04 02:53:36 +00:00
}
case 9 :
softswitches . MotorPowered = true
if ( softswitches . DriveSelect = = false ) {
NotificationCenter . default . post ( name : DiskII . N_Drive1MotorOn , object : nil )
2017-08-05 02:59:15 +00:00
motor1OffTimer ? . invalidate ( )
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive 1 Motor is on " ) }
2017-08-04 02:53:36 +00:00
} else {
NotificationCenter . default . post ( name : DiskII . N_Drive2MotorOn , object : nil )
2017-08-05 02:59:15 +00:00
motor1OffTimer ? . invalidate ( )
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive 2 Motor is on " ) }
2017-08-04 02:53:36 +00:00
}
case 10 :
softswitches . DriveSelect = false
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive 1 selected " ) }
2017-08-04 02:53:36 +00:00
case 11 :
softswitches . DriveSelect = true
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Drive 2 selected " ) }
2017-08-04 02:53:36 +00:00
case 12 :
softswitches . Q6 = false
2017-08-08 22:48:03 +00:00
let trk : UInt8
let sec : UInt8
let mode : UInt8
let blkLo : UInt8
let blkHi : UInt8
2017-08-13 00:24:30 +00:00
if ( diskImage = = nil ) {
print ( " No disk inserted, aborting. DiskII \( self ) " )
return 0x00
}
if ( diskImage ? . image is Dos33Image ) {
2017-08-08 22:48:03 +00:00
trk = CPU . sharedInstance . memoryInterface . readByte ( offset : 0xB7EC , bypassOverrides : true )
sec = CPU . sharedInstance . memoryInterface . readByte ( offset : 0xB7ED , bypassOverrides : true )
mode = CPU . sharedInstance . memoryInterface . readByte ( offset : 0xB7F4 , bypassOverrides : true )
blkLo = 0
blkHi = 0
}
2017-08-13 00:24:30 +00:00
else if ( diskImage ? . image is ProdosImage ) {
2017-08-08 22:48:03 +00:00
trk = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x41 , bypassOverrides : true )
sec = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x3D , bypassOverrides : true )
mode = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x42 , bypassOverrides : true )
blkLo = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x46 , bypassOverrides : true )
blkHi = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x47 , bypassOverrides : true )
} else {
trk = 0
sec = 0
mode = 0
blkLo = 0
blkHi = 0
}
2017-08-07 00:16:25 +00:00
if ( trk = = 2 && sec = = 4 && mode = = 1 )
{
_ = 1
}
let modeString : String
switch ( mode ) {
case 0 :
2017-08-08 04:08:05 +00:00
modeString = " seek "
2017-08-07 00:16:25 +00:00
case 1 :
2017-08-08 04:08:05 +00:00
modeString = " read "
2017-08-07 00:16:25 +00:00
case 2 :
2017-08-08 04:08:05 +00:00
modeString = " write "
2017-08-07 00:16:25 +00:00
case 4 :
2017-08-08 04:08:05 +00:00
modeString = " format "
2017-08-07 00:16:25 +00:00
default :
modeString = " ??? "
}
2017-08-08 22:48:03 +00:00
if ( debug )
{
2017-08-13 00:24:30 +00:00
if ( diskImage ? . image is Dos33Image ) {
2017-08-08 22:48:03 +00:00
print ( " Head is at nibble \( mediaPosition ) of track \( currentTrack ) . DOS is trying to \( modeString ) T \( trk ) S \( sec ) . " )
2017-08-13 00:24:30 +00:00
} else if ( diskImage ? . image is ProdosImage ) {
2017-08-08 22:48:03 +00:00
print ( " Head is at nibble \( mediaPosition ) of track \( currentTrack ) . ProDOS is trying to \( modeString ) Block $ \( blkHi . asHexString ( ) ) \( blkLo . asHexString ( ) ) (T \( trk ) S \( sec ) ). " )
if ( mode = = 1 ) {
let bufLo = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x44 , bypassOverrides : true )
let bufHi = CPU . sharedInstance . memoryInterface . readByte ( offset : 0x45 , bypassOverrides : true )
print ( " I/O buffer is located at $ \( bufHi . asHexString ( ) ) \( bufLo . asHexString ( ) ) " )
}
}
}
2017-08-07 00:16:25 +00:00
updateCurrentTrackSectorDisplay ( drive : softswitches . DriveSelect , track : currentTrack , sector : Int ( sec ) )
2017-08-05 07:29:07 +00:00
if ( softswitches . Q7 = = false && byte = = nil ) {
2017-08-07 00:16:25 +00:00
// i n r e a d m o d e a n d a r e a d w a s r e q u e s t e d . g e t t h e n e x t n i b b l e
2017-08-04 07:20:17 +00:00
return readByteOfTrack ( track : currentTrack , advance : softswitches . MotorPowered ? 1 : 0 )
2017-08-04 02:53:36 +00:00
}
2017-08-07 00:16:25 +00:00
if ( softswitches . Q7 = = true && byte = = nil ) {
// i n w r i t e m o d e
writeNibbleOfTrack ( track : currentTrack , advance : softswitches . MotorPowered ? 1 : 0 , nibble : preloadedByte )
return 0x00
}
if ( debug ) { print ( " Disk II: Operation failed! " ) }
2017-08-04 02:53:36 +00:00
case 13 :
2017-08-04 07:20:17 +00:00
// W R I T E P R O T E C T S E N S E M O D E
2017-08-04 02:53:36 +00:00
softswitches . Q6 = true
2017-08-08 04:08:05 +00:00
if ( byte != nil ) {
preloadedByte = byte !
if ( debug ) { print ( " WRITE LOAD: shift register contains \( preloadedByte . asHexString ( ) ) " ) }
}
2017-08-04 02:53:36 +00:00
case 14 :
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Disk II: READ STATUS REGISTER " ) }
2017-08-04 02:53:36 +00:00
softswitches . Q7 = false
2017-08-08 04:08:05 +00:00
if ( diskImage != nil ) {
return 0x00 | ( diskImage ! . writeProtect ? 0x80 : 0x00 ) | ( softswitches . MotorPowered ? 0x20 : 0x00 )
} else {
return 0x00 | ( softswitches . MotorPowered ? 0x20 : 0x00 )
}
2017-08-04 02:53:36 +00:00
case 15 :
softswitches . Q7 = true
2017-08-07 00:16:25 +00:00
// S T A $ C
if ( softswitches . Q6 = = true && byte != nil ) {
preloadedByte = byte !
if ( debug ) { print ( " WRITE LOAD: shift register contains \( preloadedByte . asHexString ( ) ) " ) }
}
2017-08-04 02:53:36 +00:00
default :
2017-08-07 00:16:25 +00:00
if ( debug ) { print ( " Unknown command? This can't happen. " ) }
2017-08-04 02:53:36 +00:00
}
return 0x00
}
2017-08-04 07:20:17 +00:00
func readByteOfTrack ( track : Int , advance : Int ) -> UInt8 {
2017-08-06 07:38:41 +00:00
if ( diskImage = = nil ) { return 0x00 } // N o d i s k i n s e r t e d , f a i l .
let trackData = diskImage ? . encodedTracks [ track ]
if ( trackData = = nil ) { return 0x00 } // N o d i s k i n s e r t e d , f a i l .
2017-08-04 07:20:17 +00:00
2017-08-06 07:38:41 +00:00
let result = trackData ! [ mediaPosition ]
2017-08-05 02:59:15 +00:00
// A d v a n c e t h e d r i v e t o t h e n e x t b y t e
2017-08-06 07:38:41 +00:00
mediaPosition = ( mediaPosition + advance ) % trackData ! . count
2017-08-04 07:20:17 +00:00
return result
}
2017-08-07 00:16:25 +00:00
func writeNibbleOfTrack ( track : Int , advance : Int , nibble : UInt8 ) {
if ( diskImage = = nil ) { return } // N o d i s k i n s e r t e d , f a i l .
let trackData = diskImage ? . encodedTracks [ track ]
if ( trackData = = nil ) { return } // N o d i s k i n s e r t e d , f a i l .
if ( debug ) { print ( " wrote \( nibble . asHexString ( ) ) to disk " ) }
diskImage ! . encodedTracks [ track ] [ mediaPosition ] = nibble
mediaPosition = ( mediaPosition + advance ) % trackData ! . count
}
func updateCurrentTrackSectorDisplay ( drive : Bool , track : Int , sector : Int ) {
2017-08-04 07:20:17 +00:00
if ( drive = = false ) {
2017-08-07 00:16:25 +00:00
NotificationCenter . default . post ( name : DiskII . N_Drive1TrackChanged , object : ( currentTrack , sector ) )
2017-08-04 07:20:17 +00:00
}
else {
2017-08-07 00:16:25 +00:00
NotificationCenter . default . post ( name : DiskII . N_Drive2TrackChanged , object : ( currentTrack , sector ) )
2017-08-04 07:20:17 +00:00
}
}
2017-08-04 02:53:36 +00:00
func installOverrides ( ) {
CPU . sharedInstance . memoryInterface . read_overrides . append ( readMemoryOverride ! )
CPU . sharedInstance . memoryInterface . read_overrides . append ( readIOOverride ! )
CPU . sharedInstance . memoryInterface . write_overrides . append ( writeIOOverride ! )
}
private func actionReadMemory ( something : AnyObject , address : UInt16 , byte : UInt8 ? ) -> UInt8 ? {
let offset : UInt16 = 0xC000 + UInt16 ( slotNumber * 0x100 )
let local = address - offset
return getMemoryMappedByte ( address : local )
}
private func getMemoryMappedByte ( address : UInt16 ) -> UInt8 {
// D i s k I I j u s t m a p s i t s R O M t o t h e m e m o r y a d d r e s s e d b y t h e s l o t .
return romManager . ROM [ Int ( address ) ]
}
2017-08-04 21:06:03 +00:00
@objc func disableDrive1Motor ( ) {
softswitches . MotorPowered = false
2017-08-08 04:08:05 +00:00
if ( debug ) { print ( " Drive 1 Motor is now off, saving updated image " ) }
diskImage ? . saveDiskImage ( )
2017-08-04 21:06:03 +00:00
}
@objc func disableDrive2Motor ( ) {
softswitches . MotorPowered = false
2017-08-08 04:08:05 +00:00
if ( debug ) { print ( " Drive 2 Motor is now off, saving updated image " ) }
diskImage ? . saveDiskImage ( )
2017-08-04 21:06:03 +00:00
}
2017-08-04 02:53:36 +00:00
}