diff --git a/FruitMachine.xcodeproj/project.pbxproj b/FruitMachine.xcodeproj/project.pbxproj index cc6052d..7e5aeb5 100644 --- a/FruitMachine.xcodeproj/project.pbxproj +++ b/FruitMachine.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 2A5C5BBC1F304C3A00ED351D /* A2CharacterGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C5BBB1F304C3A00ED351D /* A2CharacterGenerator.swift */; }; 2A5C5BBE1F304D4B00ED351D /* Glyph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5C5BBD1F304D4B00ED351D /* Glyph.swift */; }; 2A60851E1F2AFAE900E05B64 /* PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A60851D1F2AFAE900E05B64 /* PIA.swift */; }; + 2A6C2D171F31216700B8DC60 /* SoftswitchOverrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6C2D161F31216700B8DC60 /* SoftswitchOverrides.swift */; }; 2A6DC7E91F3045280066FE0D /* AppleIIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6DC7E81F3045280066FE0D /* AppleIIViewController.swift */; }; 2A6DC7EB1F3045C90066FE0D /* EmulatedSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6DC7EA1F3045C90066FE0D /* EmulatedSystem.swift */; }; 2A6DC7ED1F30492C0066FE0D /* ScreenDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A6DC7EC1F30492C0066FE0D /* ScreenDelegate.swift */; }; @@ -54,6 +55,7 @@ 2A5C5BBB1F304C3A00ED351D /* A2CharacterGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = A2CharacterGenerator.swift; sourceTree = ""; }; 2A5C5BBD1F304D4B00ED351D /* Glyph.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Glyph.swift; sourceTree = ""; }; 2A60851D1F2AFAE900E05B64 /* PIA.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PIA.swift; sourceTree = ""; }; + 2A6C2D161F31216700B8DC60 /* SoftswitchOverrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftswitchOverrides.swift; sourceTree = ""; }; 2A6DC7E81F3045280066FE0D /* AppleIIViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIIViewController.swift; sourceTree = ""; }; 2A6DC7EA1F3045C90066FE0D /* EmulatedSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmulatedSystem.swift; sourceTree = ""; }; 2A6DC7EC1F30492C0066FE0D /* ScreenDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenDelegate.swift; sourceTree = ""; }; @@ -161,6 +163,7 @@ children = ( 2A6DC7EE1F30492E0066FE0D /* Video */, 2AB6CACC1F3041A200DECAC0 /* AppleII.swift */, + 2A6C2D161F31216700B8DC60 /* SoftswitchOverrides.swift */, ); path = AppleII; sourceTree = ""; @@ -337,6 +340,7 @@ buildActionMask = 2147483647; files = ( 2A60851E1F2AFAE900E05B64 /* PIA.swift in Sources */, + 2A6C2D171F31216700B8DC60 /* SoftswitchOverrides.swift in Sources */, 2A5C5BBE1F304D4B00ED351D /* Glyph.swift in Sources */, 2A2126841F2A9FA300E43DC1 /* DebuggerWindowController.swift in Sources */, 2AD458E31F20661300F05121 /* CPUInstructions.swift in Sources */, diff --git a/FruitMachine/AppleII/AppleII.swift b/FruitMachine/AppleII/AppleII.swift index 36e9c4c..f2c5570 100644 --- a/FruitMachine/AppleII/AppleII.swift +++ b/FruitMachine/AppleII/AppleII.swift @@ -11,6 +11,8 @@ import Cocoa class AppleII: NSObject, EmulatedSystem { static let sharedInstance = AppleII(cpuFrequency: (14.31818 / 7 / 2) * 1000000, fps: 60.0) + var frameCounter: Int = 0 + let cg = A2CharacterGenerator(romPath: "/Users/luigi/apple2/a2.chr"); var CPU_FREQUENCY: Double @@ -51,10 +53,17 @@ class AppleII: NSObject, EmulatedSystem { } func installOverrides() { - //TODO + CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.readKeyboard) + CPU.sharedInstance.memoryInterface.read_overrides.append(SoftswitchOverrides.clearKeypressStrobeR) + CPU.sharedInstance.memoryInterface.write_overrides.append(SoftswitchOverrides.clearKeypressStrobeW) } func runFrame() { + frameCounter = (frameCounter + 1) % 60 + if(frameCounter % 15) == 0 { + emulatorViewDelegate.flashIsInverse = !emulatorViewDelegate.flashIsInverse + } + CPU.sharedInstance.cycles = 0 CPU.sharedInstance.cyclesInBatch = CYCLES_PER_BATCH CPU.sharedInstance.runCyclesBatch() @@ -66,12 +75,13 @@ class AppleII: NSObject, EmulatedSystem { let buf = pixelBase?.assumingMemoryBound(to: BitmapPixelsBE555.PixelData.self) //Text mode: Get character codes from $0400-$07FF - for address in 0x0400 ..< 0x07C0 { + for address in 0x0400 ..< 0x07F8 { let charCode = CPU.sharedInstance.memoryInterface.readByte(offset: UInt16(address), bypassOverrides: true) emulatorViewDelegate.putGlyph(buffer: buf, - glyph: cg.glyphs[Int(charCode & ~(0x80))], - pixelPosition: emulatorViewDelegate.getPixelOffset(charCellIndex: address - 0x400)) + glyph: cg.glyphs[Int(charCode & 0x3F)], + attributes: charCode & 0xC0, //d6 and d7 + pixelPosition: emulatorViewDelegate.getPixelOffset(memoryOffset: address - 0x400)) } CVPixelBufferUnlockBaseAddress(emulatorViewDelegate.pixels!, CVPixelBufferLockFlags(rawValue: 0)) diff --git a/FruitMachine/AppleII/SoftswitchOverrides.swift b/FruitMachine/AppleII/SoftswitchOverrides.swift new file mode 100644 index 0000000..c50040d --- /dev/null +++ b/FruitMachine/AppleII/SoftswitchOverrides.swift @@ -0,0 +1,36 @@ +// +// SoftswitchOverrides.swift +// FruitMachine +// +// Created by Christopher Rohl on 8/1/17. +// Copyright © 2017 Christopher Rohl. All rights reserved. +// + +import Cocoa + +extension AppleII { + + class SoftswitchOverrides: NSObject { + static let readKeyboard = ReadOverride(start: 0xC000, end: 0xC000, readAnyway: false, action: SoftswitchOverrides.actionReadKeyboard) + static func actionReadKeyboard(dummy: AnyObject, byte: UInt8?) -> UInt8? { + let b = CPU.sharedInstance.memoryInterface.readByte(offset: 0xC000, bypassOverrides: true) + + CPU.sharedInstance.memoryInterface.writeByte(offset: 0xC000, value: b) + + return b + } + + static let clearKeypressStrobeR = ReadOverride(start: 0xC010, end: 0xC010, readAnyway: false, action: SoftswitchOverrides.actionClearKeypressStrobe) + static let clearKeypressStrobeW = WriteOverride(start: 0xC010, end: 0xC010, writeAnyway: false, action: SoftswitchOverrides.actionClearKeypressStrobe) + static func actionClearKeypressStrobe(dummy: AnyObject, byte: UInt8?) -> UInt8? { + //Clears b7 of $C000 on write. + + let b = CPU.sharedInstance.memoryInterface.readByte(offset: 0xC000, bypassOverrides: true) + CPU.sharedInstance.memoryInterface.writeByte(offset: 0xC000, value: b & 0x7F, bypassOverrides: true) + //CPU.sharedInstance.memoryInterface.writeByte(offset: 0xC010, value: b & 0x7F, bypassOverrides: true) + + return b + } + } + +} diff --git a/FruitMachine/AppleII/Video/ScreenDelegate.swift b/FruitMachine/AppleII/Video/ScreenDelegate.swift index 14052fc..f59a9dc 100644 --- a/FruitMachine/AppleII/Video/ScreenDelegate.swift +++ b/FruitMachine/AppleII/Video/ScreenDelegate.swift @@ -10,6 +10,12 @@ import Cocoa extension AppleII { + enum CharacterAttributes { + case normal + case flashing + case inverse + } + class ScreenDelegate: NSObject, CALayerDelegate { static let PIXEL_WIDTH = 280 static let PIXEL_HEIGHT = 192 @@ -18,6 +24,8 @@ extension AppleII { static let CELLS_HEIGHT = 24 static let CELLS_COUNT = CELLS_WIDTH * CELLS_HEIGHT + var flashIsInverse = false + /* Pixel data stuff. */ let bitmapInfo: CGBitmapInfo = [.byteOrder16Big, CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue)] @@ -46,8 +54,18 @@ extension AppleII { } } - func putGlyph(buffer: UnsafeMutablePointer?, glyph: Glyph, pixelPosition: CGPoint) { + func putGlyph(buffer: UnsafeMutablePointer?, glyph: Glyph, attributes: UInt8, pixelPosition: CGPoint) { //You better have locked the buffer before getting here... + if(pixelPosition.x == -1 && pixelPosition.y == -1) { return } + + let ca: CharacterAttributes + if(attributes == 0x00) { + ca = .inverse + } else if(attributes == 0x40) { + ca = .flashing + } else { + ca = .normal + } //Calculate the offset to reach the desired position. let baseOffset = scanlineOffsets[Int(pixelPosition.y)] + Int(pixelPosition.x) @@ -57,7 +75,19 @@ extension AppleII { let glyphOffsetY = (charY * 8) for charX in 0..<7 { - buffer![offset + 6 - charX] = glyph.pixels[glyphOffsetY + charX] + switch(ca) { + case .normal: + buffer![offset + 6 - charX] = glyph.pixels[glyphOffsetY + charX] + case .inverse: + buffer![offset + 6 - charX] = BitmapPixelsBE555.PixelData(data: ~glyph.pixels[glyphOffsetY + charX].data) + case .flashing: + if(!flashIsInverse) { + buffer![offset + 6 - charX] = glyph.pixels[glyphOffsetY + charX] + } else { + buffer![offset + 6 - charX] = BitmapPixelsBE555.PixelData(data: ~glyph.pixels[glyphOffsetY + charX].data) + } + } + } } } @@ -66,8 +96,37 @@ extension AppleII { return CGPoint(x: charCellX * 7, y: charCellY * 8) } - func getPixelOffset(charCellIndex: Int) -> CGPoint { - return getPixelOffset(charCellX: charCellIndex % AppleII.ScreenDelegate.CELLS_WIDTH, charCellY: charCellIndex / AppleII.ScreenDelegate.CELLS_WIDTH) + func getPixelOffset(memoryOffset: Int) -> CGPoint { + //Offset is between 0x000 and 0x3FF. + //If offset & 0x28, second batch. + //If offset & 0x50, third batch. + //Else, first batch. + + var rowNumber = memoryOffset / 0x80 + let lowByte = memoryOffset & 0x0FF + let cellX: Int + + if(0x28 ... 0x4F ~= lowByte || 0xA8 ... 0xCF ~= lowByte) { + //Middle third. + rowNumber += 8 + cellX = (lowByte & ~(0x80)) - 0x28 + } + else if(0x50 ... 0x77 ~= lowByte || 0xD0 ... 0xF7 ~= lowByte) { + //Bottom third. + rowNumber += 16 + cellX = (lowByte & ~(0x80)) - 0x50 + } + else if(0x78 ... 0x7F ~= lowByte || 0xF8 ... 0xFF ~= lowByte) { + //Discard. + return CGPoint(x: -1, y: -1) + } + else { + //Top third. + rowNumber += 0 + cellX = (lowByte & ~(0x80)) + } + + return getPixelOffset(charCellX: cellX, charCellY: rowNumber) } /* Draw the screen. */ diff --git a/FruitMachine/AppleIIViewController.swift b/FruitMachine/AppleIIViewController.swift index 7cce5b2..46f22d2 100644 --- a/FruitMachine/AppleIIViewController.swift +++ b/FruitMachine/AppleIIViewController.swift @@ -25,7 +25,7 @@ class AppleIIViewController: NSViewController { self.view.addSubview(computer.emulatorView) - self.frameTimer = Timer.scheduledTimer(timeInterval: 1/60, + self.frameTimer = Timer.scheduledTimer(timeInterval: 1.0/60.0, target: self, selector: #selector(runEmulation), userInfo: nil, @@ -49,4 +49,23 @@ class AppleIIViewController: NSViewController { preferencesWindowController.loadWindow() } + override func keyDown(with event: NSEvent) { + let c = returnChar(theEvent: event) + + guard let ascii32 = c?.asciiValue else { + return + } + + //Poke the ASCII byte into $C000. + CPU.sharedInstance.memoryInterface.writeByte(offset: 0xC000, value: UInt8((ascii32 | 0x80) & 0x000000FF)) + } + + private func returnChar(theEvent: NSEvent) -> Character?{ + let s: String = theEvent.characters! + for char in s{ + return char + } + return nil + } + } diff --git a/FruitMachine/M6502/Debugger/DebuggerViewController.swift b/FruitMachine/M6502/Debugger/DebuggerViewController.swift index 19215d7..d762b65 100644 --- a/FruitMachine/M6502/Debugger/DebuggerViewController.swift +++ b/FruitMachine/M6502/Debugger/DebuggerViewController.swift @@ -74,7 +74,7 @@ class DebuggerViewController: NSViewController { isRunning = true cpuInstance.cycles = 0 - cpuInstance.cyclesInBatch = 1000000 + cpuInstance.cyclesInBatch = 10000 while(!cpuInstance.outOfCycles() && isRunning) { cpuInstance.cpuStep() diff --git a/FruitMachine/M6502/Opcodes/Opcodes.swift b/FruitMachine/M6502/Opcodes/Opcodes.swift index f7ba5d6..cc6e879 100644 --- a/FruitMachine/M6502/Opcodes/Opcodes.swift +++ b/FruitMachine/M6502/Opcodes/Opcodes.swift @@ -170,7 +170,6 @@ final class Opcodes: NSObject { var t16: UInt16 = UInt16(state.accumulator &- operand) &- UInt16((state.status_register.carry ? UInt8(0) : UInt8(1))) let t8: UInt8 = UInt8(t16 & 0xFF) - state.cyclesInBatch = 0 state.status_register.overflow = (t8 >= 0x80 && t8 <= 0xFF) if(state.status_register.decimal == true) {