Major refactor

- CPU is now it's own module
- Memory object is now shared to support mapper chips
- ROM is now stored as `Arc<[u8]>` to support mapper chips
This commit is contained in:
2026-01-24 03:38:42 -06:00
parent b5e1d1a4c3
commit f861f75b21
16 changed files with 3071 additions and 2450 deletions

View File

@@ -1,24 +1,26 @@
use std::{collections::HashMap, fmt, time::Duration};
use iced::{
Color, Element, Font,
Color, Element,
Length::{Fill, Shrink},
Point, Rectangle, Renderer, Size, Subscription, Task, Theme, mouse,
Point, Renderer, Size, Subscription, Task, Theme,
advanced::graphics::compositor::Display,
mouse,
widget::{
Action, Canvas, button,
Canvas,
canvas::{Frame, Program},
column, container, mouse_area, row, text,
column, container, row,
},
window::{self, Id, Settings},
};
use nes_emu::{
NES, PPU,
NES,
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
header_menu::header_menu,
hex_view::{HexEvent, HexView},
resize_watcher::resize_watcher,
};
use tokio::runtime::Runtime;
use tokio::{io::AsyncWriteExt, runtime::Runtime};
use tracing_subscriber::EnvFilter;
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
@@ -26,8 +28,9 @@ use tracing_subscriber::EnvFilter;
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_red.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_name_table.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "int_nmi_exit_timing.nes");
// const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
const ROM_FILE: &str = "./cpu_timing_test.nes";
const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
// const ROM_FILE: &str = "./cpu_timing_test.nes";
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
extern crate nes_emu;
@@ -44,12 +47,18 @@ fn main() -> Result<(), iced::Error> {
.run()
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MemoryTy {
Cpu,
PPU,
}
impl fmt::Display for MemoryTy {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Export {self:?} Memory")
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum WindowType {
Main,
@@ -67,6 +76,7 @@ enum HeaderButton {
// OpenTileViewer,
// OpenDebugger,
Open(WindowType),
OpenRom,
Reset,
PowerCycle,
}
@@ -83,6 +93,7 @@ impl fmt::Display for HeaderButton {
Self::Open(WindowType::Main) => write!(f, "Create new Main window"),
Self::Reset => write!(f, "Reset"),
Self::PowerCycle => write!(f, "Power Cycle"),
Self::OpenRom => write!(f, "Open ROM file"),
}
}
}
@@ -96,6 +107,7 @@ struct Emulator {
#[derive(Debug, Clone)]
enum Message {
OpenRom(NES),
Tick(usize),
Frame,
DMA,
@@ -107,6 +119,7 @@ enum Message {
Hex(Id, HexEvent),
Debugger(DebuggerMessage),
SetSize(window::Id, Size),
Export(MemoryTy),
}
impl Emulator {
@@ -180,6 +193,22 @@ impl Emulator {
Message::Header(HeaderButton::Open(w)) => {
return self.open(w);
}
Message::Header(HeaderButton::OpenRom) => {
return Task::future(
rfd::AsyncFileDialog::new()
.set_directory(".")
.add_filter("NES", &["nes"])
.set_title("Open NES Rom file")
.pick_file(),
)
.and_then(|p| {
Task::future(async move {
// println!("Opening: {}", p.path().display());
NES::async_load_nes_file(p.path()).await.ok()
})
})
.and_then(|n| Task::done(Message::OpenRom(n)));
}
Message::Hex(id, val) => {
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
return view.update(val).map(move |e| Message::Hex(id, e));
@@ -203,6 +232,33 @@ impl Emulator {
})
.then(move |_| iced::window::resize(id, size));
}
Message::OpenRom(nes) => {
self.nes = nes;
self.nes.power_cycle();
}
Message::Export(ty) => {
let raw: Vec<_> = match ty {
MemoryTy::Cpu => (0..=0xFFFF).map(|i| self.nes.mem().peek_cpu(i).unwrap_or(0)).collect(),
MemoryTy::PPU => (0..=0xFFFF).map(|i| self.nes.mem().peek_ppu(i).unwrap_or(0)).collect(),
};
return Task::future(async move {
if let Some(file) = rfd::AsyncFileDialog::new()
.set_directory(".")
.set_file_name("output.dmp")
.set_title("Save memory dump")
.save_file()
.await
{
tokio::fs::File::create(file.path())
.await
.expect("Failed to save file")
.write_all(&raw)
.await
.expect("Failed to write dump");
}
})
.discard();
}
}
// self.image.0.clone_from(self.nes.image());
Task::none()
@@ -234,20 +290,26 @@ impl Emulator {
Some(WindowType::Memory(ty, view)) => {
let hex = match ty {
MemoryTy::Cpu => view
.render(self.nes.cpu_mem())
.render(self.nes.mem(), false)
.map(move |e| Message::Hex(win, e)),
MemoryTy::PPU => view
.render(self.nes.ppu().mem())
.render(self.nes.mem(), true)
.map(move |e| Message::Hex(win, e)),
};
let content = column![hex].width(Fill).height(Fill);
let content = column![row![header_menu("Export", [*ty], Message::Export)], hex]
.width(Fill)
.height(Fill);
container(content).width(Fill).height(Fill).into()
}
Some(WindowType::TileMap) => dbg_image(DbgImage::NameTable(self.nes.ppu())).into(),
Some(WindowType::TileViewer) => {
dbg_image(DbgImage::PatternTable(self.nes.ppu())).into()
Some(WindowType::TileMap) => {
dbg_image(DbgImage::NameTable(self.nes.mem(), self.nes.ppu())).into()
}
Some(WindowType::TileViewer) => {
dbg_image(DbgImage::PatternTable(self.nes.mem(), self.nes.ppu())).into()
}
Some(WindowType::Palette) => {
dbg_image(DbgImage::Palette(self.nes.mem(), self.nes.ppu())).into()
}
Some(WindowType::Palette) => dbg_image(DbgImage::Palette(self.nes.ppu())).into(),
Some(WindowType::Debugger) => {
container(self.debugger.view(&self.nes).map(Message::Debugger))
.width(Fill)
@@ -263,7 +325,11 @@ impl Emulator {
row![
header_menu(
"Console",
[HeaderButton::Reset, HeaderButton::PowerCycle,],
[
HeaderButton::OpenRom,
HeaderButton::Reset,
HeaderButton::PowerCycle,
],
Message::Header
),
header_menu(