2017-08-01 06:02:49 +00:00
|
|
|
//
|
|
|
|
// AppleII.swift
|
|
|
|
// FruitMachine
|
|
|
|
//
|
|
|
|
// Created by Christopher Rohl on 8/1/17.
|
|
|
|
// Copyright © 2017 Christopher Rohl. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
|
2017-08-03 04:31:12 +00:00
|
|
|
final class AppleII: NSObject, EmulatedSystem {
|
2017-08-01 06:02:49 +00:00
|
|
|
static let sharedInstance = AppleII(cpuFrequency: (14.31818 / 7 / 2) * 1000000, fps: 60.0)
|
|
|
|
|
2017-08-01 21:40:01 +00:00
|
|
|
var frameCounter: Int = 0
|
|
|
|
|
2017-08-01 07:28:26 +00:00
|
|
|
let cg = A2CharacterGenerator(romPath: "/Users/luigi/apple2/a2.chr");
|
2017-08-02 08:01:06 +00:00
|
|
|
let keyboardController = KeyboardController()
|
2017-08-02 22:54:50 +00:00
|
|
|
var videoSoftswitches = VideoSoftswitches()
|
|
|
|
var videoMode: VideoMode
|
2017-08-01 07:28:26 +00:00
|
|
|
|
2017-08-03 04:31:12 +00:00
|
|
|
//Peripherals
|
|
|
|
var backplane = [Int: Peripheral?]()
|
|
|
|
|
2017-08-01 06:02:49 +00:00
|
|
|
var CPU_FREQUENCY: Double
|
|
|
|
var FRAMES_PER_SECOND: Double
|
|
|
|
var CYCLES_PER_BATCH: Int
|
|
|
|
|
|
|
|
let emulatorViewDelegate = AppleII.ScreenDelegate()
|
2017-08-04 02:53:36 +00:00
|
|
|
let emulatorView = AppleII.ScreenView(frame: NSMakeRect(0, 16, 560, 384))
|
2017-08-01 06:02:49 +00:00
|
|
|
let emuScreenLayer = CALayer()
|
|
|
|
|
|
|
|
required init(cpuFrequency: Double, fps: Double) {
|
|
|
|
CPU_FREQUENCY = cpuFrequency
|
|
|
|
FRAMES_PER_SECOND = fps
|
|
|
|
CYCLES_PER_BATCH = Int(cpuFrequency / fps)
|
2017-08-02 22:54:50 +00:00
|
|
|
|
|
|
|
videoMode = .Text
|
|
|
|
|
2017-08-07 00:16:25 +00:00
|
|
|
for i in 0...7 {
|
2017-08-03 04:31:12 +00:00
|
|
|
backplane[i] = nil
|
|
|
|
}
|
|
|
|
|
2017-08-01 06:02:49 +00:00
|
|
|
super.init()
|
|
|
|
|
2017-08-04 07:20:17 +00:00
|
|
|
setupMemory(ramConfig: .fortyeightK)
|
2017-08-07 00:16:25 +00:00
|
|
|
setupPeripherals()
|
|
|
|
loadROMs()
|
2017-08-01 06:02:49 +00:00
|
|
|
|
|
|
|
emuScreenLayer.shouldRasterize = true
|
|
|
|
emuScreenLayer.delegate = emulatorViewDelegate
|
|
|
|
emuScreenLayer.frame = emulatorView.bounds
|
|
|
|
|
|
|
|
emulatorView.wantsLayer = true
|
|
|
|
|
2017-08-07 00:16:25 +00:00
|
|
|
//emuScreenLayer.setNeedsDisplay()
|
2017-08-01 06:02:49 +00:00
|
|
|
emulatorView.layer?.addSublayer(emuScreenLayer)
|
2017-08-05 07:29:07 +00:00
|
|
|
|
2017-08-01 06:02:49 +00:00
|
|
|
installOverrides()
|
2017-08-01 07:28:26 +00:00
|
|
|
|
2017-08-02 08:01:06 +00:00
|
|
|
doReset()
|
|
|
|
}
|
|
|
|
|
|
|
|
func doReset() {
|
2017-08-02 22:54:50 +00:00
|
|
|
videoSoftswitches.reset()
|
|
|
|
videoMode = .Text
|
2017-08-01 06:02:49 +00:00
|
|
|
CPU.sharedInstance.performReset()
|
|
|
|
}
|
|
|
|
|
|
|
|
func loadROMs() {
|
|
|
|
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)
|
2017-08-07 00:16:25 +00:00
|
|
|
|
|
|
|
//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)
|
|
|
|
}
|
2017-08-01 06:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func installOverrides() {
|
2017-08-07 00:16:25 +00:00
|
|
|
for (_, peripheral) in backplane {
|
2017-08-03 04:31:12 +00:00
|
|
|
if(peripheral != nil) {
|
|
|
|
peripheral!.installOverrides()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-01 21:40:01 +00:00
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.readKeyboard)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.clearKeypressStrobeR)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.clearKeypressStrobeW)
|
2017-08-02 22:54:50 +00:00
|
|
|
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC050R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC051R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC052R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC053R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC054R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC055R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC056R)
|
|
|
|
CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.switchC057R)
|
|
|
|
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC050W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC051W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC052W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC053W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC054W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC055W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC056W)
|
|
|
|
CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.switchC057W)
|
2017-08-01 06:02:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func runFrame() {
|
2017-08-01 21:40:01 +00:00
|
|
|
frameCounter = (frameCounter + 1) % 60
|
|
|
|
if(frameCounter % 15) == 0 {
|
|
|
|
emulatorViewDelegate.flashIsInverse = !emulatorViewDelegate.flashIsInverse
|
|
|
|
}
|
|
|
|
|
2017-08-01 06:02:49 +00:00
|
|
|
CPU.sharedInstance.cycles = 0
|
|
|
|
CPU.sharedInstance.cyclesInBatch = CYCLES_PER_BATCH
|
|
|
|
CPU.sharedInstance.runCyclesBatch()
|
|
|
|
|
2017-08-01 07:28:26 +00:00
|
|
|
//update the video display
|
|
|
|
CVPixelBufferLockBaseAddress(emulatorViewDelegate.pixels!, CVPixelBufferLockFlags(rawValue: 0))
|
|
|
|
let pixelBase = CVPixelBufferGetBaseAddress(emulatorViewDelegate.pixels!)
|
2017-08-02 22:54:50 +00:00
|
|
|
let buf = pixelBase?.assumingMemoryBound(to: BitmapPixelsLE555.PixelData.self)
|
|
|
|
|
|
|
|
videoMode = getCurrentVideoMode(switches: videoSoftswitches)
|
|
|
|
|
|
|
|
if(videoMode == .Text)
|
|
|
|
{
|
|
|
|
//Text mode: Get character codes from $0400-$07FF
|
|
|
|
putGlyphs(buffer: buf!, start: 0x400, end: 0x7F8)
|
|
|
|
}
|
|
|
|
else if(videoMode == .Lores)
|
|
|
|
{
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x400, end: 0x7F8)
|
|
|
|
}
|
|
|
|
else if(videoMode == .MixedLores) {
|
|
|
|
//Draw the lores pixel rows.
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x400, end: 0x650)
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x680, end: 0x6A8)
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x700, end: 0x728)
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x780, end: 0x7A8)
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x6A8, end: 0x6D0)
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x728, end: 0x750)
|
|
|
|
putLoresPixels(buffer: buf!, start: 0x7A8, end: 0x7D0)
|
|
|
|
|
|
|
|
//Draw the bottom 4 text rows.
|
|
|
|
putGlyphs(buffer: buf!, start: 0x650, end: 0x678)
|
|
|
|
putGlyphs(buffer: buf!, start: 0x6D0, end: 0x6F8)
|
|
|
|
putGlyphs(buffer: buf!, start: 0x750, end: 0x778)
|
|
|
|
putGlyphs(buffer: buf!, start: 0x7D0, end: 0x7F8)
|
|
|
|
} else {
|
|
|
|
print("Unimplemented video mode!")
|
|
|
|
}
|
|
|
|
|
2017-08-01 07:28:26 +00:00
|
|
|
|
2017-08-02 22:54:50 +00:00
|
|
|
CVPixelBufferUnlockBaseAddress(emulatorViewDelegate.pixels!, CVPixelBufferLockFlags(rawValue: 0))
|
2017-08-04 21:06:03 +00:00
|
|
|
emulatorView.setNeedsDisplay(emulatorView.frame)
|
2017-08-02 22:54:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func putLoresPixels(buffer: UnsafeMutablePointer<BitmapPixelsLE555.PixelData>, start: UInt16, end: UInt16) {
|
|
|
|
for address in start ..< end {
|
|
|
|
let pixelData = CPU.sharedInstance.memoryInterface.readByte(offset: UInt16(address), bypassOverrides: true)
|
|
|
|
|
2017-08-03 04:31:12 +00:00
|
|
|
LoresMode.putLoresPixel(buffer: buffer,
|
2017-08-02 22:54:50 +00:00
|
|
|
pixel: pixelData,
|
|
|
|
address: UInt16(address))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func putGlyphs(buffer: UnsafeMutablePointer<BitmapPixelsLE555.PixelData>, start: UInt16, end: UInt16) {
|
|
|
|
for address in start ... end {
|
2017-08-01 07:28:26 +00:00
|
|
|
let charCode = CPU.sharedInstance.memoryInterface.readByte(offset: UInt16(address), bypassOverrides: true)
|
|
|
|
|
2017-08-03 04:31:12 +00:00
|
|
|
TextMode.putGlyph(buffer: buffer,
|
|
|
|
glyph: cg.glyphs[Int(charCode & 0x3F)],
|
|
|
|
attributes: charCode & 0xC0, //d6 and d7
|
|
|
|
pixelPosition: VideoHelpers.getPixelOffset(memoryOffset: Int(address - 0x400)))
|
2017-08-01 07:28:26 +00:00
|
|
|
}
|
2017-08-01 06:02:49 +00:00
|
|
|
}
|
|
|
|
|
2017-08-02 08:01:06 +00:00
|
|
|
enum MemoryConfiguration {
|
|
|
|
case fourK
|
|
|
|
case sixteenK
|
|
|
|
case fortyeightK
|
|
|
|
}
|
|
|
|
|
|
|
|
func setupMemory(ramConfig: MemoryConfiguration) {
|
|
|
|
let ramPages: Int
|
|
|
|
|
|
|
|
switch ramConfig {
|
|
|
|
case .fourK:
|
|
|
|
ramPages = 4096 / 256
|
|
|
|
case .sixteenK:
|
|
|
|
ramPages = 16384 / 256
|
|
|
|
case .fortyeightK:
|
|
|
|
ramPages = 49152 / 256
|
|
|
|
}
|
|
|
|
|
|
|
|
for page in 0 ..< ramPages {
|
|
|
|
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.rw //RAM
|
|
|
|
}
|
|
|
|
for page in ramPages ..< 192 {
|
|
|
|
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.null //not connected
|
|
|
|
}
|
|
|
|
for page in 224 ..< 256 {
|
2017-08-04 02:53:36 +00:00
|
|
|
CPU.sharedInstance.memoryInterface.pages[page] = MemoryInterface.pageMode.ro //ROM
|
2017-08-02 08:01:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-07 00:16:25 +00:00
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-05 07:29:07 +00:00
|
|
|
@objc func debuggerBreak() {
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-08-01 06:02:49 +00:00
|
|
|
}
|