From 1e54fceec3352f88f24b81eae822abbc81df99ed Mon Sep 17 00:00:00 2001 From: Luigi Thirty Date: Tue, 8 Aug 2017 00:08:05 -0400 Subject: [PATCH] select disk images to boot --- FruitMachine/AppleII/AppleIIBase.swift | 7 +- .../AppleII/Peripherals/DiskII/DiskII.swift | 31 ++- .../Peripherals/DiskII/DiskImage.swift | 184 +++++++++++++----- FruitMachine/AppleIIViewController.swift | 19 ++ FruitMachine/FruitMachine.storyboard | 19 +- FruitMachine/M6502/CPU.swift | 22 +++ 6 files changed, 211 insertions(+), 71 deletions(-) diff --git a/FruitMachine/AppleII/AppleIIBase.swift b/FruitMachine/AppleII/AppleIIBase.swift index 16ccf1e..3e96f8d 100644 --- a/FruitMachine/AppleII/AppleIIBase.swift +++ b/FruitMachine/AppleII/AppleIIBase.swift @@ -110,11 +110,14 @@ class AppleIIBase: NSObject, EmulatedSystem { 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") } } + func doColdReset() { + CPU.sharedInstance.coldReset() + doReset() + } + func doReset() { videoSoftswitches.reset() videoMode = .Text diff --git a/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift b/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift index 79914f3..69ace60 100644 --- a/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift +++ b/FruitMachine/AppleII/Peripherals/DiskII/DiskII.swift @@ -35,7 +35,7 @@ import Cocoa */ class DiskII: NSObject, Peripheral, HasROM { - let debug = false + let debug = true enum MotorPhase { case Phase0 @@ -219,17 +219,17 @@ class DiskII: NSObject, Peripheral, HasROM { let modeString: String switch (mode) { case 0: - modeString = "seeking" + modeString = "seek" case 1: - modeString = "reading" + modeString = "read" case 2: - modeString = "writing" + modeString = "write" case 4: - modeString = "formatting" + modeString = "format" default: modeString = "???" } - if(debug) { print("Head is at nibble \(mediaPosition) of track \(currentTrack). DOS is \(modeString) T\(trk) S\(sec).") } + if(debug) { print("Head is at nibble \(mediaPosition) of track \(currentTrack). DOS is trying to \(modeString) T\(trk) S\(sec).") } updateCurrentTrackSectorDisplay(drive: softswitches.DriveSelect, track: currentTrack, sector: Int(sec)) if(softswitches.Q7 == false && byte == nil) { @@ -247,10 +247,20 @@ class DiskII: NSObject, Peripheral, HasROM { case 13: //WRITE PROTECT SENSE MODE softswitches.Q6 = true + if(byte != nil) { + preloadedByte = byte! + if(debug) { print("WRITE LOAD: shift register contains \(preloadedByte.asHexString())") } + } + case 14: if(debug) { print("Disk II: READ STATUS REGISTER") } softswitches.Q7 = false - return 0x00 | (diskImage!.writeProtect ? 0x80 : 0x00) | (softswitches.MotorPowered ? 0x20 : 0x00) + if(diskImage != nil) { + return 0x00 | (diskImage!.writeProtect ? 0x80 : 0x00) | (softswitches.MotorPowered ? 0x20 : 0x00) + } else { + return 0x00 | (softswitches.MotorPowered ? 0x20 : 0x00) + } + case 15: softswitches.Q7 = true @@ -323,11 +333,14 @@ class DiskII: NSObject, Peripheral, HasROM { @objc func disableDrive1Motor() { softswitches.MotorPowered = false - if(debug) { print("Drive 1 Motor is now off") } + if(debug) { print("Drive 1 Motor is now off, saving updated image") } + diskImage?.saveDiskImage() + } @objc func disableDrive2Motor() { softswitches.MotorPowered = false - if(debug) { print("Drive 2 Motor is now off") } + if(debug) { print("Drive 2 Motor is now off, saving updated image") } + diskImage?.saveDiskImage() } } diff --git a/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift b/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift index 24a2719..68e8bd3 100644 --- a/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift +++ b/FruitMachine/AppleII/Peripherals/DiskII/DiskImage.swift @@ -8,26 +8,21 @@ import Cocoa -/* while I figure out this C code port */ -prefix operator ++ -postfix operator ++ - -// Increment -prefix func ++(x: inout Int) -> Int { - x += 1 - return x -} - -postfix func ++(x: inout Int) -> Int { - x += 1 - return (x - 1) -} - protocol DiskImageFormat { static var BYTES_PER_SECTOR: Int { get } static var SECTORS_PER_TRACK: Int { get } static var TRACKS_PER_DISK: Int { get } static var BYTES_PER_TRACK: Int { get } + + static var SECTOR_ORDER: [Int] { get } + + static func readTrackAndSector(imageData: [UInt8], trackNum: Int, sectorNum: Int) -> [UInt8] +} + +enum DiskFormat { + case Dos33 + case Prodos + case Raw } class Dos33Image: DiskImageFormat { @@ -37,54 +32,48 @@ class Dos33Image: DiskImageFormat { static let BYTES_PER_TRACK: Int = BYTES_PER_SECTOR * SECTORS_PER_TRACK //Sectors in a track are in this order. - // 0 7 14 6 13 5 12 4 11 3 10 2 9 1 8 15 - static let sectorOrder = [0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15] - - struct VTOC { - //http://fileformats.archiveteam.org/wiki/Apple_DOS_file_system - - //$00 unused - let catalogTrackNumber = 0 //$01 - let catalogSectorNumber = 0 //$02 - let dosInitVersion = 0 //$03 - //$04-05 unused - let volumeNumber = 0 //$06 - //$07-$26 unused - let maxTrackSectorPairs = 0 //$27, should be 122 - //$28-$2F unused - let lastFormattedTrack = 0 //$30 - let trackDirection = 0 //$31 - //$32-$33 unused - let tracksPerDisk = 0 //$34 - let sectorsPerTrack = 0 //$35 - let bytesPerSector = 0 //$36-$37 - } - - let tableOfContents = VTOC() + static let SECTOR_ORDER = [0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15] static func readTrackAndSector(imageData: [UInt8], trackNum: Int, sectorNum: Int) -> [UInt8] { //Find the track in our disk. let trackOffset = trackNum * Dos33Image.BYTES_PER_TRACK //Find the sector in this track. - let sectorOffset = sectorOrder[sectorNum] * Dos33Image.BYTES_PER_SECTOR + let sectorOffset = SECTOR_ORDER[sectorNum] * Dos33Image.BYTES_PER_SECTOR let offset = trackOffset + sectorOffset return Array(imageData[offset ..< offset + Dos33Image.BYTES_PER_SECTOR]) } } -class DiskImage: NSObject { - enum DiskFormat { - case Dos33 - case Prodos - case Raw +class ProdosImage: DiskImageFormat { + static let BYTES_PER_SECTOR: Int = 256 + static let SECTORS_PER_TRACK: Int = 16 + static let TRACKS_PER_DISK: Int = 35 + static let BYTES_PER_TRACK: Int = BYTES_PER_SECTOR * SECTORS_PER_TRACK + + //Sectors in a track are in this order. + static let SECTOR_ORDER = [0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15] + + static func readTrackAndSector(imageData: [UInt8], trackNum: Int, sectorNum: Int) -> [UInt8] { + //Find the track in our disk. + let trackOffset = trackNum * BYTES_PER_TRACK + //Find the sector in this track. + let sectorOffset = SECTOR_ORDER[sectorNum] * BYTES_PER_SECTOR + let offset = trackOffset + sectorOffset + + return Array(imageData[offset ..< offset + BYTES_PER_SECTOR]) } +} + +class DiskImage: NSObject { var encodedTracks = [[UInt8]]() var fileSize: UInt64 = 0 var image: DiskImageFormat? var writeProtect = false + var filename: String + init(diskPath: String) { do { let attr = try FileManager.default.attributesOfItem(atPath: diskPath) @@ -93,6 +82,8 @@ class DiskImage: NSObject { print("Error in DiskImage: \(error)") } + filename = diskPath + super.init() var rawData: [UInt8]? @@ -101,16 +92,39 @@ class DiskImage: NSObject { print("Couldn't load disk image") return } - //Is this a DOS 3.3 format image? Read one sector from track $11. - let catalogSector: [UInt8] = Dos33Image.readTrackAndSector(imageData: rawData!, trackNum: 0x11, sectorNum: 0) + if(filename.contains(".do")) { + //Is this a DOS 3.3 format image? Read one sector from track $11. + image = Dos33Image() + let catalogSector: [UInt8] = Dos33Image.readTrackAndSector(imageData: rawData!, trackNum: 0x11, sectorNum: 0) + for track in 0.. [UInt8]? { @@ -128,9 +142,36 @@ class DiskImage: NSObject { return nil } - private func encodeDos33Track(imageData: [UInt8], index: Int, volumeNumber: Int) -> [UInt8] { + private func decodeTrack(index: Int) -> [UInt8] { + /* Find the first sector. Each sector starts with $D5 $AA $AD */ + let track = encodedTracks[index] + var trackBytes = [UInt8]() + + if(image is Dos33Image) { + for i in 0 ..< Dos33Image.SECTORS_PER_TRACK { + let sectorOffset: Int = 0x47 + (0x18C * Dos33Image.SECTOR_ORDER.index(of: i)!) + let nibbles: [UInt8] = [UInt8](track[sectorOffset ... sectorOffset + 343]) + trackBytes.append(contentsOf: DecodeSectorSixAndTwo(nibbles: nibbles)) + } + } else if(image is ProdosImage) { + for i in 0 ..< ProdosImage.SECTORS_PER_TRACK { + let sectorOffset: Int = 0x47 + (0x18C * ProdosImage.SECTOR_ORDER.index(of: i)!) + let nibbles: [UInt8] = [UInt8](track[sectorOffset ... sectorOffset + 343]) + trackBytes.append(contentsOf: DecodeSectorSixAndTwo(nibbles: nibbles)) + } + } else if(image != nil) { + for i in 0 ..< Dos33Image.SECTORS_PER_TRACK { + let sectorOffset: Int = 0x47 + (0x18C * i) /* If we don't recognize the format, just use a sequential sector order. */ + let nibbles: [UInt8] = [UInt8](track[sectorOffset ... sectorOffset + 343]) + trackBytes.append(contentsOf: DecodeSectorSixAndTwo(nibbles: nibbles)) + } + } + + return trackBytes + } + + private func encodeTrack(imageData: [UInt8], index: Int, volumeNumber: Int) -> [UInt8] { var encodedData = [UInt8]() - let dataOffset = index * Dos33Image.BYTES_PER_TRACK //Prologue: add 48 self-syncing bytes for _ in 1..<0x31 { encodedData.append(selfSync) } @@ -190,6 +231,37 @@ class DiskImage: NSObject { return writtenData } + private func DecodeSectorSixAndTwo(nibbles: [UInt8]) -> [UInt8] { + var sector = [UInt8](repeating: 0x00, count: 256) + var readBuffer = [UInt8](repeating: 0x00, count: 343) + + readBuffer[0x155] = SixAndTwoDecode(byte: nibbles[0]) ^ 0 + + for i in 1 ... 85 { + readBuffer[0x155 - i] = SixAndTwoDecode(byte: nibbles[i]) ^ readBuffer[0x156 - i] + } + + readBuffer[0x000] = SixAndTwoDecode(byte: nibbles[86]) ^ readBuffer[0x100] + + for i in 87 ... 341 { + readBuffer[i - 86] = SixAndTwoDecode(byte: nibbles[i]) ^ readBuffer[i - 87] + } + + var secondaryShift = 0 + for i in 0 ... 255 { + let secondaryOffset = 0x100 + (0x55 - (i % 0x56)) + + sector[i] |= readBuffer[i] << 2 + sector[i] |= GetSwappedLowBits(byte: readBuffer[secondaryOffset] >> secondaryShift) + + if(secondaryOffset == 0x100) { + secondaryShift += 2 + } + } + + return sector + } + private func GetSwappedLowBits(byte: UInt8) -> UInt8 { let b0 = byte & 0b00000001 let b1 = byte & 0b00000010 @@ -235,6 +307,10 @@ class DiskImage: NSObject { return SixAndTwoTranslationTable[Int(byte)] } + func SixAndTwoDecode(byte: UInt8) -> UInt8 { + return UInt8(SixAndTwoTranslationTable.index(of: byte)!) + } + //A group of self-syncing bytes. This pattern can be repeated as long as required. //let selfSyncFive: [UInt8] = [0b11111111, 0b00111111, 0b11001111, 0b11110011, 0b11111100] private let selfSync: UInt8 = 0xFF diff --git a/FruitMachine/AppleIIViewController.swift b/FruitMachine/AppleIIViewController.swift index 3393214..7ba9b19 100644 --- a/FruitMachine/AppleIIViewController.swift +++ b/FruitMachine/AppleIIViewController.swift @@ -64,6 +64,25 @@ class AppleIIViewController: NSViewController { EmulatedSystemInstance!.doReset() } + @IBAction func doColdReset(_ sender: Any) { + EmulatedSystemInstance!.doColdReset() + } + + @IBAction func insertDiskIntoDrive1(_ sender: Any) { + let picker = NSOpenPanel() + + picker.title = "Select a 5.25\" disk image" + picker.showsHiddenFiles = false + picker.canChooseFiles = true + picker.canChooseDirectories = false + picker.allowsMultipleSelection = false + picker.allowedFileTypes = ["do", "po"] + + if(picker.runModal() == .OK) { + (EmulatedSystemInstance!.backplane[6] as! DiskII).attachDiskImage(imagePath: picker.url!.path) + } + } + override func keyDown(with event: NSEvent) { let leftArrowKeyCode = 123 let rightArrowKeyCode = 124 diff --git a/FruitMachine/FruitMachine.storyboard b/FruitMachine/FruitMachine.storyboard index 63da9f2..af9d6c0 100644 --- a/FruitMachine/FruitMachine.storyboard +++ b/FruitMachine/FruitMachine.storyboard @@ -64,13 +64,20 @@ - - + + + + - - + + + + + + + - + @@ -214,7 +221,7 @@ - + diff --git a/FruitMachine/M6502/CPU.swift b/FruitMachine/M6502/CPU.swift index 076b152..b5c03e8 100644 --- a/FruitMachine/M6502/CPU.swift +++ b/FruitMachine/M6502/CPU.swift @@ -179,6 +179,28 @@ final class CPU: NSObject { breakpoints = [UInt16]() } + func coldReset() { + instruction_register = 0 + + accumulator = 0 + index_x = 0 + index_y = 0 + stack_pointer = 0 + program_counter = 0 + status_register = StatusRegister(negative: false, overflow: false, brk: false, decimal: false, irq_disable: false, zero: false, carry: false) + + //Some instructions incur a 1-cycle penalty if crossing a page boundary. + page_boundary_crossed = false + //Branches incur a 1-cycle penalty if taken plus the page boundary penalty if necessary. + branch_was_taken = false + + performReset() + + for address in 0 ..< 0xC000 { + memoryInterface.writeByte(offset: UInt16(address), value: 0x00, bypassOverrides: true) + } + } + func getOperandByte() -> UInt8 { //Returns the operand byte after the current instruction byte. return memoryInterface.readByte(offset: program_counter + 1)