Debugger: added live screen view; pause/resume; added 6502 code interpreter for viewing code in memory

This commit is contained in:
Yoshi Sugawara 2021-02-04 06:36:32 -10:00
parent cc2fee38a4
commit 97e5d8a551
18 changed files with 1373 additions and 18 deletions

View File

@ -245,6 +245,17 @@
9250DCB31CAEEF990093CE9A /* MfiGameControllerHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 9250DCB21CAEEF990093CE9A /* MfiGameControllerHandler.m */; };
9250DCB51CAEFD3B0093CE9A /* GameController.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9250DCB41CAEFD3B0093CE9A /* GameController.framework */; };
927E431A25C48592008E5517 /* CheatFinderManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E431925C48592008E5517 /* CheatFinderManager.swift */; };
927E436425C94C12008E5517 /* Stream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E435925C94C12008E5517 /* Stream.swift */; };
927E436525C94C12008E5517 /* Bus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E435A25C94C12008E5517 /* Bus.swift */; };
927E436625C94C12008E5517 /* CPU+Instructions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E435C25C94C12008E5517 /* CPU+Instructions.swift */; };
927E436725C94C12008E5517 /* CPU+Flags.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E435D25C94C12008E5517 /* CPU+Flags.swift */; };
927E436825C94C12008E5517 /* CPU+Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E435E25C94C12008E5517 /* CPU+Stack.swift */; };
927E436925C94C12008E5517 /* CPU.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E435F25C94C12008E5517 /* CPU.swift */; };
927E436A25C94C12008E5517 /* Instruction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E436125C94C12008E5517 /* Instruction.swift */; };
927E436B25C94C12008E5517 /* Mnemonic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E436225C94C12008E5517 /* Mnemonic.swift */; };
927E436C25C94C12008E5517 /* AddressingMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E436325C94C12008E5517 /* AddressingMode.swift */; };
927E437025C99A11008E5517 /* Debug6502Interpreter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E436F25C99A11008E5517 /* Debug6502Interpreter.swift */; };
927E437325C99C06008E5517 /* DebugMemoryCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 927E437225C99C06008E5517 /* DebugMemoryCodeViewController.swift */; };
928410581CA8443A00DC5D93 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 928410571CA8443A00DC5D93 /* Images.xcassets */; };
92B9EADF24D3369700E6CFB2 /* EmulatorKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92B9EADE24D3369700E6CFB2 /* EmulatorKeyboard.swift */; };
92E2063225AADFB000AE3F28 /* PreviewUI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92E2063125AADF6E00AE3F28 /* PreviewUI.swift */; };
@ -601,6 +612,17 @@
9250DCB21CAEEF990093CE9A /* MfiGameControllerHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MfiGameControllerHandler.m; sourceTree = "<group>"; };
9250DCB41CAEFD3B0093CE9A /* GameController.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = GameController.framework; path = System/Library/Frameworks/GameController.framework; sourceTree = SDKROOT; };
927E431925C48592008E5517 /* CheatFinderManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheatFinderManager.swift; sourceTree = "<group>"; };
927E435925C94C12008E5517 /* Stream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stream.swift; sourceTree = "<group>"; };
927E435A25C94C12008E5517 /* Bus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Bus.swift; sourceTree = "<group>"; };
927E435C25C94C12008E5517 /* CPU+Instructions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CPU+Instructions.swift"; sourceTree = "<group>"; };
927E435D25C94C12008E5517 /* CPU+Flags.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CPU+Flags.swift"; sourceTree = "<group>"; };
927E435E25C94C12008E5517 /* CPU+Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CPU+Stack.swift"; sourceTree = "<group>"; };
927E435F25C94C12008E5517 /* CPU.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPU.swift; sourceTree = "<group>"; };
927E436125C94C12008E5517 /* Instruction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Instruction.swift; sourceTree = "<group>"; };
927E436225C94C12008E5517 /* Mnemonic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Mnemonic.swift; sourceTree = "<group>"; };
927E436325C94C12008E5517 /* AddressingMode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddressingMode.swift; sourceTree = "<group>"; };
927E436F25C99A11008E5517 /* Debug6502Interpreter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debug6502Interpreter.swift; sourceTree = "<group>"; };
927E437225C99C06008E5517 /* DebugMemoryCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMemoryCodeViewController.swift; sourceTree = "<group>"; };
928410571CA8443A00DC5D93 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ActiveGS/Images.xcassets; sourceTree = "<group>"; };
92B9EADD24D3369600E6CFB2 /* ActiveGS-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ActiveGS-Bridging-Header.h"; sourceTree = "<group>"; };
92B9EADE24D3369700E6CFB2 /* EmulatorKeyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmulatorKeyboard.swift; sourceTree = "<group>"; };
@ -1025,10 +1047,7 @@
92B9EADE24D3369700E6CFB2 /* EmulatorKeyboard.swift */,
92E2063125AADF6E00AE3F28 /* PreviewUI.swift */,
92B9EADD24D3369600E6CFB2 /* ActiveGS-Bridging-Header.h */,
92FA0F2325B52EA200663577 /* EmuWrapper.mm */,
92FA0F2625B52EC000663577 /* EmuWrapper.h */,
92FA0F2925B5353D00663577 /* DebugMemoryViewController.swift */,
927E431925C48592008E5517 /* CheatFinderManager.swift */,
927E435725C94BE7008E5517 /* Debugger */,
);
name = Common.iphone;
path = ../Common.iphone;
@ -1142,6 +1161,52 @@
path = ../Common.res;
sourceTree = SOURCE_ROOT;
};
927E435725C94BE7008E5517 /* Debugger */ = {
isa = PBXGroup;
children = (
927E436F25C99A11008E5517 /* Debug6502Interpreter.swift */,
927E435825C94C12008E5517 /* 65C02Interpreter */,
92FA0F2325B52EA200663577 /* EmuWrapper.mm */,
92FA0F2625B52EC000663577 /* EmuWrapper.h */,
92FA0F2925B5353D00663577 /* DebugMemoryViewController.swift */,
927E431925C48592008E5517 /* CheatFinderManager.swift */,
927E437225C99C06008E5517 /* DebugMemoryCodeViewController.swift */,
);
path = Debugger;
sourceTree = "<group>";
};
927E435825C94C12008E5517 /* 65C02Interpreter */ = {
isa = PBXGroup;
children = (
927E435925C94C12008E5517 /* Stream.swift */,
927E435A25C94C12008E5517 /* Bus.swift */,
927E435B25C94C12008E5517 /* CPU */,
927E436025C94C12008E5517 /* Instruction */,
);
path = 65C02Interpreter;
sourceTree = "<group>";
};
927E435B25C94C12008E5517 /* CPU */ = {
isa = PBXGroup;
children = (
927E435C25C94C12008E5517 /* CPU+Instructions.swift */,
927E435D25C94C12008E5517 /* CPU+Flags.swift */,
927E435E25C94C12008E5517 /* CPU+Stack.swift */,
927E435F25C94C12008E5517 /* CPU.swift */,
);
path = CPU;
sourceTree = "<group>";
};
927E436025C94C12008E5517 /* Instruction */ = {
isa = PBXGroup;
children = (
927E436125C94C12008E5517 /* Instruction.swift */,
927E436225C94C12008E5517 /* Mnemonic.swift */,
927E436325C94C12008E5517 /* AddressingMode.swift */,
);
path = Instruction;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@ -1360,10 +1425,14 @@
09BB43B011D92F70005ADA46 /* activeconfig.cpp in Sources */,
09BB43B111D92F70005ADA46 /* ActiveDownload.cpp in Sources */,
92B9EADF24D3369700E6CFB2 /* EmulatorKeyboard.swift in Sources */,
927E436B25C94C12008E5517 /* Mnemonic.swift in Sources */,
09052B9419053C9F00853FAE /* uncompr.cpp in Sources */,
09BB43B211D92F70005ADA46 /* ActiveZip.cpp in Sources */,
09052B8619053C9F00853FAE /* pngwtran.cpp in Sources */,
09052B8B19053C9F00853FAE /* zip.cpp in Sources */,
927E436825C94C12008E5517 /* CPU+Stack.swift in Sources */,
927E436725C94C12008E5517 /* CPU+Flags.swift in Sources */,
927E436925C94C12008E5517 /* CPU.swift in Sources */,
09BB43B311D92F70005ADA46 /* CA2Text.cpp in Sources */,
09BB43B411D92F70005ADA46 /* CEmulatorCtrl.cpp in Sources */,
09BB43B511D92F70005ADA46 /* interface.cpp in Sources */,
@ -1372,8 +1441,10 @@
09BB43B711D92F70005ADA46 /* rom.cpp in Sources */,
09FA6095125A7B3E00B07F77 /* activegsAppDelegate.mm in Sources */,
09052B8119053C9F00853FAE /* pngset.cpp in Sources */,
927E436425C94C12008E5517 /* Stream.swift in Sources */,
09FA6096125A7B3E00B07F77 /* activegsList.mm in Sources */,
09FA6097125A7B3E00B07F77 /* activegsViewController.mm in Sources */,
927E437325C99C06008E5517 /* DebugMemoryCodeViewController.swift in Sources */,
09052B8D19053C9F00853FAE /* compress.cpp in Sources */,
09052B8E19053C9F00853FAE /* crc32.cpp in Sources */,
09FA609A125A7B3E00B07F77 /* asyncimageview.mm in Sources */,
@ -1389,6 +1460,7 @@
09052B7D19053C9F00853FAE /* pngread.cpp in Sources */,
09C81A781657ACAE008539D5 /* adb.cpp in Sources */,
09C81A791657ACAE008539D5 /* async_event.cpp in Sources */,
927E437025C99A11008E5517 /* Debug6502Interpreter.swift in Sources */,
09C81A7A1657ACAE008539D5 /* clock.cpp in Sources */,
09052B9119053C9F00853FAE /* inflate.cpp in Sources */,
09C81A7B1657ACAE008539D5 /* compile_time.cpp in Sources */,
@ -1409,11 +1481,14 @@
924A1BAC1CB81B5800D69162 /* GameControllerKeyRemapController.m in Sources */,
09C81A881657ACAE008539D5 /* SaveState.cpp in Sources */,
09C81A891657ACAE008539D5 /* scc.cpp in Sources */,
927E436625C94C12008E5517 /* CPU+Instructions.swift in Sources */,
09C81A8A1657ACAE008539D5 /* scc_socket_driver.cpp in Sources */,
09052B8319053C9F00853FAE /* pngvcrd.cpp in Sources */,
927E436C25C94C12008E5517 /* AddressingMode.swift in Sources */,
09C81A8B1657ACAE008539D5 /* sim65816.cpp in Sources */,
09C81A8C1657ACAE008539D5 /* smartport.cpp in Sources */,
09052B9019053C9F00853FAE /* inffast.cpp in Sources */,
927E436A25C94C12008E5517 /* Instruction.swift in Sources */,
09C81A8D1657ACAE008539D5 /* sound.cpp in Sources */,
09052B7819053C9F00853FAE /* pngerror.cpp in Sources */,
09052B7B19053C9F00853FAE /* pngmem.cpp in Sources */,
@ -1425,6 +1500,7 @@
09C81A9C1657ACDD008539D5 /* macdriver_generic.cpp in Sources */,
09052B8919053C9F00853FAE /* iowin32.cpp in Sources */,
09C81A9F1657ACDD008539D5 /* scc_macdriver.cpp in Sources */,
927E436525C94C12008E5517 /* Bus.swift in Sources */,
09C81AA71657AD18008539D5 /* emulatorView.mm in Sources */,
09C81AA81657AD18008539D5 /* joystick_iphone.cpp in Sources */,
09C81AA91657AD18008539D5 /* zoomEmulatorView.mm in Sources */,

View File

@ -2,6 +2,8 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import <UIKit/UIKit.h>
#import "KeyMapper.h"
#import "EmuWrapper.h"

View File

@ -0,0 +1,14 @@
//
// BUS.swift
// Monitor
//
// Created by Michał Kałużny on 08.01.18.
//
import Foundation
public protocol Bus {
func read(from address: UInt16) throws -> UInt8
func read(from address: UInt16) throws -> UInt16
func write(to address: UInt16, value: UInt8) throws
}

View File

@ -0,0 +1,72 @@
//
// Flags.swift
// 6502
//
// Created by Michał Kałużny on 17/11/2016.
//
//
import Foundation
extension CPU {
struct Flags: OptionSet {
let rawValue: UInt8
static let negative = Flags(rawValue: 1 << 7)
static let overflow = Flags(rawValue: 1 << 6)
static let always = Flags(rawValue: 1 << 5)
static let `break` = Flags(rawValue: 1 << 4)
static let decimal = Flags(rawValue: 1 << 3)
static let interrupt = Flags(rawValue: 1 << 2)
static let zero = Flags(rawValue: 1 << 1)
static let carry = Flags(rawValue: 1 << 0)
init(rawValue: UInt8) {
self.rawValue = rawValue | 0b0010_0000
}
}
internal func recalculateStatus(flags: Flags, for value: UInt8) {
if flags.contains(.carry) {
calculateCarry(value: value)
}
if flags.contains(.overflow) {
calculateCarry(value: value)
}
if flags.contains(.negative) {
calculateSign(value: value)
}
if flags.contains(.zero) {
calculateZero(value: value)
}
}
private func calculateCarry(value: UInt8) {
}
private func calculateOverflow(value: UInt8) {
}
private func calculateSign(value: UInt8) {
let bit = (value & (1 << 7)) != 0
if bit {
Status.insert(.negative)
} else {
Status.remove(.negative)
}
}
private func calculateZero(value: UInt8) {
if value != 0 {
Status.remove(.zero)
} else {
Status.insert(.zero)
}
}
}

View File

@ -0,0 +1,246 @@
//
// CPU+Instructions.swift
// MOS6502PackageDescription
//
// Created by Michał Kałużny on 09.01.18.
//
import Foundation
extension CPU {
internal func execute(instruction: Instruction) throws {
switch instruction.mnemonic {
//MARK: Branch Instructions
case .BNE:
PC += instruction.size
if Status.contains(.zero) != true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BEQ:
PC += instruction.size
if Status.contains(.zero) == true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BPL:
PC += instruction.size
if Status.contains(.negative) != true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BCC:
PC += instruction.size
if Status.contains(.carry) != true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BCS:
PC += instruction.size
if Status.contains(.carry) == true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BMI:
PC += instruction.size
if Status.contains(.negative) == true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BVC:
PC += instruction.size
if Status.contains(.overflow) != true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
case .BVS:
PC += instruction.size
if Status.contains(.overflow) == true {
PC = try instruction.addressingMode.value(with: self, bus: bus)
}
//MARK: Stack Operations
case .TXS:
SP = X
PC += instruction.size
case .TSX:
X = SP
recalculateStatus(flags: [.zero, .negative], for: X)
PC += instruction.size
case .PLP:
let value = try pop() as UInt8
Status = Flags(rawValue: value)
PC += instruction.size
case .PHP:
try push(Status.rawValue | 0b0001_0000)
PC += instruction.size
case .PHA:
try push(A)
PC += instruction.size
case .PLA:
A = try pop()
recalculateStatus(flags: [.zero, .negative], for: A)
PC += instruction.size
case .JSR:
// JSR writes the *address of the last byte* of the instruction.
try push(PC + instruction.size - 1)
PC = try instruction.addressingMode.value(with: self, bus: bus)
case .RTS:
PC += instruction.size
PC = try pop()
// We would land on the *last byte* of the JSR, just before the jump, we need to advance the PC
PC += 1
//MARK: Register Operations
case .INX:
X = X &+ 1
recalculateStatus(flags: [.zero, .negative], for: X)
PC += instruction.size
case .DEX:
X = X &- 1
recalculateStatus(flags: [.zero, .negative], for: X)
PC += instruction.size
case .INY:
Y = Y &+ 1
recalculateStatus(flags: [.zero, .negative], for: Y)
PC += instruction.size
case .DEY:
Y = Y &- 1
recalculateStatus(flags: [.zero, .negative], for: Y)
PC += instruction.size
case .TYA:
A = Y
recalculateStatus(flags: [.negative, .zero], for: A)
PC += instruction.size
case .TXA:
A = X
recalculateStatus(flags: [.negative, .zero], for: A)
PC += instruction.size
case .TAX:
X = A
recalculateStatus(flags: [.negative, .zero], for: X)
PC += instruction.size
case .TAY:
Y = A
recalculateStatus(flags: [.negative, .zero], for: Y)
PC += instruction.size
//MARK: Flags Operations:
case .SEI:
Status.insert(.interrupt)
PC += instruction.size
case .CLD:
Status.remove(.decimal)
PC += instruction.size
case .CLC:
Status.remove(.carry)
PC += instruction.size
//MARK: Interrupt Operations:
case .BRK:
try push(PC)
try push(Status.rawValue)
PC = try bus.read(from: CPU.interruptVector)
Status.insert(.break)
//MARK: Other Instructions, clean me up please.
case .LDA:
A = try instruction.addressingMode.value(with: self, bus: bus)
recalculateStatus(flags: [.zero, .negative], for: A)
PC += instruction.size
case .STA:
let address: UInt16 = try instruction.addressingMode.value(with: self, bus: bus)
try bus.write(to: address, value: A)
PC += instruction.size
case .LDX:
X = try instruction.addressingMode.value(with: self, bus: bus)
recalculateStatus(flags: [.zero, .negative], for: X)
PC += instruction.size
case .STX:
let address = try instruction.addressingMode.value(with: self, bus: bus) as UInt16
try bus.write(to: address, value: X)
PC += instruction.size
case .LDY:
Y = try instruction.addressingMode.value(with: self, bus: bus)
recalculateStatus(flags: [.zero, .negative], for: Y)
PC += instruction.size
case .STY:
let address = try instruction.addressingMode.value(with: self, bus: bus) as UInt16
try bus.write(to: address, value: Y)
PC += instruction.size
case .JMP:
PC = try instruction.addressingMode.value(with: self, bus: bus)
case .CMP:
let value = try instruction.addressingMode.value(with: self, bus: bus) as UInt8
if A >= value {
Status.insert(.carry)
} else {
Status.remove(.carry)
}
let result = A &- value
recalculateStatus(flags: [.negative, .zero], for: result)
PC += instruction.size
case .CPY:
let value = try instruction.addressingMode.value(with: self, bus: bus) as UInt8
if Y >= value {
Status.insert(.carry)
} else {
Status.remove(.carry)
}
let result = Y &- value
recalculateStatus(flags: [.negative, .zero], for: result)
PC += instruction.size
case .CPX:
let value = try instruction.addressingMode.value(with: self, bus: bus) as UInt8
if X >= value {
Status.insert(.carry)
} else {
Status.remove(.carry)
}
let result = X &- value
recalculateStatus(flags: [.negative, .zero], for: result)
PC += instruction.size
case .ADC:
let value = try instruction.addressingMode.value(with: self, bus: bus) as UInt8
let carry: UInt8 = Status.contains(.carry) ? 1 : 0
let result = [A, value, carry].map(UInt16.init).reduce(0, +)
if result > UInt8.max {
Status.insert(.carry)
} else {
Status.remove(.carry)
}
let final: UInt8 = UInt8(result & 0xFF)
let overflow = (((A ^ final) & 0x80) != 0) && (((UInt16(A) ^ result) & 0x80) == 0)
if overflow {
Status.insert(.overflow)
} else {
Status.remove(.overflow)
}
A = final
recalculateStatus(flags: [.negative, .zero], for: A)
PC += instruction.size
case .EOR:
let value = try instruction.addressingMode.value(with: self, bus: bus) as UInt8
A = A ^ value
recalculateStatus(flags: [.negative, .zero], for: A)
PC += instruction.size
case .NOP:
PC += instruction.size
case _:
throw Error.unimplementedInstruction(instruction: instruction)
}
}
}

View File

@ -0,0 +1,45 @@
//
// CPU+Stack.swift
// MOS6502PackageDescription
//
// Created by Michał Kałużny on 09.01.18.
//
import Foundation
extension CPU {
static let stackPointerBase: UInt16 = 0x100
internal func pop() throws -> UInt8 {
SP += 1
let value: UInt8 = try bus.read(from: UInt16(SP) + CPU.stackPointerBase)
print("Popping value: \(value.hex)")
return value
}
internal func pop() throws -> UInt16 {
let low: UInt8 = try pop()
let high: UInt8 = try pop()
return UInt16(low) | UInt16(high) << 8
}
internal func push(_ value: UInt8) throws {
try bus.write(to: UInt16(SP) + CPU.stackPointerBase, value: value)
print("Pushing value: \(value.hex)")
SP -= 1
}
internal func push(_ value: UInt16) throws {
let low: UInt8 = UInt8(value & 0xFF)
let high: UInt8 = UInt8(value >> 8) & 0xFF
try push(high)
try push(low)
}
}

View File

@ -0,0 +1,99 @@
//
// CPU.swift
// 6502
//
// Created by Michał Kałużny on 15/11/2016.
//
//
import Foundation
public class CPU {
static let nmiVector: UInt16 = 0xFFFA
static let resetVector: UInt16 = 0xFFFC
static let interruptVector: UInt16 = 0xFFFE
enum Error: Swift.Error {
case unimplementedInstruction(instruction: Instruction)
}
public enum Register {
case X
case Y
}
//MARK: Registers
public var PC: UInt16 = 0
var A: UInt8 = 0
var X: UInt8 = 0
var Y: UInt8 = 0
var SP: UInt8 = 0
var Status: Flags = []
let bus: Bus
let breakpoints: [UInt16] = [0x378A]
public init(bus: Bus) {
self.bus = bus
}
public func reset() throws {
PC = try bus.read(from: CPU.resetVector)
A = 0
X = 0
Y = 0
SP = 0xFF
Status = [.break, .interrupt, .always]
}
public func step() throws {
if breakpoints.contains(PC) {
print("Breakpoint!")
}
print(self)
let instruction = try fetch()
print(instruction)
try execute(instruction: instruction)
print(self)
print("=============================")
}
public func run() throws {
while true {
try step()
}
}
public func fetch() throws -> Instruction {
return try Instruction(from: bus, PC: PC)
}
subscript(register: Register) -> UInt8 {
switch register {
case .X:
return X
case .Y:
return Y
}
}
}
extension CPU: CustomStringConvertible {
public var description: String {
return "PC: \(PC.hex) SP: \(SP.hex) A: \(A.hex) X: \(X.hex) Y: \(Y.hex) \nFlags: \(Status.rawValue.bin)"
}
}
extension FixedWidthInteger {
public var hex: String {
return String(self, radix: 16, uppercase: true)
}
var bin: String {
return String(self, radix: 2, uppercase: true)
}
}

View File

@ -0,0 +1,97 @@
//
// AddressingMode.swift
// 6502
//
// Created by Michał Kałużny on 16/11/2016.
//
//
import Foundation
public extension Instruction {
enum AddressingMode {
enum Error: Swift.Error {
case addressingModeNotImplemented
}
case accumulator
case immediate(data: UInt8)
case implied
case relative(data: Int8)
case absolute(data: UInt16)
case zeroPage(data: UInt8)
case indirect(data: UInt16)
case indexedIndirect(data: UInt8, register: CPU.Register)
case indirectIndexed(data: UInt8, register: CPU.Register)
case absoluteIndexed(data: UInt16, register: CPU.Register)
case zeroPageIndexed(data: UInt8, register: CPU.Register)
func value(with cpu: CPU, bus: Bus) throws -> UInt8 {
switch self {
case .immediate(let data):
return data
case .absolute(let data):
return try bus.read(from: data)
case .absoluteIndexed(let data, let register):
// Check if we should be overflowing here?
return try bus.read(from: data &+ UInt16(cpu[register]))
case _: throw Error.addressingModeNotImplemented
}
}
func value(with cpu: CPU, bus: Bus) throws -> UInt16 {
switch self {
case .absolute(let data):
return data
case .zeroPageIndexed(let base, let register):
switch register {
case .X:
return UInt16(base &+ cpu.X)
case .Y:
return UInt16(base &+ cpu.Y)
}
case .indirect(let data):
return try bus.read(from: data)
case .relative(let data):
return UInt16(Int32(cpu.PC) + Int32(data))
case .zeroPage(let data):
return UInt16(data)
case _: throw Error.addressingModeNotImplemented
}
}
public var dataSize: UInt16 {
switch self {
case .absolute: return 2
case .absoluteIndexed: return 1
case .implied: return 0
case .immediate: return 1
case .indirect: return 2
case .accumulator: return 0
case .relative: return 1
case .zeroPage: return 1
case .indirectIndexed: return 1
case .indexedIndirect: return 1
case .zeroPageIndexed: return 1
}
}
}
}
extension Instruction.AddressingMode: CustomStringConvertible {
public var description: String {
switch self {
case .accumulator: return "A"
case .implied: return ""
case .immediate(let data): return "#$\(String(format: "%02x", data))"
case .relative(let data): return "$\(String(format: "%0X", data))"
case .zeroPage(let data): return "$\(String(format: "%02x", data))"
case .indirect(let data): return "$\(String(format: "%02x", data))"
case .absolute(let data): return "$\(String(format: "%04x", data))"
case .indirectIndexed(let data, let register): return "($\(String(format: "%02x", data)), \(register))"
case .indexedIndirect(let data, let register): return "($\(String(format: "%02x", data))), \(register)"
case .absoluteIndexed(let data, let register): return "$\(String(format: "%04x", data)), \(register)"
case .zeroPageIndexed(let data, let register): return "$\(String(format: "%02x", data)), \(register)"
}
}
}

View File

@ -0,0 +1,215 @@
//
// Instruction.swift
// 6502
//
// Created by Michał Kałużny on 15/11/2016.
//
//
import Foundation
public struct Instruction {
public let opcode: UInt8
public let mnemonic: Mnemonic
public let addressingMode: AddressingMode
public enum Error: Swift.Error {
case unknownOpcode(opcode: UInt8)
}
public init(from bus: Bus, PC: UInt16) throws {
opcode = try bus.read(from: PC) as UInt8
mnemonic = try Mnemonic(opcode)
//MARK: Addressing Mode
switch opcode {
case 0x20: fallthrough
case 0x0d: fallthrough
case 0x0e: fallthrough
case 0x2c: fallthrough
case 0x2d: fallthrough
case 0x2e: fallthrough
case 0x4c: fallthrough
case 0x4d: fallthrough
case 0x4e: fallthrough
case 0x6d: fallthrough
case 0x7e: fallthrough
case 0x8c: fallthrough
case 0x8d: fallthrough
case 0x8e: fallthrough
case 0xac: fallthrough
case 0xad: fallthrough
case 0xae: fallthrough
case 0xcc: fallthrough
case 0xcd: fallthrough
case 0xce: fallthrough
case 0xec: fallthrough
case 0xed: fallthrough
case 0xee:
let data = try bus.read(from: PC + 1) as UInt16
addressingMode = .absolute(data: data)
case 0x1d: fallthrough
case 0x1e: fallthrough
case 0x3d: fallthrough
case 0x3e: fallthrough
case 0x5d: fallthrough
case 0x5e: fallthrough
case 0x6e: fallthrough
case 0x7d: fallthrough
case 0x9d: fallthrough
case 0xbc: fallthrough
case 0xbd: fallthrough
case 0xdd: fallthrough
case 0xde: fallthrough
case 0xfd: fallthrough
case 0xfe:
let data = try bus.read(from: PC + 1) as UInt16
addressingMode = .absoluteIndexed(data: data, register: .X)
case 0x19: fallthrough
case 0x39: fallthrough
case 0x59: fallthrough
case 0x79: fallthrough
case 0x99: fallthrough
case 0xb9: fallthrough
case 0xbe: fallthrough
case 0xd9: fallthrough
case 0xf9:
let data = try bus.read(from: PC + 1) as UInt16
addressingMode = .absoluteIndexed(data: data, register: .Y)
case 0x0a: fallthrough
case 0x2a: fallthrough
case 0x4a: fallthrough
case 0x6a:
addressingMode = .accumulator
case 0x09: fallthrough
case 0x29: fallthrough
case 0x49: fallthrough
case 0x69: fallthrough
case 0xa0: fallthrough
case 0xa2: fallthrough
case 0xa9: fallthrough
case 0xc0: fallthrough
case 0xc9: fallthrough
case 0xe0: fallthrough
case 0xe9:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .immediate(data: data)
case 0x0: fallthrough
case 0x8: fallthrough
case 0x18: fallthrough
case 0x28: fallthrough
case 0x38: fallthrough
case 0x40: fallthrough
case 0x48: fallthrough
case 0x58: fallthrough
case 0x60: fallthrough
case 0x68: fallthrough
case 0x78: fallthrough
case 0x88: fallthrough
case 0x98: fallthrough
case 0x8a: fallthrough
case 0x9a: fallthrough
case 0xa8: fallthrough
case 0xaa: fallthrough
case 0xb8: fallthrough
case 0xba: fallthrough
case 0xc8: fallthrough
case 0xca: fallthrough
case 0xd8: fallthrough
case 0xe8: fallthrough
case 0xea: fallthrough
case 0xf8:
addressingMode = .implied
case 0x1: fallthrough
case 0x21: fallthrough
case 0x41: fallthrough
case 0x61: fallthrough
case 0x81: fallthrough
case 0xa1: fallthrough
case 0xc1: fallthrough
case 0xe1:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .indexedIndirect(data: data, register: .X)
case 0x6c:
let data = try bus.read(from: PC + 1) as UInt16
addressingMode = .indirect(data: data)
case 0x11: fallthrough
case 0x31: fallthrough
case 0x51: fallthrough
case 0x71: fallthrough
case 0x91: fallthrough
case 0xb1: fallthrough
case 0xd1: fallthrough
case 0xf1:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .indirectIndexed(data: data, register: .Y)
case 0x10: fallthrough
case 0x30: fallthrough
case 0x50: fallthrough
case 0x70: fallthrough
case 0x90: fallthrough
case 0xb0: fallthrough
case 0xd0: fallthrough
case 0xf0:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .relative(data: Int8(bitPattern: data))
case 0x5: fallthrough
case 0x6: fallthrough
case 0x24: fallthrough
case 0x25: fallthrough
case 0x26: fallthrough
case 0x45: fallthrough
case 0x46: fallthrough
case 0x65: fallthrough
case 0x66: fallthrough
case 0x84: fallthrough
case 0x85: fallthrough
case 0x86: fallthrough
case 0xa4: fallthrough
case 0xa5: fallthrough
case 0xa6: fallthrough
case 0xc4: fallthrough
case 0xc5: fallthrough
case 0xc6: fallthrough
case 0xe4: fallthrough
case 0xe5: fallthrough
case 0xe6:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .zeroPage(data: data)
case 0x15: fallthrough
case 0x16: fallthrough
case 0x35: fallthrough
case 0x36: fallthrough
case 0x55: fallthrough
case 0x56: fallthrough
case 0x75: fallthrough
case 0x76: fallthrough
case 0x94: fallthrough
case 0x95: fallthrough
case 0xb4: fallthrough
case 0xb5: fallthrough
case 0xd5: fallthrough
case 0xd6: fallthrough
case 0xf5: fallthrough
case 0xf6:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .zeroPageIndexed(data: data, register: .X)
case 0x96: fallthrough
case 0xb6:
let data = try bus.read(from: PC + 1) as UInt8
addressingMode = .zeroPageIndexed(data: data, register: .Y)
default:
throw Error.unknownOpcode(opcode: opcode)
}
}
public var size: UInt16 {
return addressingMode.dataSize + 1
}
}
extension Instruction: CustomStringConvertible {
public var description: String {
return "\(mnemonic) \(addressingMode)"
}
}

View File

@ -0,0 +1,123 @@
//
// Opcode.swift
// 6502
//
// Created by Michał Kałużny on 16/11/2016.
//
//
import Foundation
private let mnemonicTable: [Instruction.Mnemonic?] = [
/* | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | */
/* 0 */ .BRK, .ORA, nil, .SLO, nil, .ORA, .ASL, .SLO, .PHP, .ORA, .ASL, nil, nil, .ORA, .ASL, .SLO, /* 0 */
/* 1 */ .BPL, .ORA, nil, .SLO, nil, .ORA, .ASL, .SLO, .CLC, .ORA, nil, .SLO, nil, .ORA, .ASL, .SLO, /* 1 */
/* 2 */ .JSR, .AND, nil, .RLA, .BIT, .AND, .ROL, .RLA, .PLP, .AND, .ROL, nil, .BIT, .AND, .ROL, .RLA, /* 2 */
/* 3 */ .BMI, .AND, nil, .RLA, nil, .AND, .ROL, .RLA, .SEC, .AND, nil, .RLA, nil, .AND, .ROL, .RLA, /* 3 */
/* 4 */ .RTI, .EOR, nil, .SRE, nil, .EOR, .LSR, .SRE, .PHA, .EOR, .LSR, nil, .JMP, .EOR, .LSR, .SRE, /* 4 */
/* 5 */ .BVC, .EOR, nil, .SRE, nil, .EOR, .LSR, .SRE, .CLI, .EOR, nil, .SRE, nil, .EOR, .LSR, .SRE, /* 5 */
/* 6 */ .RTS, .ADC, nil, .RRA, nil, .ADC, .ROR, .RRA, .PLA, .ADC, .ROR, nil, .JMP, .ADC, .ROR, .RRA, /* 6 */
/* 7 */ .BVS, .ADC, nil, .RRA, nil, .ADC, .ROR, .RRA, .SEI, .ADC, nil, .RRA, nil, .ADC, .ROR, .RRA, /* 7 */
/* 8 */ nil, .STA, nil, .SAX, .STY, .STA, .STX, .SAX, .DEY, nil, .TXA, nil, .STY, .STA, .STX, .SAX, /* 8 */
/* 9 */ .BCC, .STA, nil, nil, .STY, .STA, .STX, .SAX, .TYA, .STA, .TXS, nil, nil, .STA, nil, nil, /* 9 */
/* A */ .LDY, .LDA, .LDX, .LAX, .LDY, .LDA, .LDX, .LAX, .TAY, .LDA, .TAX, nil, .LDY, .LDA, .LDX, .LAX, /* A */
/* B */ .BCS, .LDA, nil, .LAX, .LDY, .LDA, .LDX, .LAX, .CLV, .LDA, .TSX, .LAX, .LDY, .LDA, .LDX, .LAX, /* B */
/* C */ .CPY, .CMP, nil, .DCP, .CPY, .CMP, .DEC, .DCP, .INY, .CMP, .DEX, nil, .CPY, .CMP, .DEC, .DCP, /* C */
/* D */ .BNE, .CMP, nil, .DCP, nil, .CMP, .DEC, .DCP, .CLD, .CMP, nil, .DCP, nil, .CMP, .DEC, .DCP, /* D */
/* E */ .CPX, .SBC, nil, .ISB, .CPX, .SBC, .INC, .ISB, .INX, .SBC, .NOP, .SBC, .CPX, .SBC, .INC, .ISB, /* E */
/* F */ .BEQ, .SBC, nil, .ISB, nil, .SBC, .INC, .ISB, .SED, .SBC, nil, .ISB, nil, .SBC, .INC, .ISB /* F */
]
public extension Instruction {
enum Mnemonic: String {
enum Error: Swift.Error {
case unknownMnemonic(opcode: String)
}
init(_ opcode: UInt8) throws {
guard let mnemonic = mnemonicTable[Int(opcode)] else {
throw Instruction.Error.unknownOpcode(opcode: opcode)
}
self = mnemonic
}
init(_ string: String) throws {
guard let value = Mnemonic(rawValue: string) else {
throw Error.unknownMnemonic(opcode: string)
}
self = value
}
case ADC
case AND
case ASL
case BCC
case BCS
case BEQ
case BIT
case BMI
case BNE
case BPL
case BRK
case BVC
case BVS
case CLC
case CLD
case CLI
case CLV
case CMP
case CPX
case CPY
case DCP
case DEC
case DEX
case DEY
case EOR
case INC
case INX
case INY
case ISB
case JMP
case JSR
case LAX
case LDA
case LDX
case LDY
case LSR
case NOP
case ORA
case PHA
case PHP
case PLA
case PLP
case RLA
case ROL
case ROR
case RRA
case RTI
case RTS
case SBC
case SEC
case SED
case SEI
case SLO
case SRE
case SAX
case STA
case STX
case STY
case TAX
case TAY
case TSX
case TXA
case TXS
case TYA
}
}
extension Instruction.Mnemonic: CustomStringConvertible {
public var description: String {
return "\(rawValue)"
}
}

View File

@ -0,0 +1,60 @@
//
// Stream.swift
// 6502
//
// Created by Michał Kałużny on 15/11/2016.
//
//
import Foundation
protocol RawStreamReadable {}
extension UInt8: RawStreamReadable {}
extension UInt16: RawStreamReadable {}
extension InputStream: Stream {
func read(_ length: Int) -> [UInt8] {
var buffer = Array<UInt8>(repeating: 0, count: length)
_ = read(&buffer, maxLength: length)
return buffer
}
}
class MemoryStream: Stream {
let storage: [UInt8]
var position: Int = 0
init(storage: [UInt8]) {
self.storage = storage
}
open func read(_ length: Int) -> [UInt8] {
if position+length > storage.count {
return [UInt8](repeating: 0, count: length)
}
let slice = storage[position..<position+length]
position += length
return Array(slice)
}
}
protocol Stream {
func read(_ length: Int) -> [UInt8]
}
extension Stream {
func read<T: RawStreamReadable>() -> T {
let size = MemoryLayout<T>.size
let bytes = read(size)
let data = Data(bytes: bytes)
let value: T = data.withUnsafeBytes { $0.pointee }
return value
}
func skip(_ count: Int) {
_ = read(count)
}
}

View File

@ -0,0 +1,68 @@
//
// Debug6502Interpreter.swift
// ActiveGS
//
// Created by Yoshi Sugawara on 2/2/21.
//
import Foundation
public struct AddressedInstruction: CustomStringConvertible {
let address: UInt16
let instruction: Instruction
public var description: String {
return String(format: "$%04X : \(instruction.description)", address)
}
}
enum InterpreterError: Error {
case instructionError(String)
}
public class Debug6502Interpreter: Bus {
public func read(from address: UInt16) throws -> UInt8 {
return memory[Int(address)]
}
public func read(from address: UInt16) throws -> UInt16 {
if address + 1 >= memory.count {
throw InterpreterError.instructionError("Tried to read beyond memory bus")
}
let low: UInt8 = try read(from: address)
let high: UInt8 = try read(from: address + 1)
return (UInt16(high) << 8 | UInt16(low))
}
public func write(to address: UInt16, value: UInt8) throws {
// no-op, interpreter only
}
var memory: [UInt8]
var cpu: CPU! = nil
init(memory: [UInt8]) {
self.memory = memory
self.cpu = CPU(bus: self)
}
public func interpret(startAt address:UInt16 = 0) -> [AddressedInstruction] {
cpu.PC = address
var instructions = [AddressedInstruction]()
while cpu.PC < memory.count {
do {
let instruction = try cpu.fetch()
instructions.append(AddressedInstruction(address: cpu.PC, instruction: instruction))
cpu.PC += instruction.size
} catch Instruction.Error.unknownOpcode(let opcode) {
print("Unknown opcode: \(opcode), skipping...")
cpu.PC += 1
} catch {
print("Error when interpreting, stopping...")
break
}
}
return instructions
}
}

View File

@ -0,0 +1,58 @@
//
// DebugMemoryCodeViewController.swift
// ActiveGS
//
// Created by Yoshi Sugawara on 2/2/21.
//
class DebugMemoryCodeViewController: UIViewController {
let memoryModel: EmuMemoryModel
init(memoryModel: EmuMemoryModel) {
self.memoryModel = memoryModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
lazy var tableView: UITableView = {
let view = UITableView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .clear
view.separatorStyle = .none
view.dataSource = self
view.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
return view
}()
func setupView() {
view.addSubview(tableView)
tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
tableView.backgroundColor = .clear
view.backgroundColor = .black
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
}
}
extension DebugMemoryCodeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return memoryModel.interpretedInstructions.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let instruction = memoryModel.interpretedInstructions[indexPath.row].description
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
cell.textLabel?.font = UIFont(name: "Print Char 21", size: 14)
cell.textLabel?.text = instruction
return cell
}
}

