diff --git a/Sources/Data/Bus.swift b/Sources/Data/Bus.swift index 0e6fbc0..68a7205 100644 --- a/Sources/Data/Bus.swift +++ b/Sources/Data/Bus.swift @@ -1,7 +1,8 @@ class Bus { var cpuVram: [UInt8] = .init(repeating: 0, count: 2048) var prgRom: [UInt8] - var ppu: NesPPU + let ppu: NesPPU + let joypad1: Joypad var cycles: Int = 0 var gameloopCallback: (NesPPU) -> () @@ -12,10 +13,12 @@ class Bus { fileprivate let ROM_ADDRESS_START: UInt16 = 0x8000 fileprivate let ROM_ADDRESS_END: UInt16 = 0xFFFF - init(_ rom: Rom, gameloopCallback: @escaping (NesPPU) -> ()) { + init(rom: Rom, joypad1: Joypad, gameloopCallback: @escaping (NesPPU) -> ()) { ppu = NesPPU(rom.character, rom.screenMirror) self.prgRom = rom.program self.gameloopCallback = gameloopCallback + self.joypad1 = joypad1 + } func tick(_ cycles: UInt8) { @@ -51,7 +54,7 @@ extension Bus: Memory { case 0x4000...0x4015: return 0 // Ignore APU case 0x4016: - return 0 // Ignore Joy 1 + return joypad1.read() case 0x4017: return 0 // Ignore Joy 2 case 0x2008...PPU_REGISTERS_MIRRORS_END: @@ -92,7 +95,7 @@ extension Bus: Memory { case 0x4000...0x4013, 0x4015: return // Ignore APU case 0x4016: - return // ignore Joy 1 + joypad1.write(data) case 0x4017: return // Ignore Joy 2 case 0x4014: diff --git a/Sources/Joypad.swift b/Sources/Joypad.swift new file mode 100644 index 0000000..c11e185 --- /dev/null +++ b/Sources/Joypad.swift @@ -0,0 +1,44 @@ +struct JoypadButton: OptionSet { + var rawValue: UInt8 + + static let RIGHT = JoypadButton(rawValue: 0b10000000) + static let LEFT = JoypadButton(rawValue: 0b01000000) + static let DOWN = JoypadButton(rawValue: 0b00100000) + static let UP = JoypadButton(rawValue: 0b00010000) + static let START = JoypadButton(rawValue: 0b00001000) + static let SELECT = JoypadButton(rawValue: 0b00000100) + static let BUTTON_B = JoypadButton(rawValue: 0b00000010) + static let BUTTON_A = JoypadButton(rawValue: 0b00000001) +} + +class Joypad { + var strobe = false + var buttonIndex: UInt8 = 0 + var buttonStatus = JoypadButton() + + func write(_ data: UInt8) { + strobe = data & 1 == 1 + if strobe { + buttonIndex = 0 + } + } + + func read() -> UInt8 { + if buttonIndex > 7 { + return 1 + } + let response = (buttonStatus.rawValue & (1 << buttonIndex)) >> buttonIndex + if !strobe { + buttonIndex += 1 + } + return response + } + + func setButton(_ button: JoypadButton, pressed: Bool) { + if pressed { + buttonStatus.insert(button) + } else { + buttonStatus.remove(button) + } + } +} diff --git a/Sources/Render/TileViewer.swift b/Sources/TileViewer.swift similarity index 100% rename from Sources/Render/TileViewer.swift rename to Sources/TileViewer.swift diff --git a/Sources/main.swift b/Sources/main.swift index 914f3bc..86fa19b 100644 --- a/Sources/main.swift +++ b/Sources/main.swift @@ -21,87 +21,27 @@ SDL_RenderSetScale(canvas, 3.0, 3.0) var texture = SDL_CreateTexture(canvas, SDL_PIXELFORMAT_RGB24.rawValue, Int32(SDL_TEXTUREACCESS_TARGET.rawValue), 256, 240) var event = SDL_Event() -var quit = false -// func handleUserInput(_ cpu: CPU, event: inout SDL_Event) { -// while SDL_PollEvent(&event) > 0 { -// if event.type == SDL_QUIT.rawValue { -// SDL_DestroyWindow(window) -// SDL_Quit() -// exit(0) -// } -// if event.type == SDL_KEYDOWN.rawValue { -// switch SDL_KeyCode(UInt32(event.key.keysym.sym)) { -// case SDLK_ESCAPE: -// SDL_DestroyWindow(window) -// SDL_Quit() -// exit(0) -// case SDLK_w: -// cpu.memWrite(0xff, data: 0x77) -// case SDLK_a: -// cpu.memWrite(0xff, data: 0x61) -// case SDLK_s: -// cpu.memWrite(0xff, data: 0x73) -// case SDLK_d: -// cpu.memWrite(0xff, data: 0x64) -// default: -// continue -// } -// } -// } -// } - -// func color(_ byte: UInt8) -> SDL_Color { -// switch byte{ -// case 0: -// return SDL_Color(r: 0, g: 0, b: 0, a: 255) -// case 1: -// return SDL_Color(r: 255, g: 255, b: 255, a: 255) -// case 2, 9: -// return SDL_Color(r: 128, g: 128, b: 128, a: 255) -// case 3, 10: -// return SDL_Color(r: 255, g: 0, b: 0, a: 255) -// case 4, 11: -// return SDL_Color(r: 0, g: 255, b: 0, a: 255) -// case 5, 12: -// return SDL_Color(r: 0, g: 0, b: 255, a: 255) -// case 6, 13: -// return SDL_Color(r: 255, g: 0, b: 255, a: 255) -// case 7, 14: -// return SDL_Color(r: 255, g: 255, b: 0, a: 255) -// default: -// return SDL_Color(r: 0, g: 255, b: 255, a: 255) - -// } -// } - -// func readScreenState(_ cpu: CPU, frame: inout [UInt8]) -> Bool { - -// var frame_idx = 0 -// var update = false -// for i in 0x0200..<0x600 { -// let color_idx = cpu.memRead(UInt16(i)) -// let color = color(color_idx) -// let (b1, b2, b3) = (color.r, color.b, color.g) -// if frame[frame_idx] != b1 || frame[frame_idx + 1] != b2 || frame[frame_idx + 2] != b3 { -// frame[frame_idx] = b1; -// frame[frame_idx + 1] = b2; -// frame[frame_idx + 2] = b3; -// update = true; -// } -// frame_idx += 3 -// } -// return update -// } - -guard let bytes = NSData(contentsOfFile: "smb1.nes") else { fatalError("Rom not found") } +guard let bytes = NSData(contentsOfFile: "pacman.nes") else { fatalError("Rom not found") } var gameCode = [UInt8](repeating: 0, count: bytes.length) bytes.getBytes(&gameCode, length: bytes.length) let rom = try Rom(gameCode) +let joypad1 = Joypad() + +let keyMap = [ + SDLK_DOWN : JoypadButton.DOWN, + SDLK_UP : JoypadButton.UP, + SDLK_LEFT : JoypadButton.LEFT, + SDLK_RIGHT : JoypadButton.RIGHT, + SDLK_SPACE : JoypadButton.SELECT, + SDLK_RETURN : JoypadButton.START, + SDLK_a : JoypadButton.BUTTON_A, + SDLK_s : JoypadButton.BUTTON_B +] var frame = Frame() -let bus = Bus(rom) { ppu in +let bus = Bus(rom: rom, joypad1: joypad1) { ppu in Render.render(ppu, frame: frame) SDL_UpdateTexture(texture, nil, frame.data, 256 * 3) SDL_RenderCopy(canvas, texture, nil, nil) @@ -114,75 +54,24 @@ let bus = Bus(rom) { ppu in exit(0) } if event.type == SDL_KEYDOWN.rawValue { - switch SDL_KeyCode(UInt32(event.key.keysym.sym)) { + let keyCode = SDL_KeyCode(UInt32(event.key.keysym.sym)) + switch keyCode { case SDLK_ESCAPE: SDL_DestroyWindow(window) SDL_Quit() exit(0) default: - continue + guard let key = keyMap[keyCode] else { continue } + joypad1.setButton(key, pressed: true) } } + if event.type == SDL_KEYUP.rawValue { + guard let key = keyMap[SDL_KeyCode(UInt32(event.key.keysym.sym))] else { continue } + joypad1.setButton(key, pressed: false) + } } } let cpu = CPU(bus: bus) cpu.reset() cpu.run() - -// let tileFrame = TileViewer.showTile(chrRom: rom.character, bank: 1, tileNum: 0) -// let tileFrame = TileViewer.showTileBank(chrRom: rom.character, bank: 1) - -// SDL_UpdateTexture(texture, nil, tileFrame.data, 256 * 3) -// SDL_RenderCopy(canvas, texture, nil, nil) -// SDL_RenderPresent(canvas) - -// while true { -// while SDL_PollEvent(&event) > 0 { -// if event.type == SDL_QUIT.rawValue { -// SDL_DestroyWindow(window) -// SDL_Quit() -// exit(0) -// } -// if event.type == SDL_KEYDOWN.rawValue { -// switch SDL_KeyCode(UInt32(event.key.keysym.sym)) { -// case SDLK_ESCAPE: -// SDL_DestroyWindow(window) -// SDL_Quit() -// exit(0) -// default: -// continue -// } -// } -// } -// } - -// let bus = Bus(try! Rom(gameCode)) - -//var cpu = CPU(bus: bus) -//cpu.load(gameCode) -//cpu.reset() -//cpu.programCounter = 0xC000 - -// var screenState = [UInt8](repeating: 0, count: 32 * 3 * 32) -// var rng = SystemRandomNumberGenerator() -// cpu.run(onCycle: { -// //print(dumpCpuState(cpu)) -// handleUserInput(cpu, event: &event) -// cpu.memWrite(0xfe, data: UInt8.random(in: 1...16, using: &rng)) - -// if readScreenState(cpu, frame: &screenState) { -// SDL_UpdateTexture(texture, nil, screenState, 32 * 3) -// SDL_RenderCopy(canvas, texture, nil, nil) -// SDL_RenderPresent(canvas) -// } - -// usleep(70) -// }, onComplete: { -// SDL_DestroyWindow(window) -// SDL_Quit() -// exit(0) -// }) - -// Infinite loop otherwise the program will exit prematurely -// RunLoop.main.run()