mirror of
https://github.com/Candygoblen123/SwiftNES.git
synced 2025-09-12 13:22:03 -05:00
Compare commits
5 Commits
6974110b22
...
main
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9f12bb2ba0 | ||
![]() |
af3be8d93d | ||
![]() |
df8be993f7 | ||
![]() |
59e7559d77 | ||
![]() |
bd21449256 |
@@ -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,19 +13,20 @@ 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) {
|
||||
self.cycles += Int(cycles)
|
||||
|
||||
let nmiBefore = ppu.nmiInterrupt != nil
|
||||
//print(nmiBefore)
|
||||
self.ppu.tick(cycles * 3)
|
||||
_ = self.ppu.tick(cycles * 3)
|
||||
let nmiAfter = ppu.nmiInterrupt != nil
|
||||
//print(nmiAfter)
|
||||
if !nmiBefore && nmiAfter {
|
||||
gameloopCallback(ppu)
|
||||
}
|
||||
@@ -55,7 +57,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:
|
||||
@@ -96,7 +98,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:
|
||||
|
44
Sources/Joypad.swift
Normal file
44
Sources/Joypad.swift
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -28,6 +28,9 @@ class NesPPU {
|
||||
func tick(_ cycles: UInt8) -> Bool {
|
||||
self.cycles += Int(cycles)
|
||||
if self.cycles >= 341 {
|
||||
if checkSprite0Hit(self.cycles) {
|
||||
status.setSpriteZeroHit(true)
|
||||
}
|
||||
self.cycles = self.cycles - 341
|
||||
self.scanline += 1
|
||||
|
||||
@@ -50,6 +53,12 @@ class NesPPU {
|
||||
return false
|
||||
}
|
||||
|
||||
func checkSprite0Hit(_ cycle: Int) -> Bool {
|
||||
let y = Int(oamData[0])
|
||||
let x = Int(oamData[0])
|
||||
return (y == scanline) && x <= cycle && mask.showSprites()
|
||||
}
|
||||
|
||||
func pollNMI() -> UInt8? {
|
||||
let tmp = self.nmiInterrupt
|
||||
self.nmiInterrupt = nil
|
||||
|
@@ -70,4 +70,19 @@ struct ControlRegister: OptionSet {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
func nametableAddr() -> UInt16 {
|
||||
switch rawValue & 0b11 {
|
||||
case 0:
|
||||
0x2000
|
||||
case 1:
|
||||
0x2400
|
||||
case 2:
|
||||
0x2800
|
||||
case 3:
|
||||
0x2c00
|
||||
default:
|
||||
fatalError("naemtableAddr: Not possible!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,36 +1,40 @@
|
||||
class Render {
|
||||
static func render(_ ppu: NesPPU, frame: Frame) {
|
||||
let bank = ppu.ctrl.backgroundPatternAddr()
|
||||
let scroll = (x: Int(ppu.scroll.x), y: Int(ppu.scroll.y))
|
||||
|
||||
for i in 0..<0x03c0 { // FIXME: For now, just use first nametable
|
||||
let tileAddr = UInt16(ppu.vram[i])
|
||||
let tileLoc = (col: i % 32, row: i / 32)
|
||||
let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)]
|
||||
let bgPalette = getBgPalette(ppu, tileLoc: tileLoc)
|
||||
|
||||
// MARK: Draw Background
|
||||
for y in 0...7 {
|
||||
var upper = tile[tile.startIndex + y]
|
||||
var lower = tile[tile.startIndex + y + 8]
|
||||
|
||||
for x in (0...7).reversed() {
|
||||
let value = (1 & lower) << 1 | (1 & upper)
|
||||
upper = upper >> 1
|
||||
lower = lower >> 1
|
||||
let rgb = switch value {
|
||||
case 0:
|
||||
NESColor.SYSTEM_PALLETE[Int(ppu.paletteTable[0])]
|
||||
case 1:
|
||||
NESColor.SYSTEM_PALLETE[Int(bgPalette[1])]
|
||||
case 2:
|
||||
NESColor.SYSTEM_PALLETE[Int(bgPalette[2])]
|
||||
case 3:
|
||||
NESColor.SYSTEM_PALLETE[Int(bgPalette[3])]
|
||||
let (mainNametable, secondNametable) = switch (ppu.mirroring, ppu.ctrl.nametableAddr()) {
|
||||
case (.vertical, 0x2000), (.vertical, 0x2800), (.horizontal, 0x2000), (.horizontal, 0x2400):
|
||||
(ppu.vram[0..<0x400], ppu.vram[0x400..<0x800])
|
||||
case (.vertical, 0x2400), (.vertical, 0x2c00), (.horizontal, 0x2800), (.horizontal, 0x2c00):
|
||||
(ppu.vram[0x400..<0x800], ppu.vram[0..<0x400])
|
||||
default:
|
||||
fatalError("Invalid Pallete Color type")
|
||||
}
|
||||
frame.setPixel((tileLoc.col * 8 + x, tileLoc.row * 8 + y), rgb)
|
||||
fatalError("cringe looking nametable arrangment: \(ppu.mirroring)")
|
||||
}
|
||||
|
||||
renderNameTable(
|
||||
ppu,
|
||||
frame: frame,
|
||||
nameTable: Array(mainNametable),
|
||||
viewPort: Rect(x1: scroll.x, y1: scroll.y, x2: 256, y2: 240),
|
||||
shift: (-scroll.x, -scroll.y)
|
||||
)
|
||||
|
||||
if scroll.x > 0 {
|
||||
renderNameTable(
|
||||
ppu,
|
||||
frame: frame,
|
||||
nameTable: Array(secondNametable),
|
||||
viewPort: Rect(x1: 0, y1: 0, x2: scroll.x, y2: 240),
|
||||
shift: (256 - scroll.x, 0)
|
||||
)
|
||||
} else if scroll.y > 0 {
|
||||
renderNameTable(
|
||||
ppu,
|
||||
frame: frame,
|
||||
nameTable: Array(secondNametable),
|
||||
viewPort: Rect(x1: 0, y1: 0, x2: 256, y2: scroll.y),
|
||||
shift: (0, 240 - scroll.y)
|
||||
)
|
||||
}
|
||||
|
||||
// MARK: Draw Sprites
|
||||
@@ -52,7 +56,7 @@ class Render {
|
||||
var upper = tile[tile.startIndex + y]
|
||||
var lower = tile[tile.startIndex + y + 8]
|
||||
|
||||
for x in (0...7).reversed() {
|
||||
for x in [7,6,5,4,3,2,1,0] {
|
||||
let value = (1 & lower) << 1 | (1 & upper)
|
||||
upper = upper >> 1
|
||||
lower = lower >> 1
|
||||
@@ -83,11 +87,51 @@ class Render {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func renderNameTable(_ ppu: NesPPU, frame: Frame, nameTable: [UInt8], viewPort: Rect, shift: (x: Int, y: Int)) {
|
||||
let bank = ppu.ctrl.backgroundPatternAddr()
|
||||
let attributeTable = nameTable[0x3c0..<0x400]
|
||||
|
||||
for i in 0..<0x3c0 {
|
||||
let tileAddr = UInt16(nameTable[i])
|
||||
let tileLoc = (col: i % 32, row: i / 32)
|
||||
let tile = ppu.chrRom[(bank + Int(tileAddr) * 16)...(bank + Int(tileAddr) * 16 + 15)]
|
||||
let bgPalette = getBgPalette(ppu, tileLoc: tileLoc, attributeTable: Array(attributeTable))
|
||||
|
||||
// MARK: Draw Background
|
||||
for y in 0...7 {
|
||||
var upper = tile[tile.startIndex + y]
|
||||
var lower = tile[tile.startIndex + y + 8]
|
||||
|
||||
for x in [7,6,5,4,3,2,1,0] {
|
||||
let value = (1 & lower) << 1 | (1 & upper)
|
||||
upper = upper >> 1
|
||||
lower = lower >> 1
|
||||
let rgb = switch value {
|
||||
case 0:
|
||||
NESColor.SYSTEM_PALLETE[Int(ppu.paletteTable[0])]
|
||||
case 1:
|
||||
NESColor.SYSTEM_PALLETE[Int(bgPalette[1])]
|
||||
case 2:
|
||||
NESColor.SYSTEM_PALLETE[Int(bgPalette[2])]
|
||||
case 3:
|
||||
NESColor.SYSTEM_PALLETE[Int(bgPalette[3])]
|
||||
default:
|
||||
fatalError("Invalid Pallete Color type")
|
||||
}
|
||||
|
||||
static func getBgPalette(_ ppu: NesPPU, tileLoc: (col: Int, row: Int)) -> [UInt8] {
|
||||
let pixelLoc = (x: tileLoc.col * 8 + x, y: tileLoc.row * 8 + y)
|
||||
if pixelLoc.x >= viewPort.x1 && pixelLoc.x < viewPort.x2 && pixelLoc.y >= viewPort.y1 && pixelLoc.y < viewPort.y2 {
|
||||
frame.setPixel((shift.x + pixelLoc.x, shift.y + pixelLoc.y), rgb)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func getBgPalette(_ ppu: NesPPU, tileLoc: (col: Int, row: Int), attributeTable: [UInt8]) -> [UInt8] {
|
||||
let attrTableIndex = tileLoc.row / 4 * 8 + tileLoc.col / 4
|
||||
let attrByte = ppu.vram[0x3c0 + attrTableIndex] // FIXME: still using hardcoded first nametable
|
||||
let attrByte = attributeTable[attrTableIndex]
|
||||
|
||||
let palleteIndex = switch (tileLoc.col % 4 / 2, tileLoc.row % 4 / 2) {
|
||||
case (0,0):
|
||||
@@ -103,7 +147,12 @@ class Render {
|
||||
}
|
||||
|
||||
let palleteStartIndex = 1 + Int(palleteIndex) * 4
|
||||
return [ppu.paletteTable[0], ppu.paletteTable[palleteStartIndex], ppu.paletteTable[palleteStartIndex + 1], ppu.paletteTable[palleteStartIndex + 2]]
|
||||
return [
|
||||
ppu.paletteTable[0],
|
||||
ppu.paletteTable[palleteStartIndex],
|
||||
ppu.paletteTable[palleteStartIndex + 1],
|
||||
ppu.paletteTable[palleteStartIndex + 2],
|
||||
]
|
||||
}
|
||||
|
||||
static func getSpritePalette(_ ppu: NesPPU, paletteIndex: UInt8) -> [UInt8] {
|
||||
@@ -116,3 +165,10 @@ class Render {
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
struct Rect {
|
||||
let x1: Int
|
||||
let y1: Int
|
||||
let x2: Int
|
||||
let y2: Int
|
||||
}
|
||||
|
@@ -21,88 +21,36 @@ 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
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
guard let bytes = NSData(contentsOfFile: "smb1.nes") else { fatalError("Rom not found") }
|
||||
|
||||
// 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: "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 timer = Date.now.addingTimeInterval(0.01666)
|
||||
var frame = Frame()
|
||||
let bus = Bus(rom) { ppu in
|
||||
let bus = Bus(rom: rom, joypad1: joypad1) { ppu in
|
||||
Render.render(ppu, frame: frame)
|
||||
// wait here until 16.66 ms have passed
|
||||
// i would use clock_nanosleep but that isn't available cross platform
|
||||
if timer.timeIntervalSinceNow > 0 {
|
||||
usleep(UInt32(timer.timeIntervalSinceNow * 1000000))
|
||||
}
|
||||
timer.addTimeInterval(0.01666)
|
||||
SDL_UpdateTexture(texture, nil, frame.data, 256 * 3)
|
||||
SDL_RenderCopy(canvas, texture, nil, nil)
|
||||
SDL_RenderPresent(canvas)
|
||||
@@ -114,75 +62,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()
|
||||
|
Reference in New Issue
Block a user