View File

@ -27,6 +27,16 @@ struct Orientation {
}
}
extension UIView {
func getSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
drawHierarchy(in: self.bounds, afterScreenUpdates: true)
let image = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return image
}
}
enum EmuMemoryMapSection: Int {
case zeroPage = 0
case processorStack
@ -104,7 +114,6 @@ enum EmuMemoryMapSection: Int {
}
class EmuMemoryModel {
// make this static for now
let numToDisplayPerCell = 8
let maxMemorySize = 128 * 1024
@ -180,6 +189,22 @@ class EmuMemoryModel {
}
return memory[address]
}
var memoryAsArray: [UInt8] {
var buffer = [UInt8]()
guard let memory = memory else {
return buffer
}
for address in 0..<0x95ff {
buffer.append(memory[address])
}
return buffer
}
lazy var interpretedInstructions: [AddressedInstruction] = {
let interpreter = Debug6502Interpreter(memory: memoryAsArray)
return interpreter.interpret()
}()
func refresh() {
memory = EmuWrapper.memory()
@ -206,6 +231,24 @@ class DebugMemoryButton: UIButton {
}
}
class DebugPauseResumeButton: DebugMemoryButton {
override open var isSelected: Bool {
didSet {
backgroundColor = isSelected ? .red : .clear
}
}
convenience init() {
self.init(type: .custom)
addTarget(self, action: #selector(tapped(_:)), for: .touchUpInside)
setTitle("RESUME", for: .normal)
setTitleColor(.green, for: .normal)
setTitle("PAUSE", for: .selected)
setTitleColor(.white, for: .selected)
titleLabel?.font = UIFont(name: "Print Char 21", size: 11)
}
}
protocol DebugMemoryCellDelegate: class {
func updateSelection(to address: Int)
func isAddressSelected(_ address: Int) -> Bool
@ -310,6 +353,10 @@ class DebugMemoryCell: UITableViewCell {
var actionControllerAnimatorProgress = 0.0
var animator: UIViewPropertyAnimator?
private var displayLink: CADisplayLink?
private var framesSince = 0
private var framesSinceUpdateScreen = 0
let titleLabel: UILabel = {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
@ -329,6 +376,20 @@ class DebugMemoryCell: UITableViewCell {
return button
}()
let testCodeButton: UIButton = {
let button = UIButton(type: .custom)
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("[CODE]", for: .normal)
button.addTarget(self, action: #selector(testCodeButtonTapped(_:)), for: .touchUpInside)
button.titleLabel?.font = UIFont(name: "Print Char 21", size: 12)
button.setTitleColor(.red, for: .normal)
return button
}()
@objc func testCodeButtonTapped(_ sender: UIButton) {
let controller = DebugMemoryCodeViewController(memoryModel: self.memoryModel)
present(controller, animated: true, completion: nil)
}
let tableView: UITableView = {
let view = UITableView(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
@ -337,6 +398,12 @@ class DebugMemoryCell: UITableViewCell {
return view
}()
let resumePauseEmulationButton: DebugPauseResumeButton = {
let button = DebugPauseResumeButton()
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
init() {
super.init(nibName: nil, bundle: nil)
}
@ -349,6 +416,7 @@ class DebugMemoryCell: UITableViewCell {
view.addSubview(titleLabel)
view.addSubview(tableView)
view.addSubview(dismissButton)
view.addSubview(resumePauseEmulationButton)
titleLabel.centerXAnchor.constraint(equalTo: tableView.centerXAnchor).isActive = true
titleLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 16).isActive = true
tableView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 8).isActive = true
@ -357,11 +425,18 @@ class DebugMemoryCell: UITableViewCell {
// tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
dismissButton.trailingAnchor.constraint(equalTo: tableView.trailingAnchor, constant: -8).isActive = true
dismissButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8.0).isActive = true
resumePauseEmulationButton.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 8).isActive = true
resumePauseEmulationButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 8).isActive = true
view.backgroundColor = .black
view.addSubview(testCodeButton)
testCodeButton.trailingAnchor.constraint(equalTo: dismissButton.trailingAnchor).isActive = true
testCodeButton.topAnchor.constraint(equalTo: dismissButton.bottomAnchor, constant: 4).isActive = true
}
func setupTableView() {
tableView.dataSource = self
tableView.delegate = self
tableView.register(DebugMemoryCell.self, forCellReuseIdentifier: DebugMemoryCell.identifier)
}
@ -416,17 +491,50 @@ class DebugMemoryCell: UITableViewCell {
}
}
func setupPauseResumeButton() {
resumePauseEmulationButton.onTapped = { selected in
if selected {
EmuWrapper.resume()
self.displayLink = CADisplayLink(target: self, selector: #selector(self.updateMemoryIfNeeded))
self.displayLink?.isPaused = false
self.displayLink?.add(to: RunLoop.main, forMode: .common)
} else {
EmuWrapper.pause()
self.displayLink?.isPaused = true
self.displayLink = nil
}
}
}
@objc func updateMemoryIfNeeded() {
// maybe apply cheats here?
if framesSinceUpdateScreen > 3 {
delegate?.updateEmulatorScreen()
framesSinceUpdateScreen = 1
}
if framesSince > 30 {
memoryModel.refresh()
tableView.reloadData()
delegate?.refreshActionController()
framesSince = 1
}
framesSince += 1
framesSinceUpdateScreen += 1
}
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupActionController()
setupTableView()
setupPauseResumeButton()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
memoryModel.refresh()
setupActionControllerOrientationConstraints()
resumePauseEmulationButton.isSelected = false
}
override func viewDidAppear(_ animated: Bool) {
@ -459,6 +567,8 @@ class DebugMemoryCell: UITableViewCell {
}
@objc func closeTapped(_ sender: UIButton) {
displayLink?.isPaused = true
displayLink = nil
dismiss(animated: true) {
EmuWrapper.resume()
}
@ -505,6 +615,26 @@ extension DebugMemoryViewController: UITableViewDataSource {
}
}
extension DebugMemoryViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let title = self.tableView(tableView, titleForHeaderInSection: section)
let label = UILabel(frame: .zero)
label.text = title
label.font = UIFont(name: "Print Char 21", size: 12)
label.textColor = .cyan
label.translatesAutoresizingMaskIntoConstraints = false
let view = UIView(frame: .zero)
view.addSubview(label)
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8).isActive = true
view.backgroundColor = .black
view.layer.borderColor = UIColor.cyan.cgColor
view.layer.borderWidth = 1.0
view.heightAnchor.constraint(equalToConstant: 30).isActive = true
return view
}
}
extension DebugMemoryViewController: DebugMemoryCellDelegate {
func updateSelection(to address: Int) {
memoryModel.selectedAddress = address
@ -524,6 +654,7 @@ extension DebugMemoryViewController: DebugMemoryCellDelegate {
protocol DebugMemoryViewControllerDelegate: class {
func refreshActionController()
func updateEmulatorScreen()
}
protocol DebugMemoryActionViewControllerDelegate: class {
@ -560,7 +691,7 @@ extension DebugMemoryViewController: DebugMemoryActionViewControllerDelegate {
class DebugMemoryActionViewController: UIViewController {
enum Mode {
case jumpToAddress, changeMemory, cheat
case jumpToAddress, changeMemory, cheat, screen
}
var mode: Mode = .jumpToAddress
@ -569,7 +700,7 @@ class DebugMemoryActionViewController: UIViewController {
weak var delegate:DebugMemoryActionViewControllerDelegate?
let segmentedControl: UISegmentedControl = {
let control = UISegmentedControl(items: ["Jump", "Edit", "Cheat"])
let control = UISegmentedControl(items: ["Jump", "Edit", "Cheat", "Screen"])
control.translatesAutoresizingMaskIntoConstraints = false
control.tintColor = .orange
control.selectedSegmentIndex = 0
@ -665,6 +796,7 @@ class DebugMemoryActionViewController: UIViewController {
label.text = "Memory Tools"
label.translatesAutoresizingMaskIntoConstraints = false
label.textColor = .orange
label.heightAnchor.constraint(equalToConstant: 20).isActive = true
return label
}()
@ -673,7 +805,7 @@ class DebugMemoryActionViewController: UIViewController {
button.titleLabel?.font = UIFont(name: "Print Char 21", size: 12)
button.setTitle("New Search", for: .normal)
button.layer.borderWidth = 1
button.layer.borderColor = UIColor.red.cgColor
button.layer.borderColor = UIColor.purple.cgColor
button.addTarget(self, action: #selector(cheatFinderNewSearchButtonPressed(_:)), for: .touchUpInside)
return button
}()
@ -765,6 +897,12 @@ class DebugMemoryActionViewController: UIViewController {
return view
}()
lazy var emulatorScreenView: UIImageView = {
let screenView = UIImageView(frame: .zero)
screenView.translatesAutoresizingMaskIntoConstraints = false
return screenView
}()
@objc func cheatFinderNewSearchButtonPressed(_ sender: UIButton) {
if let memory = delegate?.memory {
cheatFinder.update(with: memory)
@ -858,6 +996,8 @@ class DebugMemoryActionViewController: UIViewController {
view.addSubview(segmentedControl)
view.addSubview(editFieldsStackView)
view.addSubview(emulatorScreenView)
// view.addSubview(memoryField)
@ -875,6 +1015,13 @@ class DebugMemoryActionViewController: UIViewController {
// keyboardView.widthAnchor.constraint(equalToConstant: 200).isActive = true
keyboardView.heightAnchor.constraint(equalToConstant: 200).isActive = true
keyboardView.viewModel.delegate = self
emulatorScreenView.topAnchor.constraint(equalTo: segmentedControl.bottomAnchor, constant: 8).isActive = true
emulatorScreenView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 4.0).isActive = true
emulatorScreenView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -4.0).isActive = true
emulatorScreenView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: -16).isActive = true
emulatorScreenView.isHidden = true
view.backgroundColor = .black
}
@ -922,6 +1069,8 @@ class DebugMemoryActionViewController: UIViewController {
mode = .changeMemory
case 2:
mode = .cheat
case 3:
mode = .screen
default:
mode = .jumpToAddress
}
@ -930,10 +1079,12 @@ class DebugMemoryActionViewController: UIViewController {
func update() {
cheatFinderHide()
emulatorScreenView.isHidden = true
switch mode {
case .jumpToAddress:
memoryField.isHidden = false
memoryField.layer.borderColor = UIColor.cyan.cgColor
memoryField.textColor = .cyan
editFieldsStackView.isHidden = false
updateMemoryButton.isHidden = true
resetMemoryButton.isHidden = true
@ -957,6 +1108,12 @@ class DebugMemoryActionViewController: UIViewController {
editFieldsStackView.isHidden = true
keyboardView.isHidden = true
cheatFinderUpdateUI()
case .screen:
memoryField.isHidden = true
editFieldsStackView.isHidden = true
keyboardView.isHidden = true
emulatorScreenView.isHidden = false
updateEmulatorScreen()
}
}
@ -990,6 +1147,13 @@ extension DebugMemoryActionViewController: DebugMemoryViewControllerDelegate {
func refreshActionController() {
update()
}
func updateEmulatorScreen() {
let emulatorView = EmuWrapper.getEmulatorView()
if let snapshot = emulatorView?.getSnapshot() {
let flipped = UIImage(cgImage: snapshot.cgImage!, scale: 1.0, orientation: .downMirrored)
emulatorScreenView.image = flipped
}
}
}
extension DebugMemoryActionViewController: UITableViewDataSource {

View File

@ -18,5 +18,6 @@
+(unsigned char*) memory;
+(void)pause;
+(void)resume;
+(UIView*)getEmulatorView;
@end

View File

@ -6,11 +6,13 @@
//
#import <Foundation/Foundation.h>
#import "activegsAppDelegate.h"
#import "EmuWrapper.h"
#include "../Common.osx/cemulatorctrlmac.h"
#include "../kegs/Src/defc.h"
#include "../kegs/Src/sim65816.h"
#include "../../Common.osx/cemulatorctrlmac.h"
#include "../../kegs/Src/defc.h"
#include "../../kegs/Src/sim65816.h"
@implementation EmuWrapper
@ -26,4 +28,8 @@
r_sim65816.resume();
}
+(UIView*)getEmulatorView {
return [pManager getEmulatorView].zv.ew;
}
@end

View File

@ -491,7 +491,7 @@ struct KeyPosition {
AppleIIKey(label: "SHIFT", code: AppleKeyboardKey.KEY_SHIFT.rawValue,
keySize: .standard, isModifier: true, imageName: "shift", imageNameHighlighted: "shift.fill"),
AppleIIKey(label: "ABC", code: AppleKeyboardKey.KEY_SPECIAL_TOGGLE.rawValue, keySize: .standard, imageName: "textformat.abc"),
AppleIIKey(label: "\(UnicodeScalar(0xe080)!)", code: AppleKeyboardKey.KEY_OPTION.rawValue,
AppleIIKey(label: "\(UnicodeScalar(0xe081)!)", code: AppleKeyboardKey.KEY_APPLE.rawValue,
keySize: .standard, isModifier: true),
AppleIIKey(label: "SPACE", code: AppleKeyboardKey.KEY_SPACE.rawValue, keySize: .wide)
]
@ -528,6 +528,8 @@ struct KeyPosition {
AppleIIKey(label: "DELETE", code: AppleKeyboardKey.KEY_DELETE.rawValue, imageName: "delete.left", imageNameHighlighted: "delete.left.fill")
],
[
AppleIIKey(label: "\(UnicodeScalar(0xe080)!)", code: AppleKeyboardKey.KEY_OPTION.rawValue,
keySize: .standard, isModifier: true),
AppleIIKey(label: "RETURN", code: AppleKeyboardKey.KEY_RETURN.rawValue, keySize: .wide)
]
],
@ -543,15 +545,15 @@ struct KeyPosition {
[
SpacerKey(),
SpacerKey(),
AppleIIKey(label: "", code: AppleKeyboardKey.KEY_UP_CURSOR.rawValue),
AppleIIKey(label: "\(UnicodeScalar(0xe08b)!)", code: AppleKeyboardKey.KEY_UP_CURSOR.rawValue),
SpacerKey(),
SpacerKey()
],
[
SpacerKey(),
AppleIIKey(label: "", code: AppleKeyboardKey.KEY_LEFT_CURSOR.rawValue),
AppleIIKey(label: "", code: AppleKeyboardKey.KEY_DOWN_CURSOR.rawValue),
AppleIIKey(label: "", code: AppleKeyboardKey.KEY_RIGHT_CURSOR.rawValue),
AppleIIKey(label: "\(UnicodeScalar(0xe088)!)", code: AppleKeyboardKey.KEY_LEFT_CURSOR.rawValue),
AppleIIKey(label: "\(UnicodeScalar(0xe08a)!)", code: AppleKeyboardKey.KEY_DOWN_CURSOR.rawValue),
AppleIIKey(label: "\(UnicodeScalar(0xe095)!)", code: AppleKeyboardKey.KEY_RIGHT_CURSOR.rawValue),
SpacerKey()
],
[
@ -561,7 +563,9 @@ struct KeyPosition {
AppleIIKey(label: "DELETE", code: AppleKeyboardKey.KEY_DELETE.rawValue, imageName: "delete.left", imageNameHighlighted: "delete.left.fill")
],
[
AppleIIKey(label: "RETURN", code: AppleKeyboardKey.KEY_RETURN.rawValue, keySize: .standard)
AppleIIKey(label: "\(UnicodeScalar(0xe080)!)", code: AppleKeyboardKey.KEY_OPTION.rawValue,
keySize: .standard, isModifier: true),
AppleIIKey(label: "RETURN", code: AppleKeyboardKey.KEY_RETURN.rawValue, keySize: .wide)
]
]
)

View File

@ -87,7 +87,12 @@ void x_invalidrect()
CGContextRef g = UIGraphicsGetCurrentContext();
x_async_refresh(g,rect);
// // yoshi test...
// CGImageRef imgRef = CGBitmapContextCreateImage(g);
// UIImage *image = [UIImage imageWithCGImage:imgRef];
// CGImageRelease(imgRef);
// NSLog(@"yoshi buffer image = %@",image);
}