mirror of
https://github.com/trudnai/Steve2.git
synced 2024-12-27 09:30:26 +00:00
2380 lines
80 KiB
Swift
2380 lines
80 KiB
Swift
//
|
||
// ViewController.swift
|
||
// Steve ][
|
||
//
|
||
// Created by Tamas Rudnai on 7/25/19.
|
||
// Copyright © 2019, 2020 Tamas Rudnai. All rights reserved.
|
||
//
|
||
// This file is part of Steve ][ -- The Apple ][ Emulator.
|
||
//
|
||
// Steve ][ is free software: you can redistribute it and/or modify
|
||
// it under the terms of the GNU General Public License as published by
|
||
// the Free Software Foundation, either version 3 of the License, or
|
||
// (at your option) any later version.
|
||
//
|
||
// Steve ][ is distributed in the hope that it will be useful,
|
||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
// GNU General Public License for more details.
|
||
//
|
||
// You should have received a copy of the GNU General Public License
|
||
// along with Steve ][. If not, see <https://www.gnu.org/licenses/>.
|
||
//
|
||
|
||
|
||
import Cocoa
|
||
//import AVFoundation
|
||
|
||
//import Metal
|
||
//
|
||
//var device : MTLDevice!
|
||
//var metalLayer: CAMetalLayer!
|
||
//var vertexBuffer: MTLBuffer!
|
||
//var pipelineState: MTLRenderPipelineState!
|
||
//var commandQueue: MTLCommandQueue!
|
||
////var timer: CADisplayLink!
|
||
|
||
|
||
|
||
let K : Double = 1000.0
|
||
let M : Double = (K * K)
|
||
let G : Double = (M * K)
|
||
let T : Double = (G * K)
|
||
|
||
let KB : Double = 1024.0
|
||
let MB : Double = (KB * KB)
|
||
let GB : Double = (MB * KB)
|
||
let TB : Double = (GB * KB)
|
||
|
||
|
||
let colorWhite = NSColor.init( red:0.9296875, green:0.9296875, blue:0.9296875, alpha: 1 )
|
||
let colorGreen = NSColor.init( red:0.16796875, green:0.84375, blue:0.2890625, alpha: 1 )
|
||
let colorOrange = NSColor.init( red:1, green:0.38671875, blue:0.0078125, alpha: 1 )
|
||
|
||
var monoColor = colorGreen;
|
||
|
||
//var spk_up: AVAudioPlayer?
|
||
//var spk_dn: AVAudioPlayer?
|
||
//
|
||
//@_cdecl("ViewController_spk_up_play")
|
||
//func spk_up_play() {
|
||
// spk_up?.stop()
|
||
// spk_dn?.stop()
|
||
// spk_up?.play()
|
||
//}
|
||
//
|
||
//@_cdecl("ViewController_spk_dn_play")
|
||
//func spk_dn_play() {
|
||
// spk_up?.stop()
|
||
// spk_dn?.stop()
|
||
// spk_dn?.play()
|
||
//}
|
||
|
||
|
||
#if METAL_YES
|
||
import Metal
|
||
#endif
|
||
|
||
class ViewController: NSViewController {
|
||
|
||
static var shared : ViewController? = nil
|
||
|
||
var displayLink: CVDisplayLink?
|
||
|
||
@IBOutlet var monitorView: MonitorView!
|
||
@IBOutlet weak var textDisplayScroller: NSScrollView!
|
||
@IBOutlet var textDisplay: NSTextView!
|
||
@IBOutlet weak var speedometer: NSTextFieldCell!
|
||
@IBOutlet weak var lores: LoRes!
|
||
@IBOutlet weak var hires: HiRes!
|
||
@IBOutlet weak var splashScreen: NSView!
|
||
@IBOutlet weak var scanLines: NSImageView!
|
||
|
||
var CRTMonitor = false
|
||
var ColorMonitor = true
|
||
var Keyboard2Joystick = true
|
||
var Mouse2Joystick = false
|
||
var MouseInterface = true
|
||
|
||
|
||
// static let charConvStr : String =
|
||
// "@🄰🄱🄲🄳🄴🄵🄶🄷🄸🄹🄺🄻🄼🄽🄾🄿🅀🅁🅂🅃🅄🅅🅆🅇🅈🅉[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
// "@🅰🅱🅲🅳🅴🅵🅶🅷🅸🅹🅺🅻🅼🅽🅾🅿🆀🆁🆂🆃🆄🆅🆆🆇🆈🆉[\\]^_⬛︎!\"#$%&'()*+,-./0123456789:;<=>?" + // FL
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?"
|
||
|
||
// static let charConvStr : String =
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_░!\"#$%&'()*+,-./0123456789:;<=>?" + // FL
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{E0A0}!\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?"
|
||
|
||
// static let charConvStr : String =
|
||
// "\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" +
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_░!\"#$%&'()*+,-./0123456789:;<=>?" + // FL
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_\u{E0A0}!\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
// "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~?"
|
||
|
||
// TODO: On 80n col mode no flash + small caps are inversed on the Flash map
|
||
|
||
static let charConvStrFlashOff40 : String =
|
||
// INVERSE
|
||
"\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" +
|
||
|
||
// FLASH
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" + // FL
|
||
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{E27F}"
|
||
|
||
static let charConvStrFlashOn40 : String =
|
||
// INVERSE
|
||
"\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" +
|
||
|
||
// FLASH
|
||
"\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" +
|
||
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{E27F}"
|
||
|
||
|
||
static let charConvStrCol80 : String =
|
||
// INVERSE
|
||
"\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E120}\u{E121}\u{E122}\u{E123}\u{E124}\u{E125}\u{E126}\u{E127}\u{E128}\u{E129}\u{E12A}\u{E12B}\u{E12C}\u{E12D}\u{E12E}\u{E12F}\u{E130}\u{E131}\u{E132}\u{E133}\u{E134}\u{E135}\u{E136}\u{E137}\u{E138}\u{E139}\u{E13A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" +
|
||
|
||
// INVERSE 2 with small caps
|
||
"\u{E140}\u{E141}\u{E142}\u{E143}\u{E144}\u{E145}\u{E146}\u{E147}\u{E148}\u{E149}\u{E14A}\u{E14B}\u{E14C}\u{E14D}\u{E14E}\u{E14F}\u{E150}\u{E151}\u{E152}\u{E153}\u{E154}\u{E155}\u{E156}\u{E157}\u{E158}\u{E159}\u{E15A}\u{E15B}\u{E15C}\u{E15D}\u{E15E}\u{E15F}\u{E160}\u{E161}\u{E162}\u{E163}\u{E164}\u{E165}\u{E166}\u{E167}\u{E168}\u{E169}\u{E16A}\u{E16B}\u{E16C}\u{E16D}\u{E16E}\u{E16F}\u{E170}\u{E171}\u{E172}\u{E173}\u{E174}\u{E175}\u{E176}\u{E177}\u{E178}\u{E179}\u{E17A}\u{E13B}\u{E13C}\u{E13D}\u{E13E}\u{E13F}" +
|
||
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?" +
|
||
|
||
"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u{E27F}"
|
||
|
||
|
||
static let charConvTblFlashOn40 = Array( charConvStrFlashOn40 )
|
||
static let charConvTblFlashOff40 = Array( charConvStrFlashOff40 )
|
||
static let charConvTblCol80 = Array( charConvStrCol80 )
|
||
|
||
static var charConvTblFlashOn = charConvTblFlashOn40
|
||
static var charConvTblFlashOff = charConvTblFlashOff40
|
||
|
||
static var charConvTbl = charConvTblFlashOn
|
||
|
||
// static var romFileName = "Apple2e_Enhanced.rom"
|
||
static var romFileName = "Apple2e_32k.rom"
|
||
// static var romFileName = "077-0019 Apple IIe Diagnostic Card - English.rom"
|
||
|
||
static let textLineOfs : [Int] = [
|
||
0x000, 0x080, 0x100, 0x180, 0x200, 0x280, 0x300, 0x380, 0x028, 0x0A8, 0x128, 0x1A8,
|
||
0x228, 0x2A8, 0x328, 0x3A8, 0x050, 0x0D0, 0x150, 0x1D0, 0x250, 0x2D0, 0x350, 0x3D0
|
||
]
|
||
|
||
|
||
func dialogOK(title: String, text: String) {
|
||
let alert = NSAlert()
|
||
alert.messageText = title
|
||
alert.informativeText = text
|
||
alert.alertStyle = .warning
|
||
alert.addButton(withTitle: "OK")
|
||
alert.runModal()
|
||
// return alert.runModal() == .alertFirstButtonReturn
|
||
}
|
||
|
||
|
||
@objc func chk_woz_load(err : Int32) {
|
||
switch (err) {
|
||
case WOZ_ERR_OK:
|
||
break
|
||
|
||
case WOZ_ERR_FILE_NOT_FOUND:
|
||
dialogOK(title: "Error Loading Disk Image", text: "File Not Found!")
|
||
break
|
||
|
||
case WOZ_ERR_NOT_WOZ_FILE:
|
||
dialogOK(title: "Error Loading Disk Image", text: "File is not a WOZ image!")
|
||
break
|
||
|
||
case WOZ_ERR_BAD_CHUNK_HDR, WOZ_ERR_BAD_DATA:
|
||
dialogOK(title: "Error Loading Disk Image", text: "Malformed WOZ image!")
|
||
break
|
||
|
||
default:
|
||
dialogOK(title: "Error Loading Disk Image", text: "Unknown Error! (\(err))" )
|
||
break
|
||
}
|
||
}
|
||
|
||
|
||
var workItem : DispatchWorkItem? = nil;
|
||
@IBAction func PowerOn(_ sender: Any) {
|
||
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStop(displayLink!)
|
||
#else
|
||
upd.suspend()
|
||
#endif
|
||
|
||
cpuState = cpuState_inited;
|
||
spkr_stopAll()
|
||
|
||
//------------------------------------------------------------
|
||
// Animated Splash Screen fade out and (Text) Monitor fade in
|
||
|
||
hires.isHidden = true
|
||
lores.isHidden = true
|
||
// displayField.alphaValue = 0
|
||
// displayField.isHidden = false
|
||
textDisplayScroller.alphaValue = 0
|
||
textDisplayScroller.isHidden = false
|
||
splashScreen.alphaValue = 1
|
||
splashScreen.isHidden = false
|
||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||
NSAnimationContext.runAnimationGroup({ (context) in
|
||
context.duration = 1.0
|
||
// Use the value you want to animate to (NOT the starting value)
|
||
self.textDisplayScroller.animator().alphaValue = 1
|
||
self.hires.animator().alphaValue = 1
|
||
self.lores.animator().alphaValue = 1
|
||
self.splashScreen.animator().alphaValue = 0
|
||
},
|
||
completionHandler:{ () -> Void in
|
||
self.textDisplayScroller.alphaValue = 1
|
||
self.hires.alphaValue = 1
|
||
self.lores.alphaValue = 1
|
||
self.splashScreen.isHidden = true
|
||
})
|
||
|
||
m6502_ColdReset( Bundle.main.resourcePath! + "/rom/", ViewController.romFileName )
|
||
|
||
cpuState = cpuState_running;
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStart(self.displayLink!)
|
||
#else
|
||
self.upd.resume()
|
||
#endif
|
||
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.PauseButtonUpdate(needUpdateMainToolbar: false)
|
||
}
|
||
}
|
||
//------------------------------------------------------------
|
||
|
||
#if SPEEDTEST
|
||
if ( workItem != nil ) {
|
||
workItem!.cancel();
|
||
workItem = nil;
|
||
}
|
||
else {
|
||
workItem = DispatchWorkItem {
|
||
DispatchQueue.global(qos: .userInteractive).async {
|
||
// DispatchQueue.global(qos: .userInitiated).async {
|
||
// DispatchQueue.global(qos: .background).async {
|
||
tst6502()
|
||
}
|
||
}
|
||
DispatchQueue.global().async(execute: workItem!);
|
||
}
|
||
#else
|
||
|
||
|
||
#endif
|
||
}
|
||
|
||
|
||
@IBAction func PowerOff(_ sender: Any) {
|
||
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStop(displayLink!)
|
||
#else
|
||
upd.suspend()
|
||
#endif
|
||
|
||
cpuState = cpuState_inited;
|
||
spkr_stopAll()
|
||
|
||
//------------------------------------------------------------
|
||
// Animated Splash Screen fade out and (Text) Monitor fade in
|
||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) {
|
||
self.splashScreen.alphaValue = 0
|
||
self.splashScreen.isHidden = false
|
||
|
||
NSAnimationContext.runAnimationGroup({ (context) in
|
||
context.duration = 0.5
|
||
// Use the value you want to animate to (NOT the starting value)
|
||
self.textDisplayScroller.animator().alphaValue = 0
|
||
self.hires.animator().alphaValue = 0
|
||
self.lores.animator().alphaValue = 0
|
||
self.splashScreen.animator().alphaValue = 1
|
||
},
|
||
completionHandler:{ () -> Void in
|
||
self.textDisplayScroller.alphaValue = 0
|
||
self.textDisplayScroller.isHidden = true
|
||
self.splashScreen.isHidden = false
|
||
|
||
self.hires.alphaValue = 0
|
||
self.lores.alphaValue = 0
|
||
self.hires.isHidden = true
|
||
self.lores.isHidden = true
|
||
|
||
self.splashScreen.isHidden = false
|
||
})
|
||
}
|
||
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.PauseButtonUpdate(needUpdateMainToolbar: false)
|
||
}
|
||
|
||
|
||
// hires.isHidden = true
|
||
// lores.isHidden = true
|
||
// textDisplayScroller.alphaValue = 0
|
||
//// textDisplayScroller.isHidden = false
|
||
// splashScreen.alphaValue = 1
|
||
// splashScreen.isHidden = false
|
||
|
||
//------------------------------------------------------------
|
||
|
||
}
|
||
|
||
|
||
func debuggerShowWindow() {
|
||
if let debuggerWindowController = DebuggerWindowController.shared {
|
||
DispatchQueue.main.async {
|
||
debuggerWindowController.showWindow(self)
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
func debuggerRemoveHighlight() {
|
||
if let debuggerViewController = DebuggerViewController.shared {
|
||
debuggerViewController.remove_highlight(view: debuggerViewController.Disass_Display, line: debuggerViewController.highlighted_line_number)
|
||
}
|
||
}
|
||
|
||
|
||
func debuggerPauseUpdate() {
|
||
if let debuggerViewController = DebuggerViewController.shared {
|
||
debuggerViewController.TrunDisassAddressPC(.on)
|
||
debuggerViewController.remove_highlight(view: debuggerViewController.Disass_Display, line: debuggerViewController.highlighted_line_number)
|
||
debuggerViewController.Update()
|
||
}
|
||
}
|
||
|
||
|
||
func Resume() {
|
||
debuggerRemoveHighlight()
|
||
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStart(displayLink!)
|
||
#else
|
||
upd.resume()
|
||
#endif
|
||
|
||
cpuState = cpuState_running
|
||
|
||
DispatchQueue.main.async {
|
||
self.view.window?.windowController?.showWindow(self)
|
||
}
|
||
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.PauseButtonUpdate()
|
||
}
|
||
}
|
||
|
||
|
||
func Pause() {
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStop(displayLink!)
|
||
#else
|
||
upd.suspend()
|
||
#endif
|
||
|
||
cpuState = cpuState_halted
|
||
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.PauseButtonUpdate()
|
||
}
|
||
|
||
debuggerPauseUpdate()
|
||
}
|
||
|
||
|
||
@IBAction func Pause(_ sender: Any) {
|
||
|
||
switch ( cpuState ) {
|
||
case cpuState_halted:
|
||
Resume()
|
||
break
|
||
|
||
case cpuState_running:
|
||
Pause()
|
||
break
|
||
|
||
default:
|
||
// upd.suspend()
|
||
// cpuState = cpuState_halted
|
||
break
|
||
}
|
||
}
|
||
|
||
|
||
@IBAction func Reset(_ sender: Any) {
|
||
// m6502.interrupt = SOFTRESET;
|
||
|
||
// let saved_frm_set = clk_6502_per_frm_set;
|
||
// clk_6502_per_frm_set = 0
|
||
// clk_6502_per_frm_max = 0
|
||
// // wait for 1 ms to allow the simulation to halt
|
||
// usleep(10000);
|
||
|
||
softReset()
|
||
|
||
// clk_6502_per_frm_set = saved_frm_set
|
||
}
|
||
|
||
|
||
static let textPage1Addr = 0x400
|
||
static let textPage2Addr = 0x800
|
||
static let textBufferSize = 0x400
|
||
|
||
// static only needed to be able to initialize things
|
||
static let textLines = 24
|
||
static let textCols = 40
|
||
static let lineEndChars = 1
|
||
|
||
// these are needed to be able to easy access to these constants from instances
|
||
let textLines = ViewController.textLines
|
||
let textCols = ViewController.textCols
|
||
let lineEndChars = ViewController.lineEndChars
|
||
|
||
var frameCnt = 0
|
||
var flashInverted = false
|
||
|
||
// let spaceChar : Character = "\u{E17F}"
|
||
// let blockChar : Character = "\u{E07F}"
|
||
// static let spaceChar : Character = " "
|
||
// static let blockChar : Character = "░"
|
||
// static var flashingSpace : Character = " "
|
||
|
||
let ramBufferPointer = UnsafeRawBufferPointer(start: MEM, count: 64 * 1024)
|
||
static let textPage1Pointer = UnsafeRawBufferPointer(start: MEM + textPage1Addr, count: textBufferSize)
|
||
static let textPage2Pointer = UnsafeRawBufferPointer(start: MEM + textPage2Addr, count: textBufferSize)
|
||
static let textIntPage1BufferPointer = UnsafeRawBufferPointer(start: RAM + textPage1Addr, count: textBufferSize)
|
||
static let textIntPage2BufferPointer = UnsafeRawBufferPointer(start: RAM + textPage2Addr, count: textBufferSize)
|
||
static let textAuxBufferPointer = UnsafeRawBufferPointer(start: AUX + textPage1Addr, count: textBufferSize)
|
||
|
||
static let textPageShadowBuffer = UnsafeMutableRawBufferPointer.allocate(byteCount: textBufferSize, alignment: 1)
|
||
|
||
// TODO: Render text screen in native C
|
||
// static let textScreen = UnsafeMutableRawPointer(mutating: testText)
|
||
|
||
|
||
var textBufferPointer = textPage1Pointer
|
||
|
||
static let textArraySize = textLines * (textCols + lineEndChars) + textCols * 2
|
||
|
||
let txtClear = [Character](repeating: " ", count: textArraySize * 2)
|
||
var unicodeTextArray = [Character](repeating: " ", count: textArraySize * 2)
|
||
|
||
var s = String()
|
||
|
||
func HexDump() {
|
||
var txt : String = ""
|
||
|
||
for y in 0...textLines - 1 {
|
||
txt += String(format: "%04X: ", y * 16)
|
||
for x in 0...15 {
|
||
let byte = ramBufferPointer[ y * 16 + x ]
|
||
let chr = String(format: "%02X ", byte)
|
||
txt += chr
|
||
}
|
||
txt += "\n"
|
||
}
|
||
|
||
DispatchQueue.main.async {
|
||
self.textDisplay.string = txt
|
||
self.speedometer.stringValue = String(format: "%0.3lf MHz", mhz);
|
||
}
|
||
}
|
||
|
||
|
||
// AppleScript Keycodes
|
||
let leftArrowKey = 123
|
||
let rightArrowKey = 124
|
||
let upArrowKey = 126
|
||
let downArrowKey = 125
|
||
|
||
let F4FunctionKey = 118
|
||
let F5FunctionKey = 96
|
||
let F6FunctionKey = 97
|
||
let F7FunctionKey = 98
|
||
let F8FunctionKey = 100
|
||
|
||
|
||
override var acceptsFirstResponder: Bool {
|
||
get {
|
||
return true
|
||
}
|
||
}
|
||
|
||
|
||
func SelectAll() {
|
||
// textDisplayScroller.currentEditor()?.selectAll(nil)
|
||
// displayField.selectText(nil)
|
||
textDisplay.setSelectedRange(NSRange())
|
||
}
|
||
|
||
func Copy() {
|
||
let pasteBoard = NSPasteboard.general
|
||
pasteBoard.clearContents()
|
||
// TODO: Find a better way to avoid index out of range error when the entire text area is selected
|
||
let string = textDisplay.string + " "
|
||
let selectedRange = textDisplay.selectedRange()
|
||
if selectedRange != NSRange() {
|
||
let startIndex = string.index(string.startIndex, offsetBy: selectedRange.lowerBound)
|
||
let endIndex = string.index(string.startIndex, offsetBy: selectedRange.upperBound)
|
||
let selectedString = string[startIndex..<endIndex]
|
||
pasteBoard.setString(String(selectedString), forType: .string)
|
||
}
|
||
}
|
||
|
||
func Paste() {
|
||
let pasteBoard = NSPasteboard.general
|
||
if let str = pasteBoard.string( forType: .string ) {
|
||
print("PASTED:", str)
|
||
|
||
DispatchQueue.global(qos: .background).async {
|
||
for char in str.uppercased() {
|
||
if let ascii = char.asciiValue {
|
||
// TODO: Write separate Paste Accelerator
|
||
// disk_accelerator_speedup()
|
||
kbdPaste(ascii)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
let mouseCursorHidden = true;
|
||
let mouseCursorJoystickEmulation = NSCursor.crosshair
|
||
let mouseCursorHiddenJoystickEmulation = NSCursor.init(image: NSImage.init(size: NSSize(width: 1, height: 1)), hotSpot: NSPoint(x: 0, y: 0))
|
||
|
||
|
||
func mouseCursor(hide : Bool) {
|
||
if hide {
|
||
if mouseCursorHidden {
|
||
// NSCursor.hide() is working weird, better to set a 1px transparent cursor
|
||
// mouseCursorHiddenJoystickEmulation.set()
|
||
mouseCursorJoystickEmulation.set()
|
||
}
|
||
else {
|
||
mouseCursorJoystickEmulation.set()
|
||
}
|
||
}
|
||
else {
|
||
// if mouseCursorHidden {
|
||
// NSCursor.unhide()
|
||
// }
|
||
NSCursor.arrow.set()
|
||
}
|
||
}
|
||
|
||
|
||
func getScreenWithMouse() -> NSScreen? {
|
||
let mouseLocation = NSEvent.mouseLocation
|
||
let screens = NSScreen.screens
|
||
let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) })
|
||
|
||
return screenWithMouse
|
||
}
|
||
|
||
|
||
func convertPoint(toCG point : NSPoint) -> CGPoint {
|
||
/// Cocoa and Core Graphics (a.k.a. Quartz) use different coordinate systems. In Cocoa, the origin is at the lower left of the primary screen and y increases as you go up. In Core Graphics, the origin is at the top left of the primary screen and y increases as you go down.
|
||
/// Need to convert coordinates from Cocoa to Core Graphics
|
||
var cgpoint = view.window!.convertPoint(toScreen: point)
|
||
if let screen = getScreenWithMouse() {
|
||
cgpoint.y = NSHeight(screen.frame) - cgpoint.y;
|
||
}
|
||
return cgpoint
|
||
}
|
||
|
||
|
||
override func mouseMoved(with event: NSEvent) {
|
||
// print(#function)
|
||
var location = event.locationInWindow
|
||
// displayOrigin = textDisplayScroller.frame.origin.
|
||
// print("mx:", location.x, " my:", location.y)
|
||
|
||
var mouseCursorNeedsReplace = false
|
||
|
||
if location.x < 8 {
|
||
mouseCursorNeedsReplace = true
|
||
location.x = 8
|
||
}
|
||
if location.x > textDisplay.frame.width - 8 {
|
||
mouseCursorNeedsReplace = true
|
||
location.x = textDisplay.frame.width - 8
|
||
}
|
||
if location.y < 8 {
|
||
mouseCursorNeedsReplace = true
|
||
location.y = 8
|
||
}
|
||
if location.y > textDisplay.frame.height - 8 {
|
||
mouseCursorNeedsReplace = true
|
||
location.y = textDisplay.frame.height - 8
|
||
}
|
||
|
||
mouseCursor(hide: Mouse2Joystick)
|
||
|
||
if ( Mouse2Joystick ) {
|
||
if mouseCursorNeedsReplace {
|
||
CGWarpMouseCursorPosition(convertPoint(toCG: location))
|
||
}
|
||
|
||
pdl_prevarr[0] = pdl_valarr[0]
|
||
pdl_valarr[0] = Double(location.x / (textDisplay.frame.width) )
|
||
pdl_diffarr[0] = pdl_valarr[0] - pdl_prevarr[0]
|
||
|
||
pdl_prevarr[1] = pdl_valarr[1]
|
||
pdl_valarr[1] = 1 - Double(location.y / (textDisplay.frame.height) )
|
||
pdl_diffarr[1] = pdl_valarr[1] - pdl_prevarr[1]
|
||
}
|
||
|
||
if ( MouseInterface ) {
|
||
pdl_prevarr[2] = pdl_valarr[2]
|
||
pdl_valarr[2] = Double(location.x / (textDisplay.frame.width) )
|
||
pdl_diffarr[2] = pdl_valarr[2] - pdl_prevarr[2]
|
||
|
||
pdl_prevarr[3] = pdl_valarr[3]
|
||
pdl_valarr[3] = 1 - Double(location.y / (textDisplay.frame.height) )
|
||
pdl_diffarr[3] = pdl_valarr[3] - pdl_prevarr[3]
|
||
}
|
||
}
|
||
|
||
|
||
var savedVideoMode = videoMode_t.init()
|
||
|
||
|
||
override func keyDown(with event: NSEvent) {
|
||
|
||
m6502.ecoSpindown = ecoSpindown;
|
||
|
||
if ( cpuMode == cpuMode_eco ) {
|
||
cpuState = cpuState_running;
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStart(displayLink!)
|
||
#else
|
||
upd.resume()
|
||
#endif
|
||
}
|
||
|
||
// print("keyDown")
|
||
|
||
// switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
|
||
// case [.command] where event.characters == "l",
|
||
// [.command, .shift] where event.characters == "l":
|
||
// print("command-l or command-shift-l")
|
||
// default:
|
||
// break
|
||
// }
|
||
// print( "key = " + (event.charactersIgnoringModifiers ?? ""))
|
||
// print( "\ncharacter = " + (event.characters ?? ""))
|
||
|
||
|
||
if event.modifierFlags.contains(.command) { // .shift, .option, .control ...
|
||
if let chars = event.charactersIgnoringModifiers {
|
||
switch chars {
|
||
case "a":
|
||
// print("CMD + A")
|
||
SelectAll()
|
||
return // to avoid deselect text
|
||
|
||
case "c":
|
||
// print("CMD + C")
|
||
Copy()
|
||
|
||
case "v":
|
||
// print("CMD + V")
|
||
Paste()
|
||
|
||
default:
|
||
super.keyDown(with: event)
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
#if FUNCTIONTEST
|
||
#else
|
||
let keyCode = Int(event.keyCode)
|
||
switch keyCode {
|
||
case leftArrowKey:
|
||
// print("LEFT");
|
||
if ( Keyboard2Joystick ) {
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
pdl_valarr[0] = 0
|
||
}
|
||
kbdInput(0x88)
|
||
|
||
case rightArrowKey:
|
||
// print("RIGHT")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
if ( Keyboard2Joystick ) {
|
||
pdl_valarr[0] = 1
|
||
}
|
||
kbdInput(0x95)
|
||
|
||
case downArrowKey:
|
||
// print("DOWN")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
if ( Keyboard2Joystick ) {
|
||
pdl_valarr[1] = 1
|
||
}
|
||
else {
|
||
kbdInput(0x8A)
|
||
}
|
||
|
||
case upArrowKey:
|
||
// print("UP")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
if ( Keyboard2Joystick ) {
|
||
pdl_valarr[1] = 0
|
||
}
|
||
else {
|
||
kbdInput(0x8B)
|
||
}
|
||
|
||
case F4FunctionKey:
|
||
// if let debugger = DebuggerWindowController.current {
|
||
// debugger.Continue()
|
||
// }
|
||
|
||
Resume()
|
||
|
||
case F5FunctionKey:
|
||
// if let debugger = DebuggerWindowController.current {
|
||
// debugger.Pause()
|
||
// }
|
||
|
||
Pause()
|
||
|
||
case F6FunctionKey:
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.Step_Over(event)
|
||
}
|
||
|
||
case F7FunctionKey:
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.Step_In(event)
|
||
}
|
||
|
||
case F8FunctionKey:
|
||
if let debugger = DebuggerWindowController.shared {
|
||
debugger.Step_Out(event)
|
||
}
|
||
|
||
default:
|
||
// print("keycode: %d", keyCode)
|
||
if let chars = event.characters {
|
||
let char = chars.uppercased()[chars.startIndex]
|
||
if let ascii = char.asciiValue {
|
||
kbdInput(ascii)
|
||
}
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
// displayField.currentEditor()?.selectedRange = NSMakeRange(0, 0)
|
||
textDisplay.setSelectedRange(NSRange())
|
||
}
|
||
|
||
|
||
override func keyUp(with event: NSEvent) {
|
||
// print("keyUp")
|
||
// switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
|
||
// case [.command] where event.characters == "l",
|
||
// [.command, .shift] where event.characters == "l":
|
||
// print("command-l or command-shift-l")
|
||
// default:
|
||
// break
|
||
// }
|
||
// print( "key = " + (event.charactersIgnoringModifiers ?? ""))
|
||
// print( "\ncharacter = " + (event.characters ?? ""))
|
||
|
||
#if FUNCTIONTEST
|
||
#else
|
||
let keyCode = Int(event.keyCode)
|
||
switch keyCode {
|
||
case leftArrowKey:
|
||
// kbdInput(0x08)
|
||
// setIO(0xC064, 127);
|
||
// print("left")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
pdl_valarr[0] = 0.5
|
||
|
||
case rightArrowKey:
|
||
// kbdInput(0x15)
|
||
// setIO(0xC064, 128);
|
||
// print("right")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
pdl_valarr[0] = 0.5
|
||
|
||
case downArrowKey:
|
||
// kbdInput(0x0B)
|
||
// setIO(0xC065, 127);
|
||
// print("down")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
pdl_valarr[1] = 0.5
|
||
|
||
case upArrowKey:
|
||
// kbdInput(0x0A)
|
||
// setIO(0xC065, 128);
|
||
// print("up")
|
||
// Keyboard 2 JoyStick (Game Controller / Paddle)
|
||
pdl_valarr[1] = 0.5
|
||
|
||
default:
|
||
// print("keycode: %d", keyCode)
|
||
// if let chars = event.characters {
|
||
// let char = chars.uppercased()[chars.startIndex]
|
||
// if let ascii = char.asciiValue {
|
||
// kbdInput(ascii)
|
||
// }
|
||
// }
|
||
break
|
||
}
|
||
#endif
|
||
|
||
kbdUp()
|
||
|
||
}
|
||
|
||
override func flagsChanged(with event: NSEvent) {
|
||
let flags = event.modifierFlags.intersection(.deviceIndependentFlagsMask)
|
||
switch flags {
|
||
case [.shift]:
|
||
setIO(0xC061, 0)
|
||
setIO(0xC062, 0)
|
||
setIO(0xC063, 0) // inverted (bit 7: not pressed)
|
||
// print("shift key is pressed")
|
||
// case [.control]:
|
||
// print("control key is pressed")
|
||
case [.option] :
|
||
setIO(0xC061, 0)
|
||
setIO(0xC062, 1 << 7)
|
||
setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed)
|
||
// print("option key is pressed")
|
||
case [.command]:
|
||
// print("Command key is pressed")
|
||
setIO(0xC061, 1 << 7)
|
||
setIO(0xC062, 0)
|
||
setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed)
|
||
// case [.control, .shift]:
|
||
// print("control-shift keys are pressed")
|
||
case [.option, .shift]:
|
||
setIO(0xC061, 1 << 7)
|
||
setIO(0xC062, 0)
|
||
setIO(0xC063, 0) // inverted (bit 7: not pressed)
|
||
// print("option-shift keys are pressed")
|
||
case [.command, .shift]:
|
||
setIO(0xC061, 1 << 7)
|
||
setIO(0xC062, 0)
|
||
setIO(0xC063, 0) // inverted (bit 7: not pressed)
|
||
// print("command-shift keys are pressed")
|
||
// case [.control, .option]:
|
||
// print("control-option keys are pressed")
|
||
// case [.control, .command]:
|
||
// print("control-command keys are pressed")
|
||
case [.option, .command]:
|
||
setIO(0xC061, 1 << 7)
|
||
setIO(0xC062, 1 << 7)
|
||
setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed)
|
||
// print("option-command keys are pressed")
|
||
// case [.shift, .control, .option]:
|
||
// print("shift-control-option keys are pressed")
|
||
// case [.shift, .control, .command]:
|
||
// print("shift-control-command keys are pressed")
|
||
// case [.control, .option, .command]:
|
||
// print("control-option-command keys are pressed")
|
||
case [.shift, .command, .option]:
|
||
setIO(0xC061, 1 << 7)
|
||
setIO(0xC062, 1 << 7)
|
||
setIO(0xC063, 0); // inverted (bit 7: not pressed)
|
||
// print("shift-command-option keys are pressed")
|
||
// case [.shift, .control, .option, .command]:
|
||
// print("shift-control-option-command keys are pressed")
|
||
// case [.function]:
|
||
// print("function key is pressed")
|
||
// case [.capsLock]:
|
||
// print("capsLock key is pressed")
|
||
|
||
case [.control, .command, .option]:
|
||
Mouse2Joystick = !Mouse2Joystick
|
||
mouseCursor(hide: Mouse2Joystick)
|
||
ToolBarController.current?.MouseToJoystickMenuItem.state = Mouse2Joystick ? .on : .off
|
||
|
||
default:
|
||
setIO(0xC061, 0)
|
||
setIO(0xC062, 0)
|
||
setIO(0xC063, 1 << 7) // inverted (bit 7: not pressed)
|
||
// print("no modifier keys are pressed")
|
||
}
|
||
}
|
||
|
||
|
||
var was = 0;
|
||
|
||
var currentVideoMode = videoMode
|
||
var lastFrameTime = CACurrentMediaTime() as Double
|
||
var frameCounter : UInt32 = 0
|
||
var clkCounter : Double = 0
|
||
|
||
var shadowTxt : String = ""
|
||
var unicodeTextString : String = ""
|
||
|
||
|
||
func Flash() {
|
||
self.frameCnt += 1
|
||
|
||
if ( self.frameCnt == fps / video_fps_divider / 2 ) {
|
||
if !flashInverted {
|
||
ViewController.charConvTbl = ViewController.charConvTblFlashOn
|
||
flashInverted = true
|
||
}
|
||
}
|
||
else if ( self.frameCnt >= fps / video_fps_divider ) {
|
||
self.frameCnt = 0
|
||
|
||
if flashInverted {
|
||
ViewController.charConvTbl = ViewController.charConvTblFlashOff
|
||
flashInverted = false
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
var textNeedRender = false
|
||
|
||
func RenderText() {
|
||
Flash()
|
||
|
||
textNeedRender = false
|
||
|
||
var fromLines = 0
|
||
var toLines = textLines
|
||
|
||
if videoMode.text == 0 {
|
||
if videoMode.mixed == 1 {
|
||
fromLines = toLines - 4
|
||
}
|
||
else {
|
||
toLines = 0
|
||
}
|
||
}
|
||
|
||
unicodeTextArray = NSArray(array: txtClear, copyItems: true) as! [Character]
|
||
|
||
// render an empty space to eiminate displaying text portion of the screen covered by graphics
|
||
let charDisposition = videoMode.col80 == 0 ? 1 : 2
|
||
for y in 0 ..< fromLines {
|
||
unicodeTextArray[ y * (textCols * charDisposition + lineEndChars) + textCols * charDisposition] = "\n"
|
||
}
|
||
|
||
// 40 col
|
||
if videoMode.col80 == 0 {
|
||
if MEMcfg.txt_page_2 == 0 {
|
||
if (MEMcfg.RD_AUX_MEM == 0) {
|
||
textBufferPointer = ViewController.textPage1Pointer
|
||
}
|
||
else {
|
||
textBufferPointer = ViewController.textIntPage1BufferPointer
|
||
}
|
||
}
|
||
else {
|
||
if (MEMcfg.RD_AUX_MEM == 0) {
|
||
textBufferPointer = ViewController.textPage2Pointer
|
||
}
|
||
else {
|
||
textBufferPointer = ViewController.textIntPage2BufferPointer
|
||
}
|
||
}
|
||
|
||
if textBufferPointer.elementsEqual(ViewController.textPageShadowBuffer) {
|
||
}
|
||
else {
|
||
ViewController.textPage1Pointer.copyBytes(to: ViewController.textPageShadowBuffer)
|
||
textNeedRender = true
|
||
|
||
// render the rest of the text screen
|
||
for y in fromLines ..< toLines {
|
||
for x in 0 ..< textCols {
|
||
let byte = textBufferPointer[ ViewController.textLineOfs[y] + x ]
|
||
let idx = Int(byte);
|
||
let chr = ViewController.charConvTbl[idx]
|
||
|
||
unicodeTextArray[ y * (textCols + lineEndChars) + x ] = chr
|
||
}
|
||
|
||
unicodeTextArray[ y * (textCols + lineEndChars) + textCols ] = "\n"
|
||
}
|
||
|
||
unicodeTextString = String(unicodeTextArray)
|
||
}
|
||
}
|
||
// 80 col
|
||
else {
|
||
let auxPage = ( MEMcfg.is_80STORE == 1 ) && ( MEMcfg.txt_page_2 == 1 )
|
||
|
||
let textIntBuffer = auxPage ? ViewController.textIntPage1BufferPointer : ViewController.textPage1Pointer
|
||
let textAuxBuffer = auxPage ? ViewController.textPage1Pointer : ViewController.textAuxBufferPointer
|
||
|
||
// render the rest of the text screen
|
||
for y in fromLines ..< toLines {
|
||
for x in 0 ..< textCols {
|
||
let byte = textIntBuffer[ ViewController.textLineOfs[y] + x ]
|
||
let idx = Int(byte);
|
||
let chr = ViewController.charConvTbl[idx]
|
||
|
||
unicodeTextArray[ y * (textCols * 2 + lineEndChars) + x * 2 + 1] = chr
|
||
|
||
let byte2 = textAuxBuffer[ ViewController.textLineOfs[y] + x ]
|
||
let idx2 = Int(byte2);
|
||
let chr2 = ViewController.charConvTbl[idx2]
|
||
|
||
unicodeTextArray[ y * (textCols * 2 + lineEndChars) + x * 2] = chr2
|
||
}
|
||
|
||
unicodeTextArray[ y * (textCols * 2 + lineEndChars) + textCols * 2] = "\n"
|
||
}
|
||
|
||
unicodeTextString = String(unicodeTextArray)
|
||
}
|
||
}
|
||
|
||
func SetSplashScreenFont() {
|
||
for view in splashScreen.subviews {
|
||
if view is NSTextField {
|
||
let textField = view as! NSTextField
|
||
if let fontSize = textField.font?.pointSize {
|
||
textField.font = NSFont(name: "PrintChar21", size: fontSize)
|
||
}
|
||
}
|
||
}
|
||
|
||
// Set Apple ][ font
|
||
if let fontSize = textDisplay.font?.pointSize {
|
||
textDisplay.font = NSFont(name: "PrintChar21", size: fontSize)
|
||
ViewController.charConvTblFlashOn = ViewController.charConvTblFlashOn40
|
||
ViewController.charConvTblFlashOff = ViewController.charConvTblFlashOff40
|
||
}
|
||
}
|
||
|
||
|
||
func SetCol40() {
|
||
// Set Apple ][ font
|
||
if let fontSize = textDisplay.font?.pointSize {
|
||
textDisplay.font = NSFont(name: "PrintChar21", size: fontSize)
|
||
ViewController.charConvTblFlashOn = ViewController.charConvTblFlashOn40
|
||
ViewController.charConvTblFlashOff = ViewController.charConvTblFlashOff40
|
||
}
|
||
}
|
||
|
||
|
||
func SetCol80() {
|
||
// Set Apple ][ font
|
||
if let fontSize = textDisplay.font?.pointSize {
|
||
textDisplay.font = NSFont(name: "PRNumber3", size: fontSize)
|
||
ViewController.charConvTblFlashOn = ViewController.charConvTblCol80
|
||
ViewController.charConvTblFlashOff = ViewController.charConvTblCol80
|
||
}
|
||
}
|
||
|
||
|
||
func UpdateText() {
|
||
|
||
// TODO: Render text Screen in native C
|
||
// txt = String(bytesNoCopy: ViewController.textScreen!, length: 10, encoding: .ascii, freeWhenDone: false) ?? "HMM"
|
||
|
||
if videoMode.col80 != currentVideoMode.col80 {
|
||
currentVideoMode.col80 = videoMode.col80
|
||
|
||
if videoMode.col80 == 1 {
|
||
SetCol80()
|
||
}
|
||
else {
|
||
SetCol40()
|
||
}
|
||
}
|
||
|
||
if textNeedRender || shadowTxt != unicodeTextString {
|
||
shadowTxt = unicodeTextString
|
||
|
||
let selectedRange = textDisplay.selectedRange()
|
||
|
||
// DispatchQueue.main.async { [self] in
|
||
textDisplay.string = unicodeTextString
|
||
textDisplay.setSelectedRange(selectedRange)
|
||
// }
|
||
|
||
// let bold14 = NSFont.boldSystemFont(ofSize: 14.0)
|
||
// let textColor = NSColor.red
|
||
// let attribs = [NSAttributedString.Key.font:bold14,NSAttributedString.Key.foregroundColor:textColor,NSAttributedString.Key.paragraphStyle:textParagraph]
|
||
|
||
// let textParagraph = NSMutableParagraphStyle()
|
||
// textParagraph.lineSpacing = 0
|
||
// textParagraph.minimumLineHeight = 32.0
|
||
// textParagraph.maximumLineHeight = 32.0
|
||
//
|
||
// let attribs = [NSAttributedString.Key.paragraphStyle: textParagraph]
|
||
// let attrString:NSAttributedString = NSAttributedString.init(string: unicodeTextString, attributes: attribs)
|
||
// display.attributedStringValue = attrString
|
||
}
|
||
// display.stringValue = "testing\nit\nout"
|
||
}
|
||
|
||
|
||
func UpdateCPUspeed() {
|
||
// DispatchQueue.main.async { [self] in
|
||
// under ~1.5 MHz -- 3 decimals to be able to display 1.023 MHz
|
||
if ( (mhz < 1.4) && (mhz != floor(mhz)) ) {
|
||
speedometer.stringValue = String(format: "%0.3lf MHz", mhz);
|
||
}
|
||
// under ~100 MHz -- 1 decimal
|
||
else if (mhz < 95) {
|
||
speedometer.stringValue = String(format: "%0.1lf MHz", mhz);
|
||
}
|
||
// over ~1000 MHz -- 1 decimal GHz
|
||
else if (mhz > 950) {
|
||
speedometer.stringValue = String(format: "%0.1lf GHz", mhz / 1000);
|
||
}
|
||
// hundreds -- no decimals
|
||
else {
|
||
speedometer.stringValue = String(format: "%0.0lf MHz", mhz);
|
||
}
|
||
// }
|
||
}
|
||
|
||
|
||
func RenderGraphics() {
|
||
// only refresh graphics view when needed (aka not in text mode)
|
||
if ( videoMode.text == 0 ) {
|
||
if ( videoMode.hires == 0 ) {
|
||
// when we change video mode, buffer needs to be cleared to avoid artifacts
|
||
if ( savedVideoMode.text == 1 )
|
||
|| ( savedVideoMode.mixed != videoMode.mixed )
|
||
|| ( savedVideoMode.hires != videoMode.hires )
|
||
{
|
||
lores.clearScreen()
|
||
lores.isHidden = false
|
||
hires.isHidden = true
|
||
unicodeTextString = String(unicodeTextArray)
|
||
}
|
||
|
||
lores.Render()
|
||
}
|
||
else {
|
||
// when we change video mode, buffer needs to be cleared to avoid artifacts
|
||
if ( savedVideoMode.text == 1 )
|
||
|| ( savedVideoMode.mixed != videoMode.mixed )
|
||
|| ( savedVideoMode.hires != videoMode.hires )
|
||
{
|
||
hires.clearScreen()
|
||
hires.isHidden = false
|
||
lores.isHidden = true
|
||
unicodeTextString = String(unicodeTextArray)
|
||
}
|
||
|
||
hires.Render()
|
||
}
|
||
}
|
||
else if ( savedVideoMode.text == 0 ) {
|
||
// we just switched from grahics to text
|
||
lores.isHidden = true
|
||
hires.isHidden = true
|
||
unicodeTextString = String(unicodeTextArray)
|
||
}
|
||
|
||
savedVideoMode = videoMode
|
||
}
|
||
|
||
|
||
func Render() {
|
||
self.RenderText()
|
||
|
||
// Rendering is happening in the main thread, which has two implications:
|
||
// 1. We can update UI elements
|
||
// 2. it is independent of the simulation, de that is running in the background thread while we are busy with rendering...
|
||
// DispatchQueue.global(qos: .userInitiated).async {
|
||
DispatchQueue.main.async {
|
||
self.UpdateText()
|
||
self.UpdateCPUspeed()
|
||
|
||
#if HIRES
|
||
self.RenderGraphics()
|
||
#endif // HIRES
|
||
}
|
||
}
|
||
|
||
|
||
override func mouseDown(with event: NSEvent) {
|
||
// print(#function)
|
||
|
||
switch event.modifierFlags.intersection(.deviceIndependentFlagsMask) {
|
||
case [.control, .command, .option]:
|
||
Mouse2Joystick = !Mouse2Joystick
|
||
mouseCursor(hide: Mouse2Joystick)
|
||
ToolBarController.current?.MouseToJoystickMenuItem.state = Mouse2Joystick ? .on : .off
|
||
|
||
default:
|
||
break
|
||
}
|
||
|
||
if ( Mouse2Joystick ) {
|
||
setIO(0xC061, 1 << 7)
|
||
}
|
||
|
||
}
|
||
|
||
override func mouseUp(with event: NSEvent) {
|
||
// print(#function)
|
||
if ( Mouse2Joystick ) {
|
||
setIO(0xC061, 0)
|
||
}
|
||
}
|
||
|
||
override func rightMouseDown(with event: NSEvent) {
|
||
// print(#function)
|
||
if ( Mouse2Joystick ) {
|
||
setIO(0xC062, 1 << 7)
|
||
}
|
||
}
|
||
|
||
override func rightMouseUp(with event: NSEvent) {
|
||
// print(#function)
|
||
if ( Mouse2Joystick ) {
|
||
setIO(0xC062, 0)
|
||
}
|
||
}
|
||
|
||
override func otherMouseDown(with event: NSEvent) {
|
||
// print(#function)
|
||
if ( Mouse2Joystick ) {
|
||
setIO(0xC063, 0) // inverted (bit 7: 0 = pressed)
|
||
}
|
||
}
|
||
|
||
override func otherMouseUp(with event: NSEvent) {
|
||
// print(#function)
|
||
if ( Mouse2Joystick ) {
|
||
setIO(0xC063, 1 << 7) // inverted (bit 7: 1 = not pressed)
|
||
}
|
||
}
|
||
|
||
|
||
func diskButtonUpdate() {
|
||
DispatchQueue.main.async {
|
||
|
||
if ( self.frameCounter % DEF_DRV_LED_DIV == 0 ) {
|
||
|
||
// Disk Motor LED
|
||
if spkr_is_disk_motor_playing() {
|
||
if self.disk1_led.isHidden {
|
||
self.disk1_led.isHidden = false
|
||
}
|
||
}
|
||
else {
|
||
if !self.disk1_led.isHidden {
|
||
self.disk1_led.isHidden = true
|
||
}
|
||
}
|
||
|
||
// Disk Loaded
|
||
if woz_is_loaded() > 0 {
|
||
if self.disk1_closed.isHidden {
|
||
self.disk1_closed.isHidden = false
|
||
}
|
||
}
|
||
else {
|
||
if !self.disk1_closed.isHidden {
|
||
self.disk1_closed.isHidden = true
|
||
}
|
||
}
|
||
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
func debugBreak() {
|
||
Pause()
|
||
spkr_play_disk_motor_time = 0
|
||
spkr_stopAll()
|
||
|
||
// TODO: This should be in Debugger!
|
||
debuggerPauseUpdate()
|
||
debuggerShowWindow()
|
||
}
|
||
|
||
|
||
let UpdateSemaphore = DispatchSemaphore(value: 1)
|
||
func Update() {
|
||
// clk_6502_per_frm_max = 0
|
||
|
||
if UpdateSemaphore.wait(timeout: .now() + 0.001) == .timedOut {
|
||
// get back here next time...
|
||
return
|
||
}
|
||
|
||
diskButtonUpdate()
|
||
|
||
switch cpuState {
|
||
case cpuState_running:
|
||
clkCounter += Double(m6502.clkfrm)
|
||
// we start a new frame from here, so CPU is running even while rendering
|
||
// m6502.clkfrm = 0
|
||
|
||
frameCounter += 1
|
||
|
||
if ( frameCounter % fps == 0 ) {
|
||
let currentTime = CACurrentMediaTime() as Double
|
||
let elpasedTime = currentTime - lastFrameTime
|
||
lastFrameTime = currentTime
|
||
mhz = Double( clkCounter ) / (elpasedTime * M);
|
||
clkCounter = 0
|
||
}
|
||
|
||
#if SPEEDTEST
|
||
#else
|
||
|
||
// poll input devices like mouse and joystick
|
||
// Input()
|
||
|
||
// run some code
|
||
// cpuState = cpuState_executing
|
||
// DispatchQueue.global(qos: .userInitiated).async {
|
||
if m6502.debugger.on {
|
||
m6502_Debug()
|
||
|
||
switch m6502.interrupt {
|
||
case HALT:
|
||
debugBreak()
|
||
|
||
case BREAK: // BRK instruction
|
||
debugBreak()
|
||
|
||
case BREAKPOINT: // CPU halted because of a breakpoint
|
||
debugBreak()
|
||
|
||
case BREAKRDMEM: // CPU halted because of a breakpoint
|
||
debugBreak()
|
||
|
||
case BREAKWRMEM: // CPU halted because of a breakpoint
|
||
debugBreak()
|
||
|
||
case RET:
|
||
if m6502.debugger.mask.ret == 1 {
|
||
// Step_Out / Step_Over
|
||
if m6502.PC >= m6502.debugger.SP {
|
||
debugBreak()
|
||
}
|
||
}
|
||
|
||
case INV: // invalid instruction
|
||
debugBreak()
|
||
|
||
default:
|
||
break
|
||
}
|
||
|
||
// clear iterrupt
|
||
m6502.interrupt = NO_INT
|
||
}
|
||
else {
|
||
// DispatchQueue.global(qos: .userInitiated).async {
|
||
m6502_Run()
|
||
// }
|
||
// cpuState = cpuState_running
|
||
}
|
||
|
||
// video rendering
|
||
if ( frameCounter % video_fps_divider == 0 ) {
|
||
Render()
|
||
}
|
||
|
||
// TODO: This should be in Debugger!
|
||
if let debugger = DebuggerViewController.shared {
|
||
debugger.Update()
|
||
}
|
||
|
||
#endif
|
||
|
||
break
|
||
|
||
case cpuState_executing:
|
||
// prevent running more instances per session
|
||
// setCPUClockSpeed(freq: MHz_6502 - 1)
|
||
break
|
||
|
||
case cpuState_halting:
|
||
cpuState = cpuState_halted
|
||
// last video rendering before halt
|
||
Render()
|
||
|
||
break
|
||
|
||
case cpuState_halted:
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
CVDisplayLinkStop(displayLink!)
|
||
#else
|
||
upd.suspend()
|
||
#endif
|
||
|
||
break
|
||
|
||
default:
|
||
break
|
||
}
|
||
|
||
// ok, from now you can update again
|
||
UpdateSemaphore.signal()
|
||
}
|
||
|
||
|
||
// func FromBuf(ptr: UnsafeMutablePointer<UInt8>, length len: Int) -> String? {
|
||
// // convert the bytes using the UTF8 encoding
|
||
// if let theString = NSString(bytes: ptr, length: len, encoding: NSUTF8StringEncoding) {
|
||
// return theString as String
|
||
// } else {
|
||
// return nil // the bytes aren't valid UTF8
|
||
// }
|
||
// }
|
||
|
||
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
#else
|
||
var upd = RepeatingTimer(timeInterval: 1)
|
||
|
||
func newUpdateTimer( timeInterval : Double ) {
|
||
upd.kill()
|
||
upd = RepeatingTimer(timeInterval: timeInterval)
|
||
upd.eventHandler = {
|
||
self.Update()
|
||
}
|
||
upd.resume()
|
||
}
|
||
|
||
#endif
|
||
|
||
|
||
// Kelvin Sherlock's fix to avoid uninstalled font problems
|
||
override func awakeFromNib() {
|
||
// self.display.font = NSFont(name: "PrintChar21", size: 32)
|
||
}
|
||
|
||
required init?(coder: NSCoder) {
|
||
// print(#function)
|
||
super.init(coder: coder)
|
||
|
||
ViewController.shared = self
|
||
}
|
||
|
||
|
||
// func render() {
|
||
//
|
||
// var x = vertexData[2 * 3 + 0]
|
||
// x -= 0.01
|
||
// if x < -1 {
|
||
// x = 1
|
||
// }
|
||
// vertexData[2 * 3 + 0] = x
|
||
//
|
||
// guard let drawable = metalLayer?.nextDrawable() else { return }
|
||
// let renderPassDescriptor = MTLRenderPassDescriptor()
|
||
// renderPassDescriptor.colorAttachments[0].texture = drawable.texture
|
||
// renderPassDescriptor.colorAttachments[0].loadAction = .clear
|
||
// renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(
|
||
// red: 0.0,
|
||
// green: 104.0/255.0,
|
||
// blue: 55.0/255.0,
|
||
// alpha: 0.3)
|
||
//
|
||
// if let commandBuffer = commandQueue.makeCommandBuffer() {
|
||
// if let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) {
|
||
// renderEncoder.setRenderPipelineState(pipelineState)
|
||
// let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) // 1
|
||
// vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) // 2
|
||
// renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, index: 0)
|
||
// renderEncoder.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
|
||
// renderEncoder.endEncoding()
|
||
// commandBuffer.present(drawable)
|
||
// commandBuffer.commit()
|
||
// }
|
||
// }
|
||
// }
|
||
//
|
||
//
|
||
// var vertexData: [Float] = [
|
||
// 0.0, 1.0, 0.0,
|
||
// -1.0, -1.0, 0.0,
|
||
// 1.0, -1.0, 0.0
|
||
// ]
|
||
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
|
||
// The callback function is called everytime CVDisplayLink says its time to get a new frame.
|
||
let displayLinkOutputCallback : CVDisplayLinkOutputCallback = { (displayLink: CVDisplayLink, _ inNow: UnsafePointer<CVTimeStamp>, _ inOutputTime: UnsafePointer<CVTimeStamp>, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>, _ vController: UnsafeMutableRawPointer?) -> CVReturn in
|
||
|
||
/* The displayLinkContext is CVDisplayLink's parameter definition of the view in which we are working.
|
||
In order to access the methods of a given view we need to specify what kind of view it is as right
|
||
now the UnsafeMutablePointer<Void> just means we have a pointer to "something". To cast the pointer
|
||
such that the compiler at runtime can access the methods associated with our SwiftOpenGLView, we use
|
||
an unsafeBitCast. The definition of which states, "Returns the the bits of x, interpreted as having
|
||
type U." We may then call any of that view's methods. Here we call drawView() which we draw a
|
||
frame for rendering. */
|
||
// unsafeBitCast(displayLinkContext, SwiftOpenGLView.self).renderFrame()
|
||
|
||
/// This can be a precise FPS updater so MHz would be dead perfect pitch -- however, it makes things worse
|
||
// let now : CVTimeStamp = inNow.pointee
|
||
//// print( "RateScaler:", now.rateScalar )
|
||
// if (mySelf.last_frame_time != 0) {
|
||
// let videoTimeDiff = now.videoTime - mySelf.last_frame_time
|
||
// let currentFPS = Double(now.videoTimeScale) / Double(videoTimeDiff)
|
||
//
|
||
// fps = currentFPS
|
||
// // mySelf.setCPUClockSpeed(freq: MHz_6502)
|
||
// clk_6502_per_frm = UInt64( MHz_6502 * M / currentFPS )
|
||
// clk_6502_per_frm_set = clk_6502_per_frm
|
||
//
|
||
// }
|
||
// mySelf.last_frame_time = now.videoTime;
|
||
|
||
let mySelf = Unmanaged<ViewController>.fromOpaque(vController!).takeUnretainedValue()
|
||
mySelf.Update();
|
||
|
||
// We are going to assume that everything went well for this mock up, and pass success as the CVReturn
|
||
return kCVReturnSuccess
|
||
}
|
||
|
||
// sets a callback at every screen refresh (normally 60Hz)
|
||
func setupDisplayLink() {
|
||
// Grab the a link to the active displays, set the callback defined above, and start the link.
|
||
/* An alternative to a nested function is a global function or a closure passed as the argument--a local function
|
||
(i.e. a function defined within the class) is NOT allowed. */
|
||
// The UnsafeMutablePointer<Void>(unsafeAddressOf(self)) passes a pointer to the instance of our class.
|
||
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
|
||
|
||
let unsafeSelf: UnsafeMutableRawPointer = Unmanaged.passUnretained(self).toOpaque()
|
||
CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, unsafeSelf)
|
||
// CVDisplayLinkStart(displayLink!)
|
||
}
|
||
#endif
|
||
|
||
@IBOutlet weak var disk1_led: NSImageView!
|
||
@IBOutlet weak var disk2_led: NSImageView!
|
||
@IBOutlet weak var disk1_closed: NSImageView!
|
||
@IBOutlet weak var disk2_closed: NSImageView!
|
||
|
||
|
||
var keyDownMonitor : Any?
|
||
var keyUpMonitor : Any?
|
||
|
||
func keyEventsOn() {
|
||
// NSEvent.removeMonitor(NSEvent.EventType.flagsChanged)
|
||
// NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) {
|
||
// self.flagsChanged(with: $0)
|
||
// return $0
|
||
// }
|
||
|
||
if let event = keyDownMonitor {
|
||
NSEvent.removeMonitor(event)
|
||
keyDownMonitor = nil
|
||
}
|
||
keyDownMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) {
|
||
// print("keyDown event")
|
||
self.keyDown(with: $0)
|
||
return nil
|
||
}
|
||
if let event = keyUpMonitor {
|
||
NSEvent.removeMonitor(event)
|
||
keyUpMonitor = nil
|
||
}
|
||
keyUpMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyUp) {
|
||
// print("keyUp event")
|
||
self.keyUp(with: $0)
|
||
return nil
|
||
}
|
||
}
|
||
|
||
|
||
func keyEventsOff() {
|
||
// NSEvent.removeMonitor(NSEvent.EventType.flagsChanged)
|
||
if let event = keyDownMonitor {
|
||
NSEvent.removeMonitor(event)
|
||
keyDownMonitor = nil
|
||
}
|
||
if let event = keyUpMonitor {
|
||
NSEvent.removeMonitor(event)
|
||
keyUpMonitor = nil
|
||
}
|
||
}
|
||
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
// if let image = Disk1_open_on_img {
|
||
// Disk1_ButtonCell.alternateImage = image
|
||
// NSLog("Disk1_ButtonCell:%@", Disk1_ButtonCell)
|
||
// }
|
||
|
||
// let layer = CALayer()
|
||
// hires.layer = layer
|
||
// hires.wantsLayer = true
|
||
|
||
// device = MTLCreateSystemDefaultDevice()
|
||
// metalLayer = CAMetalLayer() // 1
|
||
// metalLayer.device = device // 2
|
||
// metalLayer.pixelFormat = .bgra8Unorm // 3
|
||
// metalLayer.framebufferOnly = true // 4
|
||
// metalLayer.frame = hires.layer!.frame // 5
|
||
// hires.layer!.addSublayer(metalLayer) // 6
|
||
//
|
||
// let dataSize = vertexData.count * MemoryLayout.size(ofValue: vertexData[0]) // 1
|
||
// vertexBuffer = device.makeBuffer(bytes: vertexData, length: dataSize, options: []) // 2
|
||
//
|
||
// // 1
|
||
// let defaultLibrary = device.makeDefaultLibrary()!
|
||
// let fragmentProgram = defaultLibrary.makeFunction(name: "basic_fragment")
|
||
// let vertexProgram = defaultLibrary.makeFunction(name: "basic_vertex")
|
||
//
|
||
// // 2
|
||
// let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
|
||
// pipelineStateDescriptor.vertexFunction = vertexProgram
|
||
// pipelineStateDescriptor.fragmentFunction = fragmentProgram
|
||
// pipelineStateDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm
|
||
//
|
||
// // 3
|
||
// pipelineState = try! device.makeRenderPipelineState(descriptor: pipelineStateDescriptor)
|
||
//
|
||
// commandQueue = device.makeCommandQueue()
|
||
|
||
// timer = CADisplayLink(target: self, selector: #selector(gameloop))
|
||
// timer.add(to: RunLoop.main, forMode: .default)
|
||
|
||
|
||
// Set Apple ][ font
|
||
SetCol40()
|
||
SetSplashScreenFont()
|
||
|
||
openLog()
|
||
|
||
hires.clearScreen();
|
||
|
||
spkr_load_sfx( Bundle.main.resourcePath! + "/sfx" )
|
||
|
||
let woz_err = woz_loadFile( Bundle.main.resourcePath! + "/dsk/Apple DOS 3.3 January 1983.woz" )
|
||
chk_woz_load(err: woz_err)
|
||
woz_flags.image_file_readonly = 1
|
||
|
||
//view.frame = CGRect(origin: CGPoint(), size: NSScreen.main!.visibleFrame.size)
|
||
|
||
// createHiRes()
|
||
|
||
self.textDisplayScroller.scaleUnitSquare(to: NSSize(width: 1, height: 1))
|
||
|
||
keyEventsOn()
|
||
|
||
// displayField.maximumNumberOfLines = textLines
|
||
// displayField.preferredMaxLayoutWidth = displayField.frame.width
|
||
|
||
// DispatchQueue.main.asyncAfter(deadline: .now() + 1/fps, execute: {
|
||
// self.update()
|
||
// })
|
||
|
||
// #if FUNCTIONTEST
|
||
// #else
|
||
// DispatchQueue.global(qos: .background).async {
|
||
// self.update()
|
||
// }
|
||
|
||
// upd.eventHandler = {
|
||
// self.Update()
|
||
// }
|
||
// upd.resume()
|
||
|
||
|
||
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
// Grab the a link to the active displays, set the callback defined above, and start the link.
|
||
/* An alternative to a nested function is a global function or a closure passed as the argument--a local function
|
||
(i.e. a function defined within the class) is NOT allowed. */
|
||
// The UnsafeMutablePointer<Void>(unsafeAddressOf(self)) passes a pointer to the instance of our class.
|
||
CVDisplayLinkCreateWithActiveCGDisplays(&displayLink)
|
||
let unsafeSelf: UnsafeMutableRawPointer = Unmanaged.passUnretained(self).toOpaque()
|
||
CVDisplayLinkSetOutputCallback(displayLink!, displayLinkOutputCallback, unsafeSelf)
|
||
CVDisplayLinkStart(displayLink!)
|
||
#else
|
||
newUpdateTimer( timeInterval: 1 / Double(fps) )
|
||
#endif
|
||
|
||
// soundGapSlider.integerValue = Int(spkr_extra_buf)
|
||
// ledingInitEdgeLabel.title = "ILE: " + String( SPKR_INITIAL_LEADING_EDGE )
|
||
// initialLeadEdgeSlider.floatValue = SPKR_INITIAL_LEADING_EDGE
|
||
// leadingEdgeLabel.title = "LE: " + String( SPKR_FADE_LEADING_EDGE )
|
||
// leadEdgeSlider.floatValue = SPKR_FADE_LEADING_EDGE
|
||
// trailingInitEdgeLabel.title = "ITE: " + String( SPKR_INITIAL_TRAILING_EDGE )
|
||
// initialTailEdgeSlider.floatValue = SPKR_INITIAL_TRAILING_EDGE
|
||
// trailingEdgeLabel.title = "TE: " + String( SPKR_FADE_TRAILING_EDGE )
|
||
// tailEdgeSlider.floatValue = SPKR_FADE_TRAILING_EDGE
|
||
|
||
|
||
// // BUGFIX: I am not sure why but if I do not adjust the frame and bounds size
|
||
// // couple of times, Cocoa miscalculates them
|
||
// var size = MonitorView.textViewBounds
|
||
// size.width /= 2
|
||
// size.height /= 2
|
||
// textDisplay.setFrameSize(size)
|
||
// textDisplay.setBoundsSize(size)
|
||
//
|
||
// size.width += 136 + 11 * 2
|
||
// size.height += 64 + 11 * 2
|
||
// view.setFrameSize(size)
|
||
// view.setBoundsSize(size)
|
||
|
||
}
|
||
|
||
|
||
override func viewDidAppear() {
|
||
// displayField.currentEditor()?.selectedRange = NSMakeRange(0, 0)
|
||
// self.displayField.window?.makeFirstResponder(self)
|
||
textDisplay.setSelectedRange(NSRange())
|
||
textDisplay.window?.makeFirstResponder(self)
|
||
|
||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||
self.monitorView.adjustTextDisplaySize()
|
||
}
|
||
}
|
||
|
||
|
||
func setSpkrExtrabuf( freq : Double ) {
|
||
// TODO: Probably this is not the best way to deal with the problem: To make sound continous independent of FPS and Freq
|
||
|
||
// spkr_extra_buf = Int32( 780 / fps )
|
||
|
||
switch freq {
|
||
case 0.25:
|
||
spkr_extra_buf = -65
|
||
break
|
||
|
||
case 0.5:
|
||
spkr_extra_buf = -140
|
||
break
|
||
|
||
case 1.5:
|
||
spkr_extra_buf = 175
|
||
break
|
||
|
||
case 2.0:
|
||
// spkr_extra_buf = Int32( Double(spkr_extra_buf) * 2.961538461538462 ) // normally it should come up as 77, but this way it is calculated with FPS
|
||
// spkr_extra_buf = 20
|
||
spkr_extra_buf = 195 // 88
|
||
break
|
||
|
||
case 2.8:
|
||
spkr_extra_buf = 65 // 185
|
||
break
|
||
|
||
case 4.0:
|
||
// spkr_extra_buf = Int32( Double(spkr_extra_buf) * 1.346153846153846 ) // normally it should come up as 35, but this way it is calculated with FPS
|
||
// spkr_extra_buf = 45
|
||
spkr_extra_buf = 25 // 90 // 80 // 20
|
||
break
|
||
|
||
default:
|
||
// spkr_extra_buf = Int32( 780 / fps ) // normally it should come up as 26, but this way it is calculated with FPS
|
||
spkr_extra_buf = 0 // 26
|
||
break
|
||
}
|
||
|
||
soundGapLabel.title = String( spkr_extra_buf )
|
||
soundGapSlider.integerValue = Int(spkr_extra_buf)
|
||
}
|
||
|
||
|
||
func setCPUClockSpeed( freq : Double ) {
|
||
spkr_stopAll();
|
||
|
||
MHz_6502 = freq
|
||
clk_6502_per_frm = UInt32( MHz_6502 * M / Double(fps) )
|
||
clk_6502_per_frm_set = clk_6502_per_frm
|
||
|
||
spkr_extra_buf = 0
|
||
// setSpkrExtrabuf(freq: freq)
|
||
}
|
||
|
||
|
||
@IBOutlet weak var soundGapLabel: NSTextFieldCell!
|
||
@IBAction func extraBuf(_ sender: NSSlider) {
|
||
spkr_extra_buf = sender.intValue
|
||
soundGapLabel.title = String( spkr_extra_buf )
|
||
}
|
||
|
||
@IBOutlet weak var ledingInitEdgeLabel: NSTextFieldCell!
|
||
@IBAction func leadingInitEdgeSelected(_ sender: NSSlider) {
|
||
SPKR_INITIAL_LEADING_EDGE = sender.floatValue
|
||
ledingInitEdgeLabel.title = "ILE: " + String( SPKR_INITIAL_LEADING_EDGE )
|
||
}
|
||
|
||
@IBOutlet weak var leadingEdgeLabel: NSTextFieldCell!
|
||
@IBAction func leadingEdgeSelected(_ sender: NSSlider) {
|
||
SPKR_FADE_LEADING_EDGE = sender.floatValue
|
||
leadingEdgeLabel.title = "LE: " + String( SPKR_FADE_LEADING_EDGE )
|
||
}
|
||
|
||
@IBOutlet weak var trailingInitEdgeLabel: NSTextFieldCell!
|
||
@IBAction func trailingInitEdgeSelected(_ sender: NSSlider) {
|
||
SPKR_INITIAL_TRAILING_EDGE = sender.floatValue
|
||
trailingInitEdgeLabel.title = "ITE: " + String( SPKR_INITIAL_TRAILING_EDGE )
|
||
}
|
||
|
||
@IBOutlet weak var trailingEdgeLabel: NSTextFieldCell!
|
||
@IBAction func trailingEdgeSelected(_ sender: NSSlider) {
|
||
SPKR_FADE_TRAILING_EDGE = sender.floatValue
|
||
trailingEdgeLabel.title = "TE: " + String( SPKR_FADE_TRAILING_EDGE )
|
||
}
|
||
|
||
|
||
@IBOutlet weak var wozExtraLabel: NSTextFieldCell!
|
||
@IBAction func wozExtraSelected(_ sender: NSSlider) {
|
||
extraForward = Int32(sender.floatValue)
|
||
wozExtraLabel.title = "WE: " + String( extraForward )
|
||
}
|
||
|
||
|
||
@IBOutlet weak var EMALabel: NSTextFieldCell!
|
||
@IBAction func EMASelected(_ sender: NSSlider) {
|
||
spkr_ema_len = Int32(sender.floatValue)
|
||
EMALabel.title = "EMA: " + String( spkr_ema_len )
|
||
}
|
||
|
||
|
||
func setSimulationMode( mode : String ) {
|
||
switch ( mode ) {
|
||
case "Eco":
|
||
cpuMode = cpuMode_eco
|
||
|
||
fps = DEFAULT_FPS
|
||
video_fps_divider = ECO_VIDEO_DIV
|
||
spkr_fps_divider = DEF_SPKR_DIV
|
||
break
|
||
|
||
case "Game":
|
||
cpuMode = cpuMode_game
|
||
cpuState = cpuState_running
|
||
|
||
fps = GAME_FPS
|
||
video_fps_divider = GAME_VIDEO_DIV
|
||
spkr_fps_divider = GAME_SPKR_DIV
|
||
break
|
||
|
||
default:
|
||
cpuMode = cpuMode_normal
|
||
cpuState = cpuState_running
|
||
|
||
fps = DEFAULT_FPS
|
||
video_fps_divider = DEF_VIDEO_DIV
|
||
spkr_fps_divider = DEF_SPKR_DIV
|
||
break
|
||
}
|
||
|
||
// spkr_fps_divider = fps / spkr_fps
|
||
// spkr_fps = fps;
|
||
|
||
spkr_play_timeout = SPKR_PLAY_TIMEOUT // * spkr_fps_divider
|
||
|
||
// pixelTrail = pow(256, 1 / Double(fps / video_fps_divider / 3) )
|
||
|
||
// spkr_buf_size = spkr_sample_rate * 2 / spkr_fps
|
||
#if SCHEDULER_CVDISPLAYLINK
|
||
#else
|
||
newUpdateTimer( timeInterval: 1 / Double(fps) )
|
||
#endif
|
||
|
||
setCPUClockSpeed(freq: MHz_6502)
|
||
|
||
// TODO: Better way to deal with speaker!!!
|
||
spkr_play_timeout = SPKR_PLAY_TIMEOUT * Int32(video_fps_divider)
|
||
}
|
||
|
||
|
||
@IBAction func setCPUMode(_ sender: NSPopUpButton) {
|
||
setSimulationMode(mode: sender.selectedItem?.title ?? "Normal" )
|
||
}
|
||
|
||
@IBOutlet weak var initialLeadEdgeSlider: NSSlider!
|
||
@IBOutlet weak var leadEdgeSlider: NSSlider!
|
||
@IBOutlet weak var initialTailEdgeSlider: NSSlider!
|
||
@IBOutlet weak var tailEdgeSlider: NSSlider!
|
||
@IBOutlet weak var soundGapSlider: NSSlider!
|
||
|
||
// @IBOutlet weak var soundGap: NSTextFieldCell!
|
||
//
|
||
// @IBAction func SoundGapChanged(_ sender: NSStepper) {
|
||
// SoundGap.integerValue = sender.integerValue
|
||
// spkr_extra_buf = Int32( sender.integerValue )
|
||
// }
|
||
|
||
@IBAction func CRTMonitorOnOff(_ sender: NSButton) {
|
||
CRTMonitor = sender.state == .on
|
||
scanLines.isHidden = !CRTMonitor
|
||
|
||
if ( CRTMonitor ) {
|
||
textDisplay.textColor = .white
|
||
// TODO: Adjust gamma so pixels are brighter
|
||
}
|
||
else {
|
||
textDisplay.textColor = colorWhite
|
||
// TODO: Adjust gamma so pixels are dimmer
|
||
}
|
||
|
||
hires.RenderFullScreen()
|
||
}
|
||
|
||
func ColorMonitorSelector( color : Bool ) {
|
||
ColorMonitor = color
|
||
|
||
if ( ColorMonitor ) {
|
||
textDisplay.textColor = colorWhite // .white
|
||
}
|
||
else {
|
||
textDisplay.textColor = colorGreen // .green
|
||
}
|
||
|
||
hires.RenderFullScreen()
|
||
}
|
||
|
||
@IBAction func ColorMonitorOnOff(_ sender: NSButton) {
|
||
ColorMonitorSelector( color: sender.state == .on )
|
||
}
|
||
|
||
func MonoMonitorChange( color: String ) {
|
||
switch color {
|
||
case "Green":
|
||
ColorMonitor = false
|
||
monoColor = colorGreen
|
||
hires.monoColor = hires.color_green
|
||
|
||
case "Amber":
|
||
ColorMonitor = false
|
||
monoColor = colorOrange
|
||
hires.monoColor = hires.color_orange
|
||
|
||
default:
|
||
ColorMonitor = false
|
||
monoColor = colorWhite
|
||
hires.monoColor = hires.color_white
|
||
}
|
||
|
||
textDisplay.textColor = monoColor
|
||
hires.RenderFullScreen()
|
||
}
|
||
|
||
@IBAction func MonitorChange(_ sender: NSButton) {
|
||
switch sender.title {
|
||
case "Green Mono":
|
||
MonoMonitorChange(color: "Green")
|
||
|
||
case "Amber Mono":
|
||
MonoMonitorChange(color: "Amber")
|
||
|
||
default:
|
||
MonoMonitorChange(color: "White")
|
||
}
|
||
}
|
||
|
||
@IBAction func Keyboard2JoystickOnOff(_ sender: NSButton) {
|
||
Keyboard2Joystick = sender.state == .on
|
||
}
|
||
|
||
@IBAction func Mouse2JoystickOnOff(_ sender: NSButton) {
|
||
Mouse2Joystick = sender.state == .on
|
||
}
|
||
|
||
@IBAction func MouseOnOff(_ sender: NSButton) {
|
||
MouseInterface = sender.state == .on
|
||
}
|
||
|
||
@IBAction func QuickDisk(_ sender: NSButton) {
|
||
if sender.state == .on {
|
||
diskAccelerator_enabled = 1;
|
||
}
|
||
else {
|
||
diskAccelerator_enabled = 0;
|
||
}
|
||
}
|
||
|
||
@IBAction func DiskSound(_ sender: NSButton) {
|
||
if sender.state == .on {
|
||
disk_sfx_enabled = 1;
|
||
}
|
||
else {
|
||
disk_sfx_enabled = 0;
|
||
}
|
||
}
|
||
|
||
|
||
func openDiskImage( url: URL ) {
|
||
woz_eject()
|
||
|
||
switch url.pathExtension.uppercased() {
|
||
|
||
case "WOZ":
|
||
let err = woz_loadFile( url.path )
|
||
|
||
if err == WOZ_ERR_OK {
|
||
NSDocumentController.shared.noteNewRecentDocumentURL(URL(fileURLWithPath: url.path))
|
||
}
|
||
else {
|
||
self.chk_woz_load(err: err)
|
||
}
|
||
|
||
case "DSK", "DO", "PO" :
|
||
let err = dsk2woz( url.path )
|
||
|
||
if err == WOZ_ERR_OK {
|
||
let err = woz_parseBuffer()
|
||
|
||
if err == WOZ_ERR_OK {
|
||
NSDocumentController.shared.noteNewRecentDocumentURL(URL(fileURLWithPath: url.path))
|
||
}
|
||
}
|
||
else {
|
||
self.chk_woz_load(err: err)
|
||
}
|
||
|
||
default:
|
||
break
|
||
|
||
}
|
||
|
||
}
|
||
|
||
@objc func openDiskImageDialog() {
|
||
let openPanel = NSOpenPanel()
|
||
openPanel.title = "Open Disk Image"
|
||
openPanel.allowsMultipleSelection = false
|
||
openPanel.canChooseDirectories = false
|
||
openPanel.canCreateDirectories = false
|
||
openPanel.canChooseFiles = true
|
||
openPanel.allowedFileTypes = ["dsk","do","po","nib", "woz"]
|
||
|
||
openPanel.begin { (result) -> Void in
|
||
if result == NSApplication.ModalResponse.OK {
|
||
// print("file:", openPanel.url!.path)
|
||
//Do what you will
|
||
//If there's only one URL, surely 'openPanel.URL'
|
||
//but otherwise a for loop works
|
||
|
||
if let url = openPanel.url {
|
||
self.openDiskImage(url: url)
|
||
}
|
||
else {
|
||
let a = NSAlert()
|
||
a.messageText = "File Not Found"
|
||
a.informativeText = "Could not locate selected file"
|
||
a.alertStyle = .critical
|
||
|
||
a.beginSheetModal( for: self.view.window! )
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
@objc func saveDiskImage() {
|
||
let openPanel = NSOpenPanel()
|
||
openPanel.title = "Save Disk Image"
|
||
openPanel.allowsMultipleSelection = false
|
||
openPanel.canChooseDirectories = false
|
||
openPanel.canCreateDirectories = false
|
||
openPanel.canChooseFiles = true
|
||
openPanel.allowedFileTypes = ["dsk","do","po","nib", "woz"]
|
||
|
||
openPanel.begin { (result) -> Void in
|
||
if result == NSApplication.ModalResponse.OK {
|
||
// print("file:", openPanel.url!.path)
|
||
//Do what you will
|
||
//If there's only one URL, surely 'openPanel.URL'
|
||
//but otherwise a for loop works
|
||
|
||
if let filePath = openPanel.url?.path {
|
||
let woz_err = woz_saveFile( filePath )
|
||
|
||
if woz_err == WOZ_ERR_OK {
|
||
NSDocumentController.shared.noteNewRecentDocumentURL(URL(fileURLWithPath: filePath))
|
||
}
|
||
else {
|
||
self.chk_woz_load(err: woz_err)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@IBOutlet weak var QuickDisk_Disk1: NSMenuItem!
|
||
@IBOutlet weak var QuickDisk_Disk2: NSMenuItem!
|
||
@IBOutlet weak var DiskSound_Disk1: NSMenuItem!
|
||
@IBOutlet weak var DiskSound_Disk2: NSMenuItem!
|
||
|
||
@IBAction func Disk1(_ sender: NSPopUpButton) {
|
||
switch sender.selectedItem?.tag {
|
||
case 1: // Open
|
||
openDiskImageDialog()
|
||
|
||
case 2: // Save
|
||
saveFile()
|
||
|
||
case 3: // Save As...
|
||
saveFileAs()
|
||
|
||
case 4: // Eject
|
||
woz_eject()
|
||
|
||
case 21: // Quick Disk
|
||
if diskAccelerator_enabled == 0 {
|
||
diskAccelerator_enabled = 1;
|
||
}
|
||
else {
|
||
diskAccelerator_enabled = 0;
|
||
}
|
||
|
||
let state : NSControl.StateValue = diskAccelerator_enabled == 1 ? .on : .off
|
||
QuickDisk_Disk1?.state = state
|
||
QuickDisk_Disk2?.state = state
|
||
break
|
||
|
||
case 22: // Disk Sound
|
||
if disk_sfx_enabled == 0 {
|
||
disk_sfx_enabled = 1;
|
||
}
|
||
else {
|
||
disk_sfx_enabled = 0;
|
||
}
|
||
|
||
let state : NSControl.StateValue = disk_sfx_enabled == 1 ? .on : .off
|
||
DiskSound_Disk1?.state = state
|
||
DiskSound_Disk2?.state = state
|
||
break
|
||
|
||
case 1000: // Open Default Disk Image
|
||
if let menuIdentifier = sender.selectedItem?.title {
|
||
let woz_err = woz_loadFile( Bundle.main.resourcePath! + "/dsk/" + menuIdentifier + ".woz" )
|
||
ViewController.shared?.chk_woz_load(err: woz_err)
|
||
woz_flags.image_file_readonly = 1
|
||
}
|
||
|
||
default:
|
||
break
|
||
}
|
||
}
|
||
|
||
@IBAction func traceEnable(_ sender: NSButton) {
|
||
switch sender.state {
|
||
case .on:
|
||
m6502.debugger.mask.trace = 1
|
||
openLog()
|
||
|
||
default:
|
||
m6502.debugger.mask.trace = 0
|
||
closeLog()
|
||
}
|
||
}
|
||
|
||
|
||
func saveFile() {
|
||
if ( woz_flags.image_file_readonly != 0 ) {
|
||
// it is readonly, save it to a different file...
|
||
saveFileAs()
|
||
}
|
||
else {
|
||
// save WOZ image file overwriting the original image
|
||
woz_saveFile(nil)
|
||
}
|
||
}
|
||
|
||
func saveFileAs() {
|
||
let savePanel = NSSavePanel()
|
||
savePanel.title = "Save WOZ Disk Image As..."
|
||
savePanel.begin { (result) in
|
||
if result.rawValue == NSApplication.ModalResponse.OK.rawValue {
|
||
woz_saveFile( savePanel.url?.path );
|
||
}
|
||
else {
|
||
let a = NSAlert()
|
||
a.messageText = "Are you sure?"
|
||
a.informativeText = "Are you sure you would like to cancel and lose all modification you have made to the Disk Image?\nALERT: You will lose all new files and modified files from this Disk Image since you loaded. Your decision if permanent and irreversible!"
|
||
a.addButton(withTitle: "Save")
|
||
a.addButton(withTitle: "Cancel")
|
||
a.alertStyle = .warning
|
||
|
||
a.beginSheetModal(for: self.view.window!, completionHandler: { (modalResponse) -> Void in
|
||
if modalResponse == .alertFirstButtonReturn {
|
||
self.saveFileAs()
|
||
}
|
||
})
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
func Cheat_Wavy_Navy_Victory() {
|
||
#if !DEBUGGER
|
||
JUMP( 0x1528 ) // called when player clears the level
|
||
#endif
|
||
}
|
||
|
||
func Cheat_Wavy_Navy_Add_3_Ships() {
|
||
#if !DEBUGGER
|
||
let ships = min( getMEM( 0x4746 ) + 3, 9 )
|
||
setMEM( 0x4746, ships )
|
||
m6502.A = ships
|
||
// m6502.X = 0x10
|
||
setMEM16(0x4728, 0x16F0) // cursor pos: 0x4728:x, 0x4729:y
|
||
CALL( 0x1FDA ) // position and number needed? A:Number of ships X:0x10
|
||
#endif
|
||
}
|
||
|
||
func Cheat_Wavy_Navy_OtherCheats() {
|
||
#if !DEBUGGER
|
||
// Replace STC / SBC $0x1 to NOPs...
|
||
setMEM( 0x1E63, 0xEA )
|
||
setMEM( 0x1E64, 0xEA )
|
||
setMEM( 0x1E65, 0xEA )
|
||
|
||
// // call to decease
|
||
// setMEM( 0x1556, 0xEA );
|
||
// setMEM( 0x1557, 0xEA );
|
||
// setMEM( 0x1558, 0xEA );
|
||
|
||
// setMEM( 0x15F3, 0xEA );
|
||
// setMEM( 0x15F4, 0xEA );
|
||
// setMEM( 0x15F5, 0xEA );
|
||
|
||
// setMEM( 0x15EA, 0xEA );
|
||
// setMEM( 0x15EB, 0xEA );
|
||
|
||
// // no end
|
||
// setMEM( 0x1515, 0xEA );
|
||
// setMEM( 0x1516, 0xEA );
|
||
|
||
// // no end
|
||
// setMEM( 0x1537, 0xEA );
|
||
// setMEM( 0x1538, 0xEA );
|
||
|
||
// lose to win
|
||
setMEM( 0x1545, 0xEA )
|
||
setMEM( 0x1546, 0xEA )
|
||
|
||
// var i : UInt16 = 0x15EA;
|
||
// while i < 0x1608 {
|
||
// setMEM( i, 0xEA )
|
||
// i += 1
|
||
// }
|
||
#endif
|
||
}
|
||
|
||
func Cheat_Wavy_Navy_Never_Lose() -> NSControl.StateValue {
|
||
#if !DEBUGGER
|
||
// Replace STC / SBC #$01 to NOPs...
|
||
// setMEM( 0x1E63, 0xEA )
|
||
// setMEM( 0x1E64, 0xEA )
|
||
// setMEM( 0x1E65, 0xEA )
|
||
|
||
if ( getMEM16(0x1E64) == 0x01E9 ) { // SBC #$01
|
||
// Replace SBC #$01 to SBC #$00...
|
||
setMEM( 0x1E65, 0 )
|
||
return .on
|
||
}
|
||
else if ( getMEM16(0x1E64) == 0x00E9 ) { // SBC #$00
|
||
// Replace SBC #$00 to SBC #$01...
|
||
setMEM( 0x1E65, 1 )
|
||
return .off
|
||
}
|
||
else {
|
||
print("Not Wavy Navy!")
|
||
return .off
|
||
}
|
||
#else
|
||
return .off
|
||
#endif
|
||
}
|
||
|
||
func Cheat_Wavy_Navy_Lose_To_Win() -> NSControl.StateValue {
|
||
#if !DEBUGGER
|
||
if ( getMEM16(0x1545) == 0x09F0 ) { // BEQ $1550
|
||
// lose to win
|
||
setMEM16( 0x1545, 0xEAEA ) // NOP NOP
|
||
return .on
|
||
}
|
||
else if ( getMEM16(0x1545) == 0xEAEA ) { // NOP NOP
|
||
// lose to win
|
||
setMEM16( 0x1545, 0x09F0 ) // BEQ $1550
|
||
return .off
|
||
}
|
||
else {
|
||
print("Not Wavy Navy!")
|
||
return .off
|
||
}
|
||
#else
|
||
return .off
|
||
#endif
|
||
}
|
||
|
||
|
||
func Get_Hard_Hat_Mack() -> UInt8 {
|
||
#if !DEBUGGER
|
||
return getMEM( 0x4EDF )
|
||
#else
|
||
return 0
|
||
#endif
|
||
}
|
||
|
||
func Cheat_Hard_Hat_Mack(add : UInt8) -> UInt8 {
|
||
#if !DEBUGGER
|
||
let ships = min( getMEM( 0x4EDF ) + add, 9 )
|
||
setMEM( 0x4EDF, ships )
|
||
// CALL( 0x1219 ) // starts from the beginning
|
||
CALL( 0x1A2B ) // refresh Mack counter on screen
|
||
|
||
return ships
|
||
#else
|
||
return 0
|
||
#endif
|
||
}
|
||
|
||
|
||
func Cheat_Hard_Hat_Mack_Never_Lose() -> NSControl.StateValue {
|
||
#if !DEBUGGER
|
||
setMEM( 0x0503, 0x18 )
|
||
setMEM( 0x0504, 0x60 )
|
||
|
||
setMEM( 0x50A5, 0xEA )
|
||
setMEM( 0x50A6, 0xEA )
|
||
setMEM( 0x50A7, 0xEA )
|
||
#endif
|
||
return .on
|
||
}
|
||
}
|
||
|
||
|
||
@_cdecl("woz_ask_to_save")
|
||
func woz_ask_to_save() {
|
||
ViewController.shared?.saveFile()
|
||
}
|
||
|