brokem implementation on main.rs - no regression
This commit is contained in:
+13
-7
@@ -1,4 +1,4 @@
|
||||
use crate::cartridge::Cartridge;
|
||||
use crate::{cartridge::Cartridge, ppu::Ppu};
|
||||
|
||||
pub trait Bus {
|
||||
fn read(&mut self, address: u16) -> u8;
|
||||
@@ -8,32 +8,38 @@ pub trait Bus {
|
||||
pub struct NesBus {
|
||||
ram: [u8; 0x800], // 2KB fixed array, mirrors to fill $0000 to $1FFF
|
||||
cart: Cartridge,
|
||||
ppu: Ppu,
|
||||
openbus: u8, // last byte on the CPU data bus
|
||||
}
|
||||
|
||||
impl NesBus {
|
||||
pub fn new(cart: Cartridge) -> Self {
|
||||
Self { ram: [0; 0x800], cart }
|
||||
Self { ram: [0; 0x800], cart, ppu: Ppu::new(), openbus: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Bus for NesBus {
|
||||
fn read(&mut self, address: u16) -> u8 {
|
||||
match address {
|
||||
let result = match address {
|
||||
0x0000..=0x1FFF => self.ram[(address & 0x07FF) as usize], // Zero-Page my beloved
|
||||
0x2000..=0x3FFF => 0, //PPU registers stub, temporal
|
||||
0x4000..=0x401F => 0, //APU controller stub, temporal
|
||||
0x4020..=0x5FFF => 0, //Expansion ROM stub. temporal
|
||||
0x2000..=0x3FFF => self.ppu.read(&mut self.cart, address, self.openbus),
|
||||
0x4000..=0x401F => self.openbus, // APU regs: unmapped reads return bus contents
|
||||
0x4020..=0x5FFF => self.openbus, // expansion: open bus
|
||||
0x6000..=0x7FFF => self.cart.read_prg_ram(address), // SRAM reads from the cartridge
|
||||
0x8000..=0xFFFF => self.cart.read_prg(address), // this is te space PRG Rom gets "loaded" to in real hardware
|
||||
}
|
||||
};
|
||||
self.openbus = result;
|
||||
result
|
||||
}
|
||||
|
||||
fn write(&mut self, address: u16, value: u8) {
|
||||
match address {
|
||||
0x0000..=0x1FFF => self.ram[(address & 0x07FF) as usize] = value,
|
||||
0x2000..=0x3FFF => self.ppu.write(&mut self.cart, address, value),
|
||||
0x6000..=0x7FFF => self.cart.write_prg_ram(address, value),
|
||||
0x8000..=0xFFFF => self.cart.write_prg(address, value),
|
||||
_ => {} // Everything else dropped for now
|
||||
}
|
||||
self.openbus = value;
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -33,10 +33,12 @@ impl Cartridge {
|
||||
let flags7: u8 = data[7];
|
||||
|
||||
// Decode bits from the bytes on the ROM
|
||||
let mirroring: Mirroring = if flags6 & 0b0001 != 0 { Mirroring::Vertical} else { Mirroring::Horizontal };
|
||||
let battery: bool = flags6 & 0b0010 != 0;
|
||||
let trainer: bool = flags6 & 0b0100 != 0;
|
||||
let four_screen: bool = flags6 & 0b1000 != 0; // this is meant to override mirroring but both will be stored
|
||||
let mirroring = if four_screen { Mirroring::FourScreen }
|
||||
else if flags6 & 0b0001 != 0 { Mirroring::Vertical }
|
||||
else { Mirroring::Horizontal };
|
||||
let mapper: u8 = ((flags7 & 0xF0) >> 4) << 4 | ((flags6 & 0xF0) >> 4);
|
||||
|
||||
// Gate for Mapper: NROM (0) and MMC1 (1) supported for now
|
||||
|
||||
+22
-4
@@ -12,11 +12,29 @@ use crate::cpu::Cpu;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
match args.len() {
|
||||
1 => run_nestest(),
|
||||
3 if args[1] == "blargg" => run_blargg(&args[2]),
|
||||
2 => run_cart(&args[1]),
|
||||
_ => eprintln!("usage: rnes [<rom> | blargg <rom>]"),
|
||||
}
|
||||
}
|
||||
|
||||
if args.len() > 1 {
|
||||
run_blargg(&args[1]);
|
||||
} else {
|
||||
run_nestest();
|
||||
fn run_cart(path: &str) {
|
||||
let cart = Cartridge::from_file(path).unwrap();
|
||||
let mut bus = NesBus::new(cart);
|
||||
let mut cpu = Cpu::new();
|
||||
cpu.reset(&mut bus); // boot from $FFFC-$FFFD
|
||||
let mut renderer = PpmRenderer::new();
|
||||
|
||||
for _ in 0..frames { // a few frames to settle the title screen
|
||||
while !bus.ppu.frame_done() {
|
||||
let cycles = cpu.step(&mut bus);
|
||||
bus.ppu.tick(&mut bus.cart, cycles as u64); // 3 PPU dots per CPU cycle
|
||||
}
|
||||
if bus.ppu.take_nmi() { cpu.set_nmi(); } // loop bridges NMI
|
||||
renderer.present(bus.ppu.framebuffer(), 256, 240);
|
||||
bus.ppu.begin_frame();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
use crate::cartridge::Cartridge;
|
||||
use crate::mapper::Mirroring;
|
||||
|
||||
pub struct Ppu {
|
||||
ctrl: u8, // $2000
|
||||
mask: u8, // $2001
|
||||
status: u8, // $2002
|
||||
oamaddr: u8, // $2003
|
||||
oam: [u8; 256], // $2004
|
||||
scroll_x: u8, // $2005
|
||||
scroll_y: u8,
|
||||
write_latch: bool, // shared W toggle for $2005/$2006, reset by $2002 read
|
||||
vram_addr: u16, // 14-bit
|
||||
data_buffer: u8, // $2007 read-buffer
|
||||
vram: [u8; 0x1000], // 4KB nametables (FourScreen support)
|
||||
palette: [u8; 32],
|
||||
}
|
||||
|
||||
impl Ppu {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
ctrl: 0, mask: 0, status: 0, oamaddr: 0,
|
||||
oam: [0; 256], scroll_x: 0, scroll_y: 0,
|
||||
write_latch: false, vram_addr: 0, data_buffer: 0,
|
||||
vram: [0; 0x1000], palette: [0; 32],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read(&mut self, cart: &mut Cartridge, addr: u16, open_bus: u8) -> u8 {
|
||||
match addr & 0x2007 {
|
||||
0x2000 => open_bus, // write-only: returns bus contents
|
||||
0x2001 => open_bus, // write-only
|
||||
0x2002 => {
|
||||
let s = (self.status & 0xE0) | (open_bus & 0x1F);
|
||||
self.status &= !0x80; // reading clears the vblank flag
|
||||
self.write_latch = false;
|
||||
s
|
||||
}
|
||||
0x2003 => open_bus, // write-only
|
||||
0x2004 => self.oam[self.oamaddr as usize],
|
||||
0x2005 => open_bus, // write-only
|
||||
0x2006 => open_bus, // write-only
|
||||
0x2007 => {
|
||||
let data = self.vram_read(cart, self.vram_addr);
|
||||
let result = if self.vram_addr >= 0x3F00 { data } else { self.data_buffer };
|
||||
self.data_buffer = data;
|
||||
self.vram_addr = self.increment_addr();
|
||||
result
|
||||
}
|
||||
_ => open_bus,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&mut self, cart: &mut Cartridge, addr: u16, val: u8) {
|
||||
match addr & 0x2007 {
|
||||
0x2000 => self.ctrl = val,
|
||||
0x2001 => self.mask = val,
|
||||
0x2002 => {} // write ignored
|
||||
0x2003 => self.oamaddr = val,
|
||||
0x2004 => {
|
||||
self.oam[self.oamaddr as usize] = val;
|
||||
self.oamaddr = self.oamaddr.wrapping_add(1);
|
||||
}
|
||||
0x2005 => {
|
||||
if !self.write_latch { self.scroll_x = val; } else { self.scroll_y = val; }
|
||||
self.write_latch = !self.write_latch;
|
||||
}
|
||||
0x2006 => {
|
||||
if !self.write_latch {
|
||||
self.vram_addr = (self.vram_addr & 0x00FF) | (((val & 0x3F) as u16) << 8);
|
||||
} else {
|
||||
self.vram_addr = (self.vram_addr & 0x7F00) | (val as u16);
|
||||
}
|
||||
self.write_latch = !self.write_latch;
|
||||
}
|
||||
0x2007 => {
|
||||
self.vram_write(cart, self.vram_addr, val);
|
||||
self.vram_addr = self.increment_addr();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn increment_addr(&self) -> u16 {
|
||||
let step = if self.ctrl & 0x04 != 0 { 32 } else { 1 };
|
||||
self.vram_addr.wrapping_add(step) & 0x3FFF
|
||||
}
|
||||
|
||||
fn vram_read(&mut self, cart: &mut Cartridge, addr: u16) -> u8 {
|
||||
match addr {
|
||||
0x0000..=0x1FFF => cart.read_chr(addr), // pattern tables -> CHR
|
||||
0x2000..=0x3EFF => self.nametable_read(cart, addr),
|
||||
0x3F00..=0x3FFF => self.palette_read(addr),
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn vram_write(&mut self, cart: &mut Cartridge, addr: u16, val: u8) {
|
||||
match addr {
|
||||
0x0000..=0x1FFF => cart.write_chr(addr, val), // CHR-RAM writes through
|
||||
0x2000..=0x3EFF => {
|
||||
let off = self.nametable_offset(cart, addr);
|
||||
self.vram[off] = val;
|
||||
}
|
||||
0x3F00..=0x3FFF => {
|
||||
let i = palette_index(addr);
|
||||
self.palette[i] = val;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn nametable_offset(&self, cart: &Cartridge, addr: u16) -> usize {
|
||||
let a = addr & 0x2FFF; // fold the $3000-$3EFF mirror into $2000-$2FFF
|
||||
let table = match cart.mirroring() {
|
||||
Mirroring::Horizontal => (a >> 10) & 1,
|
||||
Mirroring::Vertical => (a >> 11) & 1,
|
||||
Mirroring::OneScreenLow => 0,
|
||||
Mirroring::OneScreenHigh => 1,
|
||||
Mirroring::FourScreen => (a >> 10) & 3,
|
||||
};
|
||||
table as usize * 0x400 + (a & 0x3FF) as usize
|
||||
}
|
||||
|
||||
fn nametable_read(&self, cart: &Cartridge, addr: u16) -> u8 {
|
||||
self.vram[self.nametable_offset(cart, addr)]
|
||||
}
|
||||
|
||||
fn palette_read(&self, addr: u16) -> u8 {
|
||||
self.palette[palette_index(addr)]
|
||||
}
|
||||
}
|
||||
|
||||
fn palette_index(addr: u16) -> usize {
|
||||
let mut i = (addr & 0x1F) as usize; // $3F20-$3FFF folds into $3F00-$3F1F
|
||||
if i >= 0x10 && i % 4 == 0 { i -= 0x10; } // 10/14/18/1C mirror to 00/04/08/0C
|
||||
i
|
||||
}
|
||||
+7
-8
@@ -5,22 +5,21 @@ pub trait Renderer {
|
||||
}
|
||||
|
||||
pub struct PpmRenderer {
|
||||
dir: String,
|
||||
frame: u32,
|
||||
}
|
||||
|
||||
|
||||
impl PpmRenderer {
|
||||
pub fn new(dir: &str) -> Self {
|
||||
Self { dir: dir.to_string(), frame: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl PpmRenderer {
|
||||
pub fn new() -> Self {
|
||||
Self { frame: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Renderer for PpmRenderer {
|
||||
fn present(&mut self, framebuffer: &[u8], width: u16, height: u16) {
|
||||
std::fs::create_dir_all(&self.dir).expect("failed to create render dir");
|
||||
let path = format!("{}/frame_{:04}.ppm", self.dir, self.frame);
|
||||
std::fs::create_dir_all("renders").expect("failed to create renders dir");
|
||||
let path = format!("renders/frame_{:04}.ppm", self.frame);
|
||||
std::fs::write(&path, ppm_bytes(framebuffer, width, height))
|
||||
.expect("failed to write PPM file");
|
||||
self.frame += 1;
|
||||
|
||||
Reference in New Issue
Block a user