From 26549c773702d2e0c82601ad57e6d9c2373dcfe6 Mon Sep 17 00:00:00 2001 From: Luigi Thirty Date: Sun, 6 Aug 2017 20:16:25 -0400 Subject: [PATCH] more disk fixes adding language card --- FruitMachine.xcodeproj/project.pbxproj | 6 + FruitMachine/AppleII/AppleII.swift | 38 ++- .../AppleII/Peripherals/DiskII/DiskII.swift | 129 ++++++---- .../Peripherals/DiskII/DiskImage.swift | 1 + .../AppleII/Peripherals/LanguageCard16K.swift | 60 +++++ FruitMachine/AppleII/Video/ScreenView.swift | 4 +- FruitMachine/AppleIIViewController.swift | 15 +- FruitMachine/FruitMachine.storyboard | 12 +- FruitMachine/PreferencesWindow.xib | 226 ++++++++++++++++-- .../PreferencesWindowController.swift | 55 ++++- 10 files changed, 457 insertions(+), 89 deletions(-) create mode 100644 FruitMachine/AppleII/Peripherals/LanguageCard16K.swift diff --git a/FruitMachine.xcodeproj/project.pbxproj b/FruitMachine.xcodeproj/project.pbxproj index 30cb7e3..137895d 100644 --- a/FruitMachine.xcodeproj/project.pbxproj +++ b/FruitMachine.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 2A044E2C1F37D65A000E8085 /* LanguageCard16K.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A044E2B1F37D65A000E8085 /* LanguageCard16K.swift */; }; 2A16ADBB1F33C341004A0333 /* DiskImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A16ADBA1F33C341004A0333 /* DiskImage.swift */; }; 2A2126841F2A9FA300E43DC1 /* DebuggerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */; }; 2A22EBFB1F21A7A700A36A61 /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */; }; @@ -56,6 +57,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2A044E2B1F37D65A000E8085 /* LanguageCard16K.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageCard16K.swift; sourceTree = ""; }; 2A16ADBA1F33C341004A0333 /* DiskImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskImage.swift; sourceTree = ""; }; 2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggerWindowController.swift; sourceTree = ""; }; 2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerExtensions.swift; sourceTree = ""; }; @@ -150,6 +152,7 @@ isa = PBXGroup; children = ( 2A16ADB91F33C2ED004A0333 /* DiskII */, + 2A044E2B1F37D65A000E8085 /* LanguageCard16K.swift */, 2A63C23B1F32CD4800D4F4F8 /* Peripheral.swift */, ); path = Peripherals; @@ -430,6 +433,7 @@ 2AE5BA061F2469EB00FAA343 /* AddressConversions.swift in Sources */, 2AE42E0A1F28521E00C4900E /* WriteOverride.swift in Sources */, 2A6DC7ED1F30492C0066FE0D /* ScreenDelegate.swift in Sources */, + 2A044E2C1F37D65A000E8085 /* LanguageCard16K.swift in Sources */, 2A5BC5191F29A28D008C03BE /* AppleIScreenView.swift in Sources */, 2A63C2371F32C55100D4F4F8 /* VideoHelpers.swift in Sources */, 2A63C23A1F32CCE900D4F4F8 /* DiskII.swift in Sources */, @@ -510,6 +514,7 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -559,6 +564,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/FruitMachine/AppleII/AppleII.swift b/FruitMachine/AppleII/AppleII.swift index 5eb123c..1818ba7 100644 --- a/FruitMachine/AppleII/AppleII.swift +++ b/FruitMachine/AppleII/AppleII.swift @@ -36,15 +36,15 @@ final class AppleII: NSObject, EmulatedSystem { videoMode = .Text - for i in 1...7 { + for i in 0...7 { backplane[i] = nil } - backplane[6] = DiskII(slot: 6, romPath: "/Users/luigi/apple2/341-0027-a.p5") super.init() - loadROMs() setupMemory(ramConfig: .fortyeightK) + setupPeripherals() + loadROMs() emuScreenLayer.shouldRasterize = true emuScreenLayer.delegate = emulatorViewDelegate @@ -52,7 +52,7 @@ final class AppleII: NSObject, EmulatedSystem { emulatorView.wantsLayer = true - emuScreenLayer.setNeedsDisplay() + //emuScreenLayer.setNeedsDisplay() emulatorView.layer?.addSublayer(emuScreenLayer) installOverrides() @@ -70,11 +70,19 @@ final class AppleII: NSObject, EmulatedSystem { CPU.sharedInstance.memoryInterface.loadBinary(path: "/Users/luigi/apple2/341-0001-00.e0", offset: 0xE000, length: 0x800) CPU.sharedInstance.memoryInterface.loadBinary(path: "/Users/luigi/apple2/341-0002-00.e8", offset: 0xE800, length: 0x800) CPU.sharedInstance.memoryInterface.loadBinary(path: "/Users/luigi/apple2/341-0003-00.f0", offset: 0xF000, length: 0x800) - CPU.sharedInstance.memoryInterface.loadBinary(path: "/Users/luigi/apple2/341-0004-00.f8", offset: 0xF800, length: 0x800) + + //Hardware handles this, really. + if(backplane[0] is LanguageCard16K) { + //Language Card ROM + CPU.sharedInstance.memoryInterface.loadBinary(path: "/Users/luigi/apple2/341-0020-00.f8", offset: 0xF800, length: 0x800) + } else { + //Integer BASIC ROM + CPU.sharedInstance.memoryInterface.loadBinary(path: "/Users/luigi/apple2/341-0004-00.f8", offset: 0xF800, length: 0x800) + } } func installOverrides() { - for (slotNum, peripheral) in backplane { + for (_, peripheral) in backplane { if(peripheral != nil) { peripheral!.installOverrides() } @@ -203,6 +211,24 @@ final class AppleII: NSObject, EmulatedSystem { } } + func setupPeripherals() { + let defaults = UserDefaults.standard + + let slot0 = defaults.string(forKey: "a2_Peripherals_Slot0") + if(slot0 == "Language Card (16K)") { + backplane[0] = LanguageCard16K(slot: 0, romPath: "/Users/luigi/apple2/341-0020-00.f8") + } + + let slot6 = defaults.string(forKey: "a2_Peripherals_Slot6") + if(slot6 == "Disk II") { + backplane[6] = DiskII(slot: 6, romPath: "/Users/luigi/apple2/341-0027-a.p5") + + let drive = backplane[6]! as! DiskII + drive.attachDiskImage(imagePath: "/Users/luigi/apple2/Apex II - Apple II Diagnostic (v4.7-1986).DSK") + //drive.attachDiskImage(imagePath: "/Users/luigi/apple2/clean332sysmas.do") + } + } + @objc func debuggerBreak() { } diff --git a/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift b/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift index f4126e5..79914f3 100644 --- a/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift +++ b/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift @@ -34,7 +34,9 @@ import Cocoa H H WRITE LOAD */ -class DiskII: NSObject, Peripheral { +class DiskII: NSObject, Peripheral, HasROM { + let debug = false + enum MotorPhase { case Phase0 case Phase1 @@ -77,6 +79,8 @@ class DiskII: NSObject, Peripheral { var mediaPosition: Int = 0 var motorPhase: MotorPhase = .Phase0 + var preloadedByte: UInt8 = 0x00 + var diskImage: DiskImage? init(slot: Int, romPath: String) { @@ -106,10 +110,11 @@ class DiskII: NSObject, Peripheral { } //http://ftp.twaren.net/NetBSD/misc/wrstuden/Apple_PDFs/Software%20control%20of%20IWM.pdf - private func actionDispatchOperation(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? { + private func actionDispatchOperation(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? + { let operationNumber = UInt8(address & 0xFF) - UInt8(0x80 & 0xFF) - UInt8(0x10 * slotNumber) - print("Disk II command: \(operationNumber)") + if(debug) { print("Disk II command: \(operationNumber)") } //Update the softswitches. switch(operationNumber) { @@ -123,15 +128,15 @@ class DiskII: NSObject, Peripheral { { currentTrack -= 1 } - print("Drive now on track \(currentTrack)") - updateCurrentTrackDisplay(drive: softswitches.DriveSelect) + if(debug) { print("Drive now on track \(currentTrack)") } + //updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } else if(motorPhase == .Phase3) { motorPhase = .Phase0 if(currentTrack % 2 == 1 && currentTrack < 34) { currentTrack += 1 } - print("Drive now on track \(currentTrack)") - updateCurrentTrackDisplay(drive: softswitches.DriveSelect) + if(debug) { print("Drive now on track \(currentTrack)") } + //updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } case 2: softswitches.Phase1 = false @@ -149,15 +154,15 @@ class DiskII: NSObject, Peripheral { if(currentTrack % 2 == 1 && currentTrack > 0) { currentTrack -= 1 } - print("Drive now on track \(currentTrack)") - updateCurrentTrackDisplay(drive: softswitches.DriveSelect) + if(debug) { print("Drive now on track \(currentTrack)") } + //updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } else if(motorPhase == .Phase1) { motorPhase = .Phase2 if(currentTrack % 2 == 0 && currentTrack < 34) { currentTrack += 1; } - print("Drive now on track \(currentTrack)") - updateCurrentTrackDisplay(drive: softswitches.DriveSelect) + if(debug) { print("Drive now on track \(currentTrack)") } + //updateCurrentTrackDisplay(drive: softswitches.DriveSelect) } case 6: softswitches.Phase3 = false @@ -175,7 +180,7 @@ class DiskII: NSObject, Peripheral { selector: #selector(disableDrive1Motor), userInfo: nil, repeats: false) - print("Drive 1 Motor will turn off in 1 second") + if(debug) { print("Drive 1 Motor will turn off in 1 second") } } else { NotificationCenter.default.post(name: DiskII.N_Drive2MotorOff, object: nil) motor2OffTimer = Timer.scheduledTimer(timeInterval: 1.0, @@ -183,63 +188,80 @@ class DiskII: NSObject, Peripheral { selector: #selector(disableDrive2Motor), userInfo: nil, repeats: false) - print("Drive 2 Motor will turn off in 1 second") + if(debug) { print("Drive 2 Motor will turn off in 1 second") } } case 9: softswitches.MotorPowered = true if(softswitches.DriveSelect == false) { NotificationCenter.default.post(name: DiskII.N_Drive1MotorOn, object: nil) motor1OffTimer?.invalidate() - print("Drive 1 Motor is on") + if(debug) { print("Drive 1 Motor is on") } } else { NotificationCenter.default.post(name: DiskII.N_Drive2MotorOn, object: nil) motor1OffTimer?.invalidate() - print("Drive 2 Motor is on") + if(debug) { print("Drive 2 Motor is on") } } case 10: softswitches.DriveSelect = false - print("Drive 1 selected") + if(debug) { print("Drive 1 selected") } case 11: softswitches.DriveSelect = true - print("Drive 2 selected") + if(debug) { print("Drive 2 selected") } case 12: softswitches.Q6 = false + let trk = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7EC, bypassOverrides: true) + let sec = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7ED, bypassOverrides: true) + let mode = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7F4, bypassOverrides: true) + if(trk == 2 && sec == 4 && mode == 1) + { + _ = 1 + } + let modeString: String + switch (mode) { + case 0: + modeString = "seeking" + case 1: + modeString = "reading" + case 2: + modeString = "writing" + case 4: + modeString = "formatting" + default: + modeString = "???" + } + if(debug) { print("Head is at nibble \(mediaPosition) of track \(currentTrack). DOS is \(modeString) T\(trk) S\(sec).") } + updateCurrentTrackSectorDisplay(drive: softswitches.DriveSelect, track: currentTrack, sector: Int(sec)) + if(softswitches.Q7 == false && byte == nil) { - //in read mode and a read was requested. get the next byte - let trk = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7EC, bypassOverrides: true) - let sec = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7ED, bypassOverrides: true) - let mode = CPU.sharedInstance.memoryInterface.readByte(offset: 0xB7F4, bypassOverrides: true) - if(trk == 2 && sec == 4 && mode == 1) - { - _ = 1 - } - let modeString: String - switch (mode) { - case 0: - modeString = "seeking" - case 1: - modeString = "reading" - case 2: - modeString = "writing" - case 4: - modeString = "formatting" - default: - modeString = "???" - } - - print("Head is at nibble \(mediaPosition) of track \(currentTrack). DOS is \(modeString) T\(trk) S\(sec).") + //in read mode and a read was requested. get the next nibble return readByteOfTrack(track: currentTrack, advance: softswitches.MotorPowered ? 1 : 0) } + if(softswitches.Q7 == true && byte == nil) { + //in write mode + writeNibbleOfTrack(track: currentTrack, advance: softswitches.MotorPowered ? 1 : 0, nibble: preloadedByte) + return 0x00 + } + + if(debug) { print("Disk II: Operation failed!") } + case 13: //WRITE PROTECT SENSE MODE softswitches.Q6 = true case 14: - //READ MODE + if(debug) { print("Disk II: READ STATUS REGISTER") } softswitches.Q7 = false + return 0x00 | (diskImage!.writeProtect ? 0x80 : 0x00) | (softswitches.MotorPowered ? 0x20 : 0x00) case 15: softswitches.Q7 = true + + //STA $C + if(softswitches.Q6 == true && byte != nil) { + preloadedByte = byte! + if(debug) { print("WRITE LOAD: shift register contains \(preloadedByte.asHexString())") } + } + default: - print("Unknown command? This can't happen.") + if(debug) { print("Unknown command? This can't happen.") } } return 0x00 @@ -258,12 +280,25 @@ class DiskII: NSObject, Peripheral { return result } - func updateCurrentTrackDisplay(drive: Bool) { + func writeNibbleOfTrack(track: Int, advance: Int, nibble: UInt8) { + if(diskImage == nil) { return} //No disk inserted, fail. + + let trackData = diskImage?.encodedTracks[track] + if(trackData == nil) { return } //No disk inserted, fail. + + 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) { if(drive == false) { - NotificationCenter.default.post(name: DiskII.N_Drive1TrackChanged, object: currentTrack) + NotificationCenter.default.post(name: DiskII.N_Drive1TrackChanged, object: (currentTrack, sector)) } else { - NotificationCenter.default.post(name: DiskII.N_Drive2TrackChanged, object: currentTrack) + NotificationCenter.default.post(name: DiskII.N_Drive2TrackChanged, object: (currentTrack, sector)) } } @@ -288,11 +323,11 @@ class DiskII: NSObject, Peripheral { @objc func disableDrive1Motor() { softswitches.MotorPowered = false - print("Drive 1 Motor is now off") + if(debug) { print("Drive 1 Motor is now off") } } @objc func disableDrive2Motor() { softswitches.MotorPowered = false - print("Drive 2 Motor is now off") + if(debug) { print("Drive 2 Motor is now off") } } } diff --git a/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift b/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift index b6a79d6..24a2719 100644 --- a/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift +++ b/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift @@ -83,6 +83,7 @@ class DiskImage: NSObject { var encodedTracks = [[UInt8]]() var fileSize: UInt64 = 0 var image: DiskImageFormat? + var writeProtect = false init(diskPath: String) { do { diff --git a/FruitMachine/AppleII/Peripherals/LanguageCard16K.swift b/FruitMachine/AppleII/Peripherals/LanguageCard16K.swift new file mode 100644 index 0000000..700bfe8 --- /dev/null +++ b/FruitMachine/AppleII/Peripherals/LanguageCard16K.swift @@ -0,0 +1,60 @@ +// +// LanguageCard16K.swift +// FruitMachine +// +// Created by Christopher Rohl on 8/6/17. +// Copyright © 2017 Christopher Rohl. All rights reserved. +// + +import Cocoa + +class LanguageCard16K: NSObject, Peripheral, HasROM { + var slotNumber: Int + var romManager: ROMManager + + //16KB of RAM on the Language Card. + var ram = [UInt8](repeating: 0xCC, count: 16384) + + var readIOOverride: ReadOverride? = nil + var writeIOOverride: WriteOverride? = nil + + func installOverrides() { + func installOverrides() { + CPU.sharedInstance.memoryInterface.read_overrides.append(readIOOverride!) + CPU.sharedInstance.memoryInterface.write_overrides.append(writeIOOverride!) + } + } + + init(slot: Int, romPath: String) { + slotNumber = slot + romManager = ROMManager(path: romPath, atAddress: 0x0, size: 2048) + + super.init() + + 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) + } + + private func actionDispatchOperation(something: AnyObject, address: UInt16, byte: UInt8?) -> UInt8? + { + let operationNumber = UInt8(address & 0xFF) - UInt8(0x80 & 0xFF) - UInt8(0x10 * slotNumber) + var isRead = false + if(byte == nil) { + isRead = true + } + + print("Language Card command: \(isRead == false ? "Read" : "Write") $\(operationNumber.asHexString())") + + return 0x00 + } + + + +} diff --git a/FruitMachine/AppleII/Video/ScreenView.swift b/FruitMachine/AppleII/Video/ScreenView.swift index 45836e3..b830a2c 100644 --- a/FruitMachine/AppleII/Video/ScreenView.swift +++ b/FruitMachine/AppleII/Video/ScreenView.swift @@ -26,10 +26,8 @@ extension AppleII { override func draw(_ dirtyRect: NSRect) { super.draw(dirtyRect) - // Drawing code here. - + layer?.sublayers![0].setNeedsDisplay() - layer?.sublayers![0].display() } } diff --git a/FruitMachine/AppleIIViewController.swift b/FruitMachine/AppleIIViewController.swift index f53312f..feeef69 100644 --- a/FruitMachine/AppleIIViewController.swift +++ b/FruitMachine/AppleIIViewController.swift @@ -27,12 +27,10 @@ class AppleIIViewController: NSViewController { preferencesWindowController = PreferencesWindowController() self.view.addSubview(computer.emulatorView) + preferencesWindowController.setupDefaultsIfRequired() setupDriveNotifications() NotificationCenter.default.addObserver(self, selector: #selector(self.debuggerBreak), name: DebuggerNotifications.Break, object: nil) - - let drive = computer.backplane[6]! as! DiskII - drive.attachDiskImage(imagePath: "/Users/luigi/apple2/clean332sysmas.do") - + self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0, target: self, selector: #selector(runEmulation), @@ -60,6 +58,7 @@ class AppleIIViewController: NSViewController { @IBAction func showPreferences(_ sender: Any) { preferencesWindowController.loadWindow() + preferencesWindowController.showWindow(self) } @IBAction func doReset(_ sender: Any) { @@ -107,12 +106,12 @@ class AppleIIViewController: NSViewController { /* drive lights */ @objc func drive1TrackChanged(notification: NSNotification) { - let num = notification.object as? Int - lbl_Drive1.stringValue = "D1 \(num!)" + let num = notification.object as? (Int, Int) + lbl_Drive1.stringValue = "D1 T\(num!.0) S\(num!.1)" } @objc func drive2TrackChanged(notification: NSNotification) { - let num = notification.object as? Int - lbl_Drive2.stringValue = "D2 \(num!)" + let num = notification.object as? (Int, Int) + lbl_Drive2.stringValue = "D1 T\(num!.0) S\(num!.1)" } @objc func drive1MotorOff(notification: NSNotification) { diff --git a/FruitMachine/FruitMachine.storyboard b/FruitMachine/FruitMachine.storyboard index bbd8ce2..50d5192 100644 --- a/FruitMachine/FruitMachine.storyboard +++ b/FruitMachine/FruitMachine.storyboard @@ -187,19 +187,19 @@ - - + + - + - - + + - + diff --git a/FruitMachine/PreferencesWindow.xib b/FruitMachine/PreferencesWindow.xib index 4821d7a..f97dd74 100644 --- a/FruitMachine/PreferencesWindow.xib +++ b/FruitMachine/PreferencesWindow.xib @@ -8,6 +8,8 @@ + + @@ -19,30 +21,30 @@ - + - + - + - + - + - + @@ -51,7 +53,7 @@ - + @@ -60,7 +62,7 @@ - + @@ -69,7 +71,7 @@ - + @@ -78,7 +80,7 @@ - + @@ -87,7 +89,7 @@ - + @@ -96,7 +98,7 @@