diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index b67c782..43a4c94 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,4 +1,4 @@
-name: Rust Tests
+name: Cargo Tests, Nestest
on:
push:
diff --git a/README.md b/README.md
index def3be5..65f8ac9 100644
--- a/README.md
+++ b/README.md
@@ -1,14 +1,22 @@
-# NES Emulator
+
+
+# SelectNES
-**A Nintendo Entertainment System (NES) emulator built in Rust.**
+[](https://github.com/Polar-404/SelectNes/actions/workflows/test.yml)
+[](https://opensource.org/licenses/MIT)
+[](https://www.rust-lang.org/)
- 
+**A Nintendo Entertainment System (NES) emulator built in Rust.**
+
----
## Showcase
+
-
+| Super Mario Bros. | Kirby's Adventure |
+| :---: | :---: |
+|

|

|
+
> [!NOTE]
> This project was created purely as a hobby, a personal challenge, and an opportunity to practice low-level programming and system architecture in Rust. So for the foreseeable future it is not intended to compete with established emulators but rather serves as a proof of concept and a deep learning experience regarding low-level programming and the NES hardware.
@@ -22,10 +30,10 @@ Make sure you have [Rust and Cargo](https://www.rust-lang.org/tools/install) ins
```bash
# Clone the repository
-git clone https://github.com/Polar-404/NES_Emulator.git
+git clone https://github.com/Polar-404/SelectNES.git
# Navigate to the directory
-cd NES_Emulator
+cd SelectNES
# Compile and run
cargo run --release
@@ -33,23 +41,16 @@ cargo run --release
## Controls
-| NES Button | Primary | Secondary |
-| :-------------- | :-----: | :-----------: |
-| **D-Pad Up** | `W` | `Up Arrow` |
-| **D-Pad Down** | `S` | `Down Arrow` |
-| **D-Pad Left** | `A` | `Left Arrow` |
-| **D-Pad Right** | `D` | `Right Arrow` |
-| **A** | `J` | `Z` |
-| **B** | `K` | `X` |
-| **Select** | `N` | `C` |
-| **Start** | `M` | `V` |
-
-**System Commands:**
-* **Volume Up:** `+`
-* **Volume Down:** `-`
-* **Pause/Menu:** `Esc`
-* **Change Color Palette:** `.` (Period)
-* **Paste ROM Path:** `Ctrl + V`
+| NES Button | Primary |
+| :-------------- | :-----------: |
+| **D-Pad Up** | `Up Arrow` |
+| **D-Pad Down** | `Down Arrow` |
+| **D-Pad Left** | `Left Arrow` |
+| **D-Pad Right** | `Right Arrow` |
+| **A** | `Z` |
+| **B** | `X` |
+| **Select** | `C` |
+| **Start** | `V` |
---
## Current Features
@@ -67,38 +68,45 @@ cargo run --release
- NROM (Mapper 0)
- MMC1 (Mapper 1)
-
+
+ - MMC3 (Mapper 4)
+
+- Debug Tools: (Pattern Table viewer, Palette viewer e Hex Memory viewer)
+
---
## Tech Stack
-- **[Rust](https://www.rust-lang.org/):** Main language used for the project.
-
-- **[Macroquad](https://macroquad.rs/):** Core library for graphics rendering and input handling.
-
-- **[Cpal](https://github.com/RustAudio/cpal):** Library for audio processing and output.
-
-- **[Ringbuf](https://crates.io/crates/ringbuf):** Lock-free circular buffer for audio.
-
-- **[Arboard](https://crates.io/crates/arboard):** System clipboard access.
-
-- **Others:** `image`, `lazy_static`, `sysinfo`.
-
+- **[Rust](https://www.rust-lang.org/):** Main language used for the project, ensuring memory safety and high performance.
+
+- **Graphics & UI:**
+ - **[Glow](https://github.com/grovesNL/glow):** "GL on Whatever" — used for cross-platform OpenGL bindings.
+ - **[Egui](https://github.com/emilk/egui):** Immediate mode GUI library used for the debugging tools (PPU, Memory, and CPU viewers).
+ - **OpenGL:** Low-level rendering for the NES screen and implementation of custom shaders.
+
+- **[Cpal](https://github.com/RustAudio/cpal):** Low-level library for audio processing and output.
+
+- **[Ringbuf](https://crates.io/crates/ringbuf):** Lock-free circular buffer for efficient audio synchronization.
+- **[Arboard](https://crates.io/crates/arboard):** Native system clipboard access for easy ROM path pasting.
+
+- **Others:** `image`, `rfd`, `serde`.
+
+
---
## Roadmap / To-Do
As this is an ongoing learning project, several areas still need improvement:
-- [ ] **User Interface:** Improve the start menu and add more graphics and audio configuration options.
+- [x] **User Interface:** Improve the start menu and add more graphics and audio configuration options.
- [ ] **Audio (APU):** Implement the DMC (Delta Modulation Channel).
- [ ] **Synchronization:** Sync audio with FPS to maintain a more stable frame rate, faithful to the original console.
-- [ ] **Mappers:** Add support for more mappers (e.g., MMC3) to increase game compatibility.
+- [ ] **Mappers:** Add support for more mappers to increase game compatibility.
-- [ ] **Saves:** Implement Save/Load states functionality.
+- [x] **Saves:** Implement Save/Load states functionality. (only in-game saves so far, saving the emulator state isn't implemented yet)
- [ ] **Code Quality:** Refactor and clean up the codebase, and potentially add documentation and internationalization (EN/PT-BR).
@@ -108,15 +116,16 @@ As this is an ongoing learning project, several areas still need improvement:
- [ ] **Scripting:** Implement user script support with Lua.
-- [ ] **More Palettes:** Implement the ability for the user to insert their own palettes via interface and/or a designated folder with `.pal` files (maybe even `.hex` files).
+- [x] **More Palettes:** Implement the ability for the user to insert their own palettes via interface and/or a designated folder with `.pal` files (maybe even `.hex` files).
-- [ ] **Custom Graphics Pipeline:** Transition from Macroquad to OpenGL/Glow for
+- [x] **Custom Graphics Pipeline:** Transition from Macroquad to OpenGL/Glow for
- lower input latency
- better frame synchronization.
- Custom CRT/NTSC shaders.
- [ ] **WebAssembly (WASM):** Browser-based emulation — play directly without installing anything.
+
---
## Acknowledgments & References
@@ -128,6 +137,7 @@ This project would not have been possible without the incredible emulation commu
- **[bugzmanov/nes_ebook](https://github.com/bugzmanov/nes_ebook):** The e-book "Writing NES Emulator in Rust" was a fundamental reference. Parts of the CPU implementation and Design Patterns were heavily based on his code to understand Rust's nuances applied to emulation.
+
---
## Licenses
diff --git a/src/memory/bus.rs b/src/memory/bus.rs
index deaa8af..a3fc101 100644
--- a/src/memory/bus.rs
+++ b/src/memory/bus.rs
@@ -1,7 +1,6 @@
use std::path::Path;
use crate::engine::console::{self, LogType};
-use crate::memory::mappers::InesMapper163;
use crate::memory::{mappers, mapper_base::*};
use crate::{
@@ -237,6 +236,8 @@ where T: Mapper + 'static {
)
}
+const NES_SIGNATURE: [u8; 4] = [0x4E, 0x45, 0x53, 0x1A];
+
/// Loads an iNES ROM file and returns the appropriate "mapper" for the cartridge.
///
/// The "Mappers" in this codebase are a customized 'struct/data format' with all the data of the cartridge on it
@@ -286,9 +287,18 @@ pub fn load_rom_from_file(path: &Path) -> Result>, Box> 4);
+ let header = &rom_data[0..16];
+
+ let mapper_match = (header[7] & 0xF0) | (header[6] >> 4);
- let has_trainer = (rom_data[6] & 0b0000_0100) != 0;
+ let has_trainer = (header[6] & 0b0000_0100) != 0;
+
+ if header[0..4] != NES_SIGNATURE {
+ console::print_logs(
+ LogType::Warning,
+ format!("THE ROM HEADER DOESN'T HAVE A NES SIGNATURE, THIS ROM MIGHT BE INVALID")
+ );
+ }
console::print_logs(LogType::Info, format!("--- ROM HEADER INFO ---"));
console::print_logs(LogType::Info, format!("Byte 4 (PRG Banks): {}", rom_data[4]));
@@ -328,8 +338,9 @@ pub fn load_rom_from_file(path: &Path) -> Result>, Box Ok(wrap_in_pointers(mappers::InesMapper000::new(prg_rom_data, chr_rom_data, mirroring_type))),
1 => Ok(wrap_in_pointers(mappers::InesMapper001::new(prg_rom_data, chr_rom_data, GameSave::new(path)))),
+ 2 => Ok(wrap_in_pointers(mappers::InesMapper002::new(prg_rom_data, mirroring_type, GameSave::new(path)))),
4 => Ok(wrap_in_pointers(mappers::InesMapper004::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))),
- 163 => Ok(wrap_in_pointers(InesMapper163::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))),
+ 163 => Ok(wrap_in_pointers(mappers::InesMapper163::new(prg_rom_data, chr_rom_data, mirroring_type, GameSave::new(path)))),
_ => Err(format!("Mapper {} is not supported yet", mapper_match).into())
}
diff --git a/src/memory/mappers/ines_mapper002.rs b/src/memory/mappers/ines_mapper002.rs
new file mode 100644
index 0000000..5b10c6b
--- /dev/null
+++ b/src/memory/mappers/ines_mapper002.rs
@@ -0,0 +1,93 @@
+use crate::memory::{
+ game_save::GameSave,
+ mapper_base::{Mapper, Mirroring}
+};
+
+
+///https://www.nesdev.org/wiki/UxROM
+pub struct InesMapper002 {
+ game_save: GameSave,
+
+ prg_rom: Box<[u8]>,
+
+ ///**InesMapper002 / UxROM** doesn't have a chr_rom, instead, it uses a chr_ram(usually 8kb)
+ ///that starts empty and the game will write the tiles there before it tries to render the screen
+ chr_ram: Box<[u8]>,
+
+ mirroring: Mirroring,
+
+ /// Bank select ($8000-$FFFF)
+ /// ```text
+ /// 7 bit 0
+ /// ---- ----
+ /// xxxx pPPP
+ /// ||||
+ /// ++++- Select 16 KB PRG ROM bank for CPU $8000-$BFFF
+ /// (UNROM uses bits 2-0; UOROM uses bits 3-0)
+ /// ```
+ /// Emulator implementations of iNES mapper 2 treat this as a full 8-bit bank select register, without bus conflicts. This allows the mapper to be used for similar boards that are compatible.
+ ///
+ /// To make use of all 8-bits for a 4 MB PRG ROM, an NES 2.0 header must be used (iNES can only effectively go to 2 MB).
+ ///
+ /// The original UxROM boards used by Nintendo were subject to bus conflicts,
+ /// and the relevant games all work around this in software. Some emulators (notably FCEUX) will have bus conflicts by default,
+ /// but others have none. NES 2.0 submappers were assigned to accurately specify whether the game should be emulated with bus conflicts.
+ bank_select: u8,
+}
+impl InesMapper002 {
+ pub fn new(prg_rom: Box<[u8]>, mirroring: Mirroring, game_save: GameSave) -> Self {
+
+ Self {
+ game_save,
+
+ prg_rom,
+ chr_ram: vec![0; 8192].into_boxed_slice(),
+
+ mirroring,
+
+ bank_select: 0,
+ }
+ }
+}
+
+impl Mapper for InesMapper002 {
+
+ fn read(&self, addr: u16) -> u8 {
+ match addr {
+ 0x6000..=0x7FFF => {
+ self.game_save.read(addr)
+ }
+ 0x8000..=0xBFFF => {
+ let addr = (self.bank_select as usize * 0x4000) + (addr as usize - 0x8000);
+ self.prg_rom[addr]
+ }
+ 0xC000..=0xFFFF => {
+ let addr = (self.prg_rom.len() - 0x4000) + (addr as usize - 0xC000);
+ self.prg_rom[addr]
+ }
+ _ => 0
+ }
+
+ }
+ fn write(&mut self, addr: u16, val: u8) {
+ match addr {
+ 0x6000..=0x7FFF => {
+ self.game_save.write(addr, val);
+ }
+ 0x8000..=0xFFFF => {
+ self.bank_select = val;
+ }
+ _ => {}
+ }
+
+ }
+ fn read_chr(&self, addr: u16) -> u8 {
+ self.chr_ram[addr as usize]
+ }
+ fn write_chr(&mut self, addr: u16, val: u8) {
+ self.chr_ram[addr as usize] = val;
+ }
+ fn mirroring(&self) -> Mirroring {
+ self.mirroring
+ }
+}
diff --git a/src/memory/mappers/mod.rs b/src/memory/mappers/mod.rs
index eab76d5..1dffe4f 100644
--- a/src/memory/mappers/mod.rs
+++ b/src/memory/mappers/mod.rs
@@ -1,5 +1,6 @@
pub mod ines_mapper000;
pub mod ines_mapper001;
+pub mod ines_mapper002;
pub mod ines_mapper004;
pub mod ines_mapper163;
@@ -7,5 +8,6 @@ pub mod dummy_mapper;
pub use self::ines_mapper000::InesMapper000;
pub use self::ines_mapper001::InesMapper001;
+pub use self::ines_mapper002::InesMapper002;
pub use self::ines_mapper004::InesMapper004;
pub use self::ines_mapper163::InesMapper163;
\ No newline at end of file