6-and-2 encoding for disk images
reading the volume table of contents from dos 3.3 disk images
This commit is contained in:
parent
ab3c32af3d
commit
897c883837
|
@ -7,6 +7,7 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
2A16ADBB1F33C341004A0333 /* DiskImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A16ADBA1F33C341004A0333 /* DiskImage.swift */; };
|
||||||
2A2126841F2A9FA300E43DC1 /* DebuggerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */; };
|
2A2126841F2A9FA300E43DC1 /* DebuggerWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */; };
|
||||||
2A22EBFB1F21A7A700A36A61 /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */; };
|
2A22EBFB1F21A7A700A36A61 /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */; };
|
||||||
2A5BC5191F29A28D008C03BE /* AppleIScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BC5181F29A28D008C03BE /* AppleIScreenView.swift */; };
|
2A5BC5191F29A28D008C03BE /* AppleIScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5BC5181F29A28D008C03BE /* AppleIScreenView.swift */; };
|
||||||
|
@ -55,6 +56,7 @@
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
2A16ADBA1F33C341004A0333 /* DiskImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiskImage.swift; sourceTree = "<group>"; };
|
||||||
2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggerWindowController.swift; sourceTree = "<group>"; };
|
2A2126831F2A9FA300E43DC1 /* DebuggerWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebuggerWindowController.swift; sourceTree = "<group>"; };
|
||||||
2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerExtensions.swift; sourceTree = "<group>"; };
|
2A22EBFA1F21A7A700A36A61 /* IntegerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerExtensions.swift; sourceTree = "<group>"; };
|
||||||
2A5BC5181F29A28D008C03BE /* AppleIScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIScreenView.swift; sourceTree = "<group>"; };
|
2A5BC5181F29A28D008C03BE /* AppleIScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIScreenView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -117,6 +119,15 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
2A16ADB91F33C2ED004A0333 /* DiskII */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
2A63C2391F32CCE900D4F4F8 /* DiskII.swift */,
|
||||||
|
2A16ADBA1F33C341004A0333 /* DiskImage.swift */,
|
||||||
|
);
|
||||||
|
path = DiskII;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
2A5BC51A1F29A2EB008C03BE /* Frameworks */ = {
|
2A5BC51A1F29A2EB008C03BE /* Frameworks */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -138,7 +149,7 @@
|
||||||
2A63C2381F32CCDB00D4F4F8 /* Peripherals */ = {
|
2A63C2381F32CCDB00D4F4F8 /* Peripherals */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
2A63C2391F32CCE900D4F4F8 /* DiskII.swift */,
|
2A16ADB91F33C2ED004A0333 /* DiskII */,
|
||||||
2A63C23B1F32CD4800D4F4F8 /* Peripheral.swift */,
|
2A63C23B1F32CD4800D4F4F8 /* Peripheral.swift */,
|
||||||
);
|
);
|
||||||
path = Peripherals;
|
path = Peripherals;
|
||||||
|
@ -398,6 +409,7 @@
|
||||||
2AA8B5FC1F2A8EAD002B350F /* Terminal.swift in Sources */,
|
2AA8B5FC1F2A8EAD002B350F /* Terminal.swift in Sources */,
|
||||||
2A5BC51E1F29A4C3008C03BE /* AppleIBitmapDisplay.swift in Sources */,
|
2A5BC51E1F29A4C3008C03BE /* AppleIBitmapDisplay.swift in Sources */,
|
||||||
2AD458CE1F205EB700F05121 /* AppDelegate.swift in Sources */,
|
2AD458CE1F205EB700F05121 /* AppDelegate.swift in Sources */,
|
||||||
|
2A16ADBB1F33C341004A0333 /* DiskImage.swift in Sources */,
|
||||||
2A91852A1F2EA84D00A9E5BE /* BitmapPixels.swift in Sources */,
|
2A91852A1F2EA84D00A9E5BE /* BitmapPixels.swift in Sources */,
|
||||||
2AE42E431F28665300C4900E /* A1CharacterGenerator.swift in Sources */,
|
2AE42E431F28665300C4900E /* A1CharacterGenerator.swift in Sources */,
|
||||||
2A6DC7F01F30495D0066FE0D /* ScreenView.swift in Sources */,
|
2A6DC7F01F30495D0066FE0D /* ScreenView.swift in Sources */,
|
||||||
|
|
|
@ -26,7 +26,7 @@ final class AppleII: NSObject, EmulatedSystem {
|
||||||
var CYCLES_PER_BATCH: Int
|
var CYCLES_PER_BATCH: Int
|
||||||
|
|
||||||
let emulatorViewDelegate = AppleII.ScreenDelegate()
|
let emulatorViewDelegate = AppleII.ScreenDelegate()
|
||||||
let emulatorView = AppleII.ScreenView(frame: NSMakeRect(0, 0, 560, 384))
|
let emulatorView = AppleII.ScreenView(frame: NSMakeRect(0, 16, 560, 384))
|
||||||
let emuScreenLayer = CALayer()
|
let emuScreenLayer = CALayer()
|
||||||
|
|
||||||
required init(cpuFrequency: Double, fps: Double) {
|
required init(cpuFrequency: Double, fps: Double) {
|
||||||
|
@ -39,12 +39,12 @@ final class AppleII: NSObject, EmulatedSystem {
|
||||||
for i in 1...7 {
|
for i in 1...7 {
|
||||||
backplane[i] = nil
|
backplane[i] = nil
|
||||||
}
|
}
|
||||||
|
backplane[6] = DiskII(slot: 6, romPath: "/Users/luigi/apple2/341-0027-a.p5")
|
||||||
|
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
loadROMs()
|
loadROMs()
|
||||||
setupMemory(ramConfig: .sixteenK)
|
setupMemory(ramConfig: .sixteenK)
|
||||||
backplane[6] = DiskII(slot: 6, romPath: "/Users/luigi/apple2/341-0027-a.p5")
|
|
||||||
|
|
||||||
emuScreenLayer.shouldRasterize = true
|
emuScreenLayer.shouldRasterize = true
|
||||||
emuScreenLayer.delegate = emulatorViewDelegate
|
emuScreenLayer.delegate = emulatorViewDelegate
|
||||||
|
@ -199,7 +199,7 @@ final class AppleII: NSObject, EmulatedSystem {
|
||||||
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.null //not connected
|
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.null //not connected
|
||||||
}
|
}
|
||||||
for page in 224 ..< 256 {
|
for page in 224 ..< 256 {
|
||||||
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.ro //not connected
|
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.ro //ROM
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
//
|
|
||||||
// DiskII.swift
|
|
||||||
// FruitMachine
|
|
||||||
//
|
|
||||||
// Created by Christopher Rohl on 8/2/17.
|
|
||||||
// Copyright © 2017 Christopher Rohl. All rights reserved.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Cocoa
|
|
||||||
|
|
||||||
class DiskII: NSObject, Peripheral {
|
|
||||||
let slotNumber: Int
|
|
||||||
let romManager: ROMManager
|
|
||||||
var readMemoryOverride: ReadOverride? = nil
|
|
||||||
var writeMemoryOverride: WriteOverride? = nil
|
|
||||||
|
|
||||||
init(slot: Int, romPath: String) {
|
|
||||||
slotNumber = slot
|
|
||||||
romManager = ROMManager(path: romPath, atAddress: 0x0, size: 256)
|
|
||||||
|
|
||||||
super.init()
|
|
||||||
|
|
||||||
readMemoryOverride = ReadOverride(start: UInt16(0xC000 + (0x100 * slotNumber)),
|
|
||||||
end: UInt16(0xC0FF + (0x100 * slotNumber)),
|
|
||||||
readAnyway: false,
|
|
||||||
action: actionReadMemory)
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
//Disk II just maps its ROM to the memory addressed by the slot.
|
|
||||||
|
|
||||||
return romManager.ROM[Int(address)]
|
|
||||||
}
|
|
||||||
|
|
||||||
func installOverrides() {
|
|
||||||
CPU.sharedInstance.memoryInterface.read_overrides.append(readMemoryOverride!)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
//
|
||||||
|
// DiskII.swift
|
||||||
|
// FruitMachine
|
||||||
|
//
|
||||||
|
// Created by Christopher Rohl on 8/2/17.
|
||||||
|
// Copyright © 2017 Christopher Rohl. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DiskII: NSObject, Peripheral {
|
||||||
|
enum MotorPhase {
|
||||||
|
case Phase0
|
||||||
|
case Phase1
|
||||||
|
case Phase2
|
||||||
|
case Phase3
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Notifications */
|
||||||
|
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")
|
||||||
|
|
||||||
|
/* Softswitches */
|
||||||
|
struct Softswitches {
|
||||||
|
var Phase0 = false
|
||||||
|
var Phase1 = false
|
||||||
|
var Phase2 = false
|
||||||
|
var Phase3 = false
|
||||||
|
var MotorPowered = false
|
||||||
|
var DriveSelect = false //false = 1, true = 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
|
||||||
|
var motorPhase: MotorPhase = .Phase0
|
||||||
|
|
||||||
|
var diskImage = DiskImage(diskPath: "/Users/luigi/apple2/master.dsk")
|
||||||
|
|
||||||
|
init(slot: Int, romPath: String) {
|
||||||
|
slotNumber = slot
|
||||||
|
romManager = ROMManager(path: romPath, atAddress: 0x0, size: 256)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
//http://ftp.twaren.net/NetBSD/misc/wrstuden/Apple_PDFs/Software%20control%20of%20IWM.pdf
|
||||||
|
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)")
|
||||||
|
|
||||||
|
//Update the softswitches.
|
||||||
|
switch(operationNumber) {
|
||||||
|
case 0:
|
||||||
|
softswitches.Phase0 = false
|
||||||
|
case 1:
|
||||||
|
softswitches.Phase0 = true
|
||||||
|
if(motorPhase == .Phase1) {
|
||||||
|
motorPhase = .Phase0
|
||||||
|
if(currentTrack % 2 == 0) {
|
||||||
|
if(currentTrack >= 0) { currentTrack -= 1 }
|
||||||
|
}
|
||||||
|
} else if(motorPhase == .Phase3) {
|
||||||
|
motorPhase = .Phase0
|
||||||
|
if(currentTrack % 2 == 1) {
|
||||||
|
if(currentTrack <= 34) { currentTrack += 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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
|
||||||
|
if(currentTrack % 2 == 0) {
|
||||||
|
if(currentTrack >= 0) { currentTrack -= 1 }
|
||||||
|
}
|
||||||
|
} else if(motorPhase == .Phase1) {
|
||||||
|
motorPhase = .Phase2
|
||||||
|
if(currentTrack % 2 == 0) {
|
||||||
|
if(currentTrack <= 34) { currentTrack += 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
softswitches.Phase3 = false
|
||||||
|
case 7:
|
||||||
|
softswitches.Phase3 = true
|
||||||
|
if(motorPhase == .Phase0 || motorPhase == .Phase2) {
|
||||||
|
motorPhase = .Phase3
|
||||||
|
}
|
||||||
|
case 8:
|
||||||
|
softswitches.MotorPowered = false
|
||||||
|
if(softswitches.DriveSelect == false) {
|
||||||
|
NotificationCenter.default.post(name: DiskII.N_Drive1MotorOff, object: nil)
|
||||||
|
} else {
|
||||||
|
NotificationCenter.default.post(name: DiskII.N_Drive2MotorOff, object: nil)
|
||||||
|
}
|
||||||
|
case 9:
|
||||||
|
softswitches.MotorPowered = true
|
||||||
|
if(softswitches.DriveSelect == false) {
|
||||||
|
NotificationCenter.default.post(name: DiskII.N_Drive1MotorOn, object: nil)
|
||||||
|
} else {
|
||||||
|
NotificationCenter.default.post(name: DiskII.N_Drive2MotorOn, object: nil)
|
||||||
|
}
|
||||||
|
case 10:
|
||||||
|
softswitches.DriveSelect = false
|
||||||
|
case 11:
|
||||||
|
softswitches.DriveSelect = true
|
||||||
|
case 12:
|
||||||
|
softswitches.Q6 = false
|
||||||
|
if(byte == nil) { //read
|
||||||
|
//write to Q6L -> read byte
|
||||||
|
}
|
||||||
|
case 13:
|
||||||
|
softswitches.Q6 = true
|
||||||
|
case 14:
|
||||||
|
softswitches.Q7 = false
|
||||||
|
case 15:
|
||||||
|
softswitches.Q7 = true
|
||||||
|
default:
|
||||||
|
print("Unknown command? This can't happen.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0x00
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
//Disk II just maps its ROM to the memory addressed by the slot.
|
||||||
|
|
||||||
|
return romManager.ROM[Int(address)]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,248 @@
|
||||||
|
//
|
||||||
|
// DosFormat.swift
|
||||||
|
// FruitMachine
|
||||||
|
//
|
||||||
|
// Created by Christopher Rohl on 8/3/17.
|
||||||
|
// Copyright © 2017 Christopher Rohl. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Cocoa
|
||||||
|
|
||||||
|
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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
class Dos33Image: 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 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 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.index(of: sectorNum)! * Dos33Image.BYTES_PER_SECTOR
|
||||||
|
let offset = trackOffset + sectorOffset
|
||||||
|
|
||||||
|
return Array<UInt8>(imageData[offset ..< offset + Dos33Image.BYTES_PER_SECTOR])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiskImage: NSObject {
|
||||||
|
enum DiskFormat {
|
||||||
|
case Dos33
|
||||||
|
case Prodos
|
||||||
|
case Raw
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodedTracks = [UInt8]()
|
||||||
|
var fileSize: UInt64 = 0
|
||||||
|
var image: DiskImageFormat?
|
||||||
|
|
||||||
|
init(diskPath: String) {
|
||||||
|
do {
|
||||||
|
let attr = try FileManager.default.attributesOfItem(atPath: diskPath)
|
||||||
|
fileSize = attr[FileAttributeKey.size] as! UInt64
|
||||||
|
} catch {
|
||||||
|
print("Error in DiskImage: \(error)")
|
||||||
|
}
|
||||||
|
|
||||||
|
super.init()
|
||||||
|
var rawData: [UInt8]?
|
||||||
|
|
||||||
|
rawData = loadImageBytes(path: diskPath, size: Int(fileSize))
|
||||||
|
if(rawData == nil) {
|
||||||
|
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)
|
||||||
|
|
||||||
|
encodeDos33Track(imageData: rawData!, index: 0, volumeNumber: Int(catalogSector[0x06]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadImageBytes(path: String, size: Int) -> [UInt8]? {
|
||||||
|
do {
|
||||||
|
var data = [UInt8](repeating: 0xCC, count: Int(fileSize))
|
||||||
|
|
||||||
|
let fileContent: NSData = try NSData(contentsOfFile: path)
|
||||||
|
fileContent.getBytes(&data, range: NSRange(location: 0, length: Int(size)))
|
||||||
|
|
||||||
|
return data
|
||||||
|
} catch {
|
||||||
|
print(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func encodeDos33Track(imageData: [UInt8], index: Int, volumeNumber: Int) {
|
||||||
|
var encodedData = [UInt8]()
|
||||||
|
let dataOffset = index * Dos33Image.BYTES_PER_TRACK
|
||||||
|
|
||||||
|
//Prologue: add 48 self-syncing bytes
|
||||||
|
for _ in 0..<0x30 { encodedData.append(selfSync) }
|
||||||
|
|
||||||
|
for sectorNum in 0 ..< Dos33Image.SECTORS_PER_TRACK {
|
||||||
|
//Address Field
|
||||||
|
encodedData.append(contentsOf: addressPrologue)
|
||||||
|
encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(volumeNumber)))) //Volume byte
|
||||||
|
encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(index)))) //Track number
|
||||||
|
encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(sectorNum)))) //Sector number
|
||||||
|
let checksum: UInt8 = UInt8(volumeNumber) ^ UInt8(index) ^ UInt8(sectorNum)
|
||||||
|
encodedData.append(contentsOf: UInt16toUInt8Array(word: FourAndFourEncode(byte: UInt8(checksum)))) //Checksum value
|
||||||
|
encodedData.append(contentsOf: addressEpilogue)
|
||||||
|
|
||||||
|
//Gap2 - 5 bytes
|
||||||
|
for _ in 0..<5 { encodedData.append(selfSync) }
|
||||||
|
|
||||||
|
//Data Field
|
||||||
|
encodedData.append(contentsOf: dataPrologue)
|
||||||
|
encodedData.append(contentsOf: EncodeSectorSixAndTwo(sector: Dos33Image.readTrackAndSector(imageData: imageData, trackNum: index, sectorNum: sectorNum)))
|
||||||
|
encodedData.append(contentsOf: dataEpilogue)
|
||||||
|
|
||||||
|
//Gap2
|
||||||
|
for _ in 0..<5 { encodedData.append(selfSync) }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func UInt16toUInt8Array(word: UInt16) -> [UInt8] {
|
||||||
|
var r = [UInt8]()
|
||||||
|
r.append(UInt8((word & 0xFF00) >> 8))
|
||||||
|
r.append(UInt8(word & 0x00FF))
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncodeSectorSixAndTwo(sector: [UInt8]) -> [UInt8] {
|
||||||
|
let encodedBuffer = SixAndTwoPrenibblize(sector: sector)
|
||||||
|
var writtenData = [UInt8](repeating: 0x00, count: 343)
|
||||||
|
|
||||||
|
//We have a prepared buffer.
|
||||||
|
writtenData[0] = SixAndTwoTranslationTable[Int(0 ^ encodedBuffer[0x155])]
|
||||||
|
writtenData[86] = SixAndTwoTranslationTable[Int(encodedBuffer[0x100] ^ encodedBuffer[0x00])]
|
||||||
|
|
||||||
|
for index in 0x00 ... 0xFE {
|
||||||
|
writtenData[87 + index] = SixAndTwoTranslationTable[Int(encodedBuffer[index] ^ encodedBuffer[index + 1])]
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, index) in (0x100 ... 0x154).enumerated() {
|
||||||
|
writtenData[85-i] = SixAndTwoTranslationTable[Int(encodedBuffer[index] ^ encodedBuffer[index + 1])]
|
||||||
|
}
|
||||||
|
|
||||||
|
writtenData[342] = SixAndTwoTranslationTable[Int(encodedBuffer[0xFF])]
|
||||||
|
|
||||||
|
return writtenData
|
||||||
|
}
|
||||||
|
|
||||||
|
func SixAndTwoPrenibblize(sector: [UInt8]) -> [UInt8] {
|
||||||
|
//Create a 342-byte buffer from a 256-byte sector.
|
||||||
|
var nibblized: [UInt8] = [UInt8](repeating: 0x00, count: 342)
|
||||||
|
|
||||||
|
for byte in 0x00...0x55 {
|
||||||
|
//nibblized[byte] = SixAndTwoTranslationTable[Int(sector[byte] >> 2)]
|
||||||
|
nibblized[byte] = sector[byte] >> 2
|
||||||
|
let b0 = (sector[byte] & 0b00000001)
|
||||||
|
let b1 = (sector[byte] & 0b00000010)
|
||||||
|
let low = 0x00 | (b0 << 1 | b1 >> 1)
|
||||||
|
|
||||||
|
nibblized[0x155 - byte] |= low
|
||||||
|
}
|
||||||
|
|
||||||
|
for byte in 0x56...0xAA {
|
||||||
|
//nibblized[byte] = SixAndTwoTranslationTable[Int(sector[byte] >> 2)]
|
||||||
|
nibblized[byte] = sector[byte] >> 2
|
||||||
|
let b0 = (sector[byte] & 0b00000001)
|
||||||
|
let b1 = (sector[byte] & 0b00000010)
|
||||||
|
let low = (b0 << 1 | b1 >> 1)
|
||||||
|
|
||||||
|
nibblized[0x155 - (byte % 0x56)] |= (low << 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
for byte in 0xAB...0xFF {
|
||||||
|
//nibblized[byte] = SixAndTwoTranslationTable[Int(sector[byte] >> 2)]
|
||||||
|
nibblized[byte] = sector[byte] >> 2
|
||||||
|
let b0 = (sector[byte] & 0b00000001)
|
||||||
|
let b1 = (sector[byte] & 0b00000010)
|
||||||
|
let low = (b0 << 1 | b1 >> 1)
|
||||||
|
|
||||||
|
//Now we have a full six bits.
|
||||||
|
let completeLow: UInt8 = nibblized[0x155 - (byte % 0x56)] | (low << 4)
|
||||||
|
//nibblized[0x155 - (byte % 0x56)] = SixAndTwoTranslationTable[Int(completeLow)]
|
||||||
|
nibblized[0x155 - (byte % 0x56)] = completeLow
|
||||||
|
}
|
||||||
|
|
||||||
|
return nibblized
|
||||||
|
}
|
||||||
|
|
||||||
|
//Convert bytes to the different encoding schemes.
|
||||||
|
func FourAndFourEncode(byte: UInt8) -> UInt16 {
|
||||||
|
/*
|
||||||
|
4 and 4 encoded bytes require two bytes (by splitting actual bits
|
||||||
|
evenly between two bytes) and have the following format:
|
||||||
|
|
||||||
|
1 b7 1 b5 1 b3 1 b1
|
||||||
|
1 b6 1 b4 1 b2 1 b0
|
||||||
|
*/
|
||||||
|
var encoded: UInt16 = 0
|
||||||
|
|
||||||
|
let hi: UInt16 = UInt16((byte >> 1) | 0b10101010)
|
||||||
|
let lo: UInt16 = UInt16(byte | 0b10101010)
|
||||||
|
|
||||||
|
encoded = (hi << 8) | lo
|
||||||
|
return encoded
|
||||||
|
}
|
||||||
|
|
||||||
|
func SixAndTwoEncode(byte: UInt8) -> UInt8 {
|
||||||
|
return SixAndTwoTranslationTable[Int(byte)]
|
||||||
|
}
|
||||||
|
|
||||||
|
//A group of self-syncing bytes. This pattern can be repeated as long as required.
|
||||||
|
//let selfSyncFive: [UInt8] = [0b11111111, 0b00111111, 0b11001111, 0b11110011, 0b11111100]
|
||||||
|
let selfSync: UInt8 = 0xFF
|
||||||
|
|
||||||
|
let addressPrologue: [UInt8] = [0xD5, 0xAA, 0x96]
|
||||||
|
let addressEpilogue: [UInt8] = [0xDE, 0xAA, 0xEB]
|
||||||
|
|
||||||
|
let dataPrologue: [UInt8] = [0xD5, 0xAA, 0xAD]
|
||||||
|
let dataEpilogue: [UInt8] = [0xDE, 0xAA, 0xEB]
|
||||||
|
|
||||||
|
let SixAndTwoTranslationTable: [UInt8] = [0x96, 0x97, 0x9A, 0x9B, 0x9D, 0x9E, 0x9F, 0xA6,
|
||||||
|
0xA7, 0xab, 0xac, 0xad, 0xae, 0xaf, 0xb2, 0xb3,
|
||||||
|
0xb4, 0xb5, 0xb6, 0xb7, 0xb9, 0xba, 0xbb, 0xbc,
|
||||||
|
0xbd, 0xbe, 0xbf, 0xcb, 0xcd, 0xce, 0xcf, 0xd3,
|
||||||
|
0xd6, 0xd7, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde,
|
||||||
|
0xdf, 0xe5, 0xe6, 0xe7, 0xe9, 0xea, 0xeb, 0xec,
|
||||||
|
0xed, 0xee, 0xef, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
|
||||||
|
0xf7, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff]
|
||||||
|
}
|
|
@ -10,6 +10,9 @@ import Cocoa
|
||||||
|
|
||||||
class AppleIIViewController: NSViewController {
|
class AppleIIViewController: NSViewController {
|
||||||
|
|
||||||
|
@IBOutlet weak var lbl_Drive1: NSTextField!
|
||||||
|
@IBOutlet weak var lbl_Drive2: NSTextField!
|
||||||
|
|
||||||
let computer = AppleII.sharedInstance
|
let computer = AppleII.sharedInstance
|
||||||
var debuggerWindowController: DebuggerWindowController!
|
var debuggerWindowController: DebuggerWindowController!
|
||||||
var preferencesWindowController: PreferencesWindowController!
|
var preferencesWindowController: PreferencesWindowController!
|
||||||
|
@ -25,6 +28,8 @@ class AppleIIViewController: NSViewController {
|
||||||
|
|
||||||
self.view.addSubview(computer.emulatorView)
|
self.view.addSubview(computer.emulatorView)
|
||||||
|
|
||||||
|
setupDriveNotifications()
|
||||||
|
|
||||||
self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0,
|
self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0,
|
||||||
target: self,
|
target: self,
|
||||||
selector: #selector(runEmulation),
|
selector: #selector(runEmulation),
|
||||||
|
@ -81,4 +86,28 @@ class AppleIIViewController: NSViewController {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setupDriveNotifications() {
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(self.drive1MotorOn), name: DiskII.N_Drive1MotorOn, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(self.drive2MotorOn), name: DiskII.N_Drive2MotorOn, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(self.drive1MotorOff), name: DiskII.N_Drive1MotorOff, object: nil)
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(self.drive2MotorOff), name: DiskII.N_Drive2MotorOff, object: nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* drive lights */
|
||||||
|
@objc func drive1MotorOff(notification: NSNotification) {
|
||||||
|
lbl_Drive1.textColor = NSColor.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func drive2MotorOff(notification: NSNotification) {
|
||||||
|
lbl_Drive2.textColor = NSColor.textColor
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func drive1MotorOn(notification: NSNotification) {
|
||||||
|
lbl_Drive1.textColor = NSColor.red
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func drive2MotorOn(notification: NSNotification) {
|
||||||
|
lbl_Drive2.textColor = NSColor.red
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,10 +157,10 @@
|
||||||
<windowController id="0Wh-dg-gsU" sceneMemberID="viewController">
|
<windowController id="0Wh-dg-gsU" sceneMemberID="viewController">
|
||||||
<window key="window" title="Fruit Machine" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" frameAutosaveName="" animationBehavior="default" id="O0r-h6-7La">
|
<window key="window" title="Fruit Machine" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" frameAutosaveName="" animationBehavior="default" id="O0r-h6-7La">
|
||||||
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
|
||||||
<rect key="contentRect" x="293" y="264" width="560" height="360"/>
|
<rect key="contentRect" x="293" y="264" width="560" height="400"/>
|
||||||
<rect key="screenRect" x="0.0" y="0.0" width="1916" height="936"/>
|
<rect key="screenRect" x="0.0" y="0.0" width="1916" height="936"/>
|
||||||
<value key="minSize" type="size" width="560" height="360"/>
|
<value key="minSize" type="size" width="560" height="400"/>
|
||||||
<value key="maxSize" type="size" width="560" height="360"/>
|
<value key="maxSize" type="size" width="560" height="400"/>
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="delegate" destination="0Wh-dg-gsU" id="tmJ-Ae-YJP"/>
|
<outlet property="delegate" destination="0Wh-dg-gsU" id="tmJ-Ae-YJP"/>
|
||||||
</connections>
|
</connections>
|
||||||
|
@ -178,9 +178,33 @@
|
||||||
<objects>
|
<objects>
|
||||||
<viewController id="gw1-M7-9Je" customClass="AppleIIViewController" customModule="FruitMachine" customModuleProvider="target" sceneMemberID="viewController">
|
<viewController id="gw1-M7-9Je" customClass="AppleIIViewController" customModule="FruitMachine" customModuleProvider="target" sceneMemberID="viewController">
|
||||||
<view key="view" id="DiY-xb-Usb">
|
<view key="view" id="DiY-xb-Usb">
|
||||||
<rect key="frame" x="0.0" y="0.0" width="560" height="384"/>
|
<rect key="frame" x="0.0" y="0.0" width="560" height="400"/>
|
||||||
<autoresizingMask key="autoresizingMask"/>
|
<autoresizingMask key="autoresizingMask"/>
|
||||||
|
<subviews>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="jaO-yO-mV1">
|
||||||
|
<rect key="frame" x="446" y="0.0" width="46" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Drive 1" id="Yaa-0g-KnI">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Xl9-rO-Y9T">
|
||||||
|
<rect key="frame" x="496" y="0.0" width="46" height="17"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||||
|
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Drive 2" id="Kaf-uu-AhW">
|
||||||
|
<font key="font" metaFont="system"/>
|
||||||
|
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||||
|
</textFieldCell>
|
||||||
|
</textField>
|
||||||
|
</subviews>
|
||||||
</view>
|
</view>
|
||||||
|
<connections>
|
||||||
|
<outlet property="lbl_Drive1" destination="jaO-yO-mV1" id="IR7-NA-vZH"/>
|
||||||
|
<outlet property="lbl_Drive2" destination="Xl9-rO-Y9T" id="E1A-nb-rM5"/>
|
||||||
|
</connections>
|
||||||
</viewController>
|
</viewController>
|
||||||
<customObject id="o4k-Ip-qpX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
<customObject id="o4k-Ip-qpX" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
|
||||||
</objects>
|
</objects>
|
||||||
|
|
Loading…
Reference in New Issue