working on drag and drop to load games
This commit is contained in:
parent
435a1b41fc
commit
91f0aa479b
|
@ -7,6 +7,8 @@ Nestur is an NES emulator. There are plenty of full-featured emulators out there
|
||||||
|
|
||||||
<img src="pics/smb.png" width=250> <img src="pics/zelda_dungeon.png" width=250> <img src="pics/kirby.png" width=250> <img src="pics/dk.png" width=250> <img src="pics/smb3.png" width=250> <img src="pics/excitebike.png" width=250>
|
<img src="pics/smb.png" width=250> <img src="pics/zelda_dungeon.png" width=250> <img src="pics/kirby.png" width=250> <img src="pics/dk.png" width=250> <img src="pics/smb3.png" width=250> <img src="pics/excitebike.png" width=250>
|
||||||
|
|
||||||
|
The code aims to follow the explanations from the [NES dev wiki](https://wiki.nesdev.com/w/index.php/NES_reference_guide) where possible, especially in the PPU, and the comments quote from it often. Thanks to everyone who contributes to that wiki/forum, and to Michael Fogleman's [NES](https://github.com/fogleman/nes) and Scott Ferguson's [Fergulator](https://github.com/scottferg/Fergulator) for getting me unstuck at several points.
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
```
|
```
|
||||||
Button | Key
|
Button | Key
|
||||||
|
@ -25,8 +27,6 @@ ___________________
|
||||||
## Save states
|
## Save states
|
||||||
To save the state of the game at any time, press F5. To load the most recent save state, press F9. If the game is called `mygame.nes`, the save state files will be called `mygame-X.dat` where `X` is a number. To load any previous save state, drag and drop a `.dat` file onto the window.
|
To save the state of the game at any time, press F5. To load the most recent save state, press F9. If the game is called `mygame.nes`, the save state files will be called `mygame-X.dat` where `X` is a number. To load any previous save state, drag and drop a `.dat` file onto the window.
|
||||||
|
|
||||||
The code aims to follow the explanations from the [NES dev wiki](https://wiki.nesdev.com/w/index.php/NES_reference_guide) where possible, especially in the PPU, and the comments quote from it often. Thanks to everyone who contributes to that wiki/forum, and to Michael Fogleman's [NES](https://github.com/fogleman/nes) and Scott Ferguson's [Fergulator](https://github.com/scottferg/Fergulator) for getting me unstuck at several points.
|
|
||||||
|
|
||||||
## Compilation and use
|
## Compilation and use
|
||||||
|
|
||||||
1. Install [Rust](https://www.rust-lang.org/tools/install)
|
1. Install [Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
|
51
src/main.rs
51
src/main.rs
|
@ -18,10 +18,15 @@ use state::{save_state, load_state, find_next_filename, find_last_filename};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::{Instant, Duration};
|
use std::time::{Instant, Duration};
|
||||||
|
|
||||||
|
use sdl2::Sdl;
|
||||||
|
use sdl2::render::{Canvas, Texture};
|
||||||
use sdl2::keyboard::Keycode;
|
use sdl2::keyboard::Keycode;
|
||||||
use sdl2::EventPump;
|
use sdl2::EventPump;
|
||||||
use sdl2::event::Event;
|
use sdl2::event::Event;
|
||||||
use sdl2::pixels::PixelFormatEnum;
|
use sdl2::pixels::PixelFormatEnum;
|
||||||
|
use sdl2::video::Window;
|
||||||
|
use sdl2::messagebox::*;
|
||||||
|
|
||||||
// use cpuprofiler::PROFILER;
|
// use cpuprofiler::PROFILER;
|
||||||
|
|
||||||
|
@ -37,18 +42,48 @@ fn main() -> Result<(), String> {
|
||||||
let byte_height = 240 * screen::SCALE_FACTOR; // NES image is 240 pixels tall, multiply by scale factor for total number of rows needed
|
let byte_height = 240 * screen::SCALE_FACTOR; // NES image is 240 pixels tall, multiply by scale factor for total number of rows needed
|
||||||
let mut screen_buffer = vec![0; byte_width * byte_height]; // contains raw RGB data for the screen
|
let mut screen_buffer = vec![0; byte_width * byte_height]; // contains raw RGB data for the screen
|
||||||
|
|
||||||
|
let argv = std::env::args().collect::<Vec<String>>();
|
||||||
|
let filename = if argv.len() > 1 {
|
||||||
|
argv[1].to_string()
|
||||||
|
} else {
|
||||||
|
show_simple_message_box(MessageBoxFlag::OK, title: &str, message: &str, window: W)
|
||||||
|
let name;
|
||||||
|
'waiting: loop {
|
||||||
|
for event in event_pump.poll_iter() {
|
||||||
|
match event {
|
||||||
|
Event::DropFile{ filename: f, .. } => {
|
||||||
|
name = f;
|
||||||
|
break 'waiting;
|
||||||
|
},
|
||||||
|
_ => std::thread::sleep(Duration::from_millis(500)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
name
|
||||||
|
};
|
||||||
|
run_game(&sdl_context, &mut event_pump, &mut screen_buffer, &mut canvas, &mut texture, &filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_game(
|
||||||
|
sdl_context: &Sdl,
|
||||||
|
event_pump: &mut EventPump,
|
||||||
|
screen_buffer: &mut Vec<u8>,
|
||||||
|
canvas: &mut Canvas<Window>,
|
||||||
|
texture: &mut Texture,
|
||||||
|
filename: &str
|
||||||
|
) -> Result<(), String> {
|
||||||
|
|
||||||
// Set up audio
|
// Set up audio
|
||||||
let mut temp_buffer = vec![]; // receives one sample each time the APU ticks. this is a staging buffer so we don't have to lock the mutex too much.
|
let mut temp_buffer = vec![]; // receives one sample each time the APU ticks. this is a staging buffer so we don't have to lock the mutex too much.
|
||||||
let apu_buffer = Arc::new(Mutex::new(Vec::<f32>::new())); // stays in this thread, receives raw samples between frames
|
let apu_buffer = Arc::new(Mutex::new(Vec::<f32>::new())); // stays in this thread, receives raw samples between frames
|
||||||
let sdl_buffer = Arc::clone(&apu_buffer); // used in audio device's callback to select the samples it needs
|
let sdl_buffer = Arc::clone(&apu_buffer); // used in audio device's callback to select the samples it needs
|
||||||
let audio_device = audio::initialize(&sdl_context, sdl_buffer).expect("Could not create audio device");
|
let audio_device = audio::initialize(sdl_context, sdl_buffer).expect("Could not create audio device");
|
||||||
let mut half_cycle = false;
|
let mut half_cycle = false;
|
||||||
audio_device.resume();
|
audio_device.resume();
|
||||||
|
|
||||||
// Initialize hardware components
|
// Initialize hardware components
|
||||||
let filename = get_filename();
|
let filepath = Path::new(filename).to_path_buf();
|
||||||
let filepath = Path::new(&filename).to_path_buf();
|
let mapper = get_mapper(filename.to_string());
|
||||||
let mapper = get_mapper(filename.clone());
|
|
||||||
let ppu = Ppu::new(mapper.clone());
|
let ppu = Ppu::new(mapper.clone());
|
||||||
let apu = Apu::new();
|
let apu = Apu::new();
|
||||||
let mut cpu = Cpu::new(mapper.clone(), ppu, apu);
|
let mut cpu = Cpu::new(mapper.clone(), ppu, apu);
|
||||||
|
@ -79,12 +114,12 @@ fn main() -> Result<(), String> {
|
||||||
for _ in 0..cpu_cycles * 3 {
|
for _ in 0..cpu_cycles * 3 {
|
||||||
let (pixel, end_of_frame) = cpu.ppu.clock();
|
let (pixel, end_of_frame) = cpu.ppu.clock();
|
||||||
match pixel {
|
match pixel {
|
||||||
Some((x, y, color)) => draw_pixel(&mut screen_buffer, x, y, color),
|
Some((x, y, color)) => draw_pixel(screen_buffer, x, y, color),
|
||||||
None => (),
|
None => (),
|
||||||
};
|
};
|
||||||
if end_of_frame {
|
if end_of_frame {
|
||||||
fps += 1; // keep track of how many frames we've rendered this second
|
fps += 1; // keep track of how many frames we've rendered this second
|
||||||
draw_to_window(&mut texture, &mut canvas, &screen_buffer)?; // draw the buffer to the window with SDL
|
draw_to_window(texture, canvas, &screen_buffer)?; // draw the buffer to the window with SDL
|
||||||
let mut b = apu_buffer.lock().unwrap(); // unlock mutex to the real buffer
|
let mut b = apu_buffer.lock().unwrap(); // unlock mutex to the real buffer
|
||||||
b.append(&mut temp_buffer); // send this frame's audio data, emptying the temp buffer
|
b.append(&mut temp_buffer); // send this frame's audio data, emptying the temp buffer
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
|
@ -93,7 +128,7 @@ fn main() -> Result<(), String> {
|
||||||
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
|
std::thread::sleep(timer + Duration::from_millis(1000/60) - now);
|
||||||
}
|
}
|
||||||
timer = Instant::now();
|
timer = Instant::now();
|
||||||
if !process_events(&mut event_pump, &filepath, &mut cpu) {
|
if !process_events(event_pump, &filepath, &mut cpu) {
|
||||||
break 'running;
|
break 'running;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,7 +197,7 @@ TODO:
|
||||||
- high- and low-pass audio filters
|
- high- and low-pass audio filters
|
||||||
- DMC audio channel
|
- DMC audio channel
|
||||||
- untangle CPU and APU/PPU?
|
- untangle CPU and APU/PPU?
|
||||||
- GUI? drag and drop ROMs?
|
- GUI: load new game if .nes dropped, instructions on screen if no arg given, error messages if wrong file dropped.
|
||||||
- reset function/button
|
- reset function/button
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue