@tmustier/pi-nes 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +89 -1
- package/README.md +78 -49
- package/extensions/nes/config.ts +1 -18
- package/extensions/nes/index.ts +1 -21
- package/extensions/nes/native/nes-core/Cargo.lock +0 -2
- package/extensions/nes/native/nes-core/Cargo.toml +1 -1
- package/extensions/nes/native/nes-core/index.d.ts +5 -0
- package/extensions/nes/native/nes-core/index.node +0 -0
- package/extensions/nes/native/nes-core/native.d.ts +5 -0
- package/extensions/nes/native/nes-core/src/lib.rs +25 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/.cargo-ok +1 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/.cargo_vcs_info.json +5 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/.travis.yml +3 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/Cargo.toml +23 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/LICENSE +21 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/README.md +59 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/apu.rs +1114 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/audio.rs +6 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/button.rs +23 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/cpu.rs +2364 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_audio.rs +49 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_display.rs +47 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/default_input.rs +33 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/display.rs +10 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/input.rs +7 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/joypad.rs +86 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/lib.rs +168 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/mapper.rs +502 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/memory.rs +73 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/ppu.rs +1378 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/register.rs +560 -0
- package/extensions/nes/native/nes-core/vendor/nes_rust/src/rom.rs +231 -0
- package/extensions/nes/nes-component.ts +3 -8
- package/extensions/nes/nes-core.ts +29 -9
- package/extensions/nes/paths.ts +40 -0
- package/extensions/nes/renderer.ts +53 -88
- package/package.json +1 -1
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
use audio::{Audio, BUFFER_CAPACITY};
|
|
2
|
+
|
|
3
|
+
pub struct DefaultAudio {
|
|
4
|
+
buffer_index: usize,
|
|
5
|
+
buffer: [f32; BUFFER_CAPACITY],
|
|
6
|
+
previous_value: f32
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
impl DefaultAudio {
|
|
10
|
+
pub fn new() -> Self {
|
|
11
|
+
DefaultAudio {
|
|
12
|
+
buffer_index: 0,
|
|
13
|
+
buffer: [0.0; BUFFER_CAPACITY],
|
|
14
|
+
previous_value: 0.0
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl Audio for DefaultAudio {
|
|
20
|
+
fn push(&mut self, value: f32) {
|
|
21
|
+
if self.buffer_index >= BUFFER_CAPACITY {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
self.buffer[self.buffer_index] = value;
|
|
25
|
+
self.buffer_index += 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
fn copy_sample_buffer(&mut self, sample_buffer: &mut [f32]) {
|
|
29
|
+
// @TODO: Remove side effect?
|
|
30
|
+
|
|
31
|
+
// @TODO: Remove magic number
|
|
32
|
+
let client_sample_buffer_length = 4096;
|
|
33
|
+
|
|
34
|
+
for i in 0..client_sample_buffer_length {
|
|
35
|
+
sample_buffer[i] = match i >= self.buffer_index {
|
|
36
|
+
true => self.previous_value,
|
|
37
|
+
false => self.buffer[i]
|
|
38
|
+
};
|
|
39
|
+
self.previous_value = sample_buffer[i];
|
|
40
|
+
}
|
|
41
|
+
for i in client_sample_buffer_length..self.buffer.len() {
|
|
42
|
+
self.buffer[i - client_sample_buffer_length] = self.buffer[i];
|
|
43
|
+
}
|
|
44
|
+
self.buffer_index = match self.buffer_index < client_sample_buffer_length {
|
|
45
|
+
true => 0,
|
|
46
|
+
false => self.buffer_index - client_sample_buffer_length
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
use display::{
|
|
2
|
+
Display,
|
|
3
|
+
PIXEL_BYTES,
|
|
4
|
+
PIXELS_CAPACITY,
|
|
5
|
+
SCREEN_HEIGHT,
|
|
6
|
+
SCREEN_WIDTH
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
pub struct DefaultDisplay {
|
|
10
|
+
pixels: Vec<u8>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl DefaultDisplay {
|
|
14
|
+
pub fn new() -> Self {
|
|
15
|
+
DefaultDisplay {
|
|
16
|
+
pixels: vec![0; PIXELS_CAPACITY]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
impl Display for DefaultDisplay {
|
|
22
|
+
fn render_pixel(&mut self, x: u16, y: u16, c: u32) {
|
|
23
|
+
let r = ((c >> 16) & 0xff) as u8;
|
|
24
|
+
let g = ((c >> 8) & 0xff) as u8;
|
|
25
|
+
let b = (c & 0xff) as u8;
|
|
26
|
+
let base_index = (y as u32 * SCREEN_WIDTH + x as u32) * PIXEL_BYTES;
|
|
27
|
+
// Is this memory layout, BGR, correct?
|
|
28
|
+
self.pixels[(base_index + 2) as usize] = r;
|
|
29
|
+
self.pixels[(base_index + 1) as usize] = g;
|
|
30
|
+
self.pixels[(base_index + 0) as usize] = b;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fn vblank(&mut self) {
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
fn copy_to_rgba_pixels(&self, pixels: &mut [u8]) {
|
|
37
|
+
for y in 0..SCREEN_HEIGHT {
|
|
38
|
+
for x in 0..SCREEN_WIDTH {
|
|
39
|
+
let base_index = (y * SCREEN_WIDTH + x) as usize;
|
|
40
|
+
pixels[base_index * 4 + 0] = self.pixels[base_index * 3 + 0];
|
|
41
|
+
pixels[base_index * 4 + 1] = self.pixels[base_index * 3 + 1];
|
|
42
|
+
pixels[base_index * 4 + 2] = self.pixels[base_index * 3 + 2];
|
|
43
|
+
pixels[base_index * 4 + 3] = 255;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
use std::collections::VecDeque;
|
|
2
|
+
|
|
3
|
+
use input::Input;
|
|
4
|
+
use button;
|
|
5
|
+
|
|
6
|
+
pub struct DefaultInput {
|
|
7
|
+
events: VecDeque<(button::Button, button::Event)>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
impl DefaultInput {
|
|
11
|
+
pub fn new() -> Self {
|
|
12
|
+
DefaultInput {
|
|
13
|
+
events: VecDeque::<(button::Button, button::Event)>::new()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
impl Input for DefaultInput {
|
|
19
|
+
fn get_input(&mut self) -> Option<(button::Button, button::Event)> {
|
|
20
|
+
match self.events.len() > 0 {
|
|
21
|
+
true => self.events.pop_front(),
|
|
22
|
+
false => None
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fn press(&mut self, button: button::Button) {
|
|
27
|
+
self.events.push_back((button, button::Event::Press));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fn release(&mut self, button: button::Button) {
|
|
31
|
+
self.events.push_back((button, button::Event::Release));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pub const SCREEN_WIDTH: u32 = 256;
|
|
2
|
+
pub const SCREEN_HEIGHT: u32 = 240;
|
|
3
|
+
pub const PIXEL_BYTES: u32 = 3;
|
|
4
|
+
pub const PIXELS_CAPACITY: usize = SCREEN_WIDTH as usize * SCREEN_HEIGHT as usize * PIXEL_BYTES as usize;
|
|
5
|
+
|
|
6
|
+
pub trait Display {
|
|
7
|
+
fn render_pixel(&mut self, x: u16, y: u16, c: u32);
|
|
8
|
+
fn vblank(&mut self);
|
|
9
|
+
fn copy_to_rgba_pixels(&self, pixels: &mut [u8]);
|
|
10
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
use button;
|
|
2
|
+
use register::Register;
|
|
3
|
+
|
|
4
|
+
const BUTTON_NUM: u8 = 8;
|
|
5
|
+
|
|
6
|
+
pub enum Button {
|
|
7
|
+
A,
|
|
8
|
+
B,
|
|
9
|
+
Select,
|
|
10
|
+
Start,
|
|
11
|
+
Up,
|
|
12
|
+
Down,
|
|
13
|
+
Left,
|
|
14
|
+
Right
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn button_index(button: Button) -> usize {
|
|
18
|
+
match button {
|
|
19
|
+
Button::A => 0,
|
|
20
|
+
Button::B => 1,
|
|
21
|
+
Button::Select => 2,
|
|
22
|
+
Button::Start => 3,
|
|
23
|
+
Button::Up => 4,
|
|
24
|
+
Button::Down => 5,
|
|
25
|
+
Button::Left => 6,
|
|
26
|
+
Button::Right => 7
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
pub struct Joypad {
|
|
31
|
+
register: Register<u8>,
|
|
32
|
+
latch: u8,
|
|
33
|
+
current_button: u8,
|
|
34
|
+
buttons: [bool; BUTTON_NUM as usize]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl Joypad {
|
|
38
|
+
pub fn new() -> Self {
|
|
39
|
+
Joypad {
|
|
40
|
+
register: Register::<u8>::new(),
|
|
41
|
+
latch: 0,
|
|
42
|
+
current_button: 0,
|
|
43
|
+
buttons: [false; BUTTON_NUM as usize]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
pub fn handle_input(&mut self, button: Button, event: button::Event) {
|
|
48
|
+
match event {
|
|
49
|
+
button::Event::Press => self.press_button(button),
|
|
50
|
+
button::Event::Release => self.release_button(button)
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
pub fn load_register(&mut self) -> u8 {
|
|
55
|
+
let button = match self.latch == 1 {
|
|
56
|
+
true => 1,
|
|
57
|
+
_ => {
|
|
58
|
+
let value = self.current_button;
|
|
59
|
+
self.current_button += 1;
|
|
60
|
+
value
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
match button >= BUTTON_NUM || self.buttons[button as usize] {
|
|
65
|
+
true => 1,
|
|
66
|
+
false => 0
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn store_register(&mut self, mut value: u8) {
|
|
71
|
+
self.register.store(value);
|
|
72
|
+
value = value & 1;
|
|
73
|
+
if value == 1 {
|
|
74
|
+
self.current_button = 0;
|
|
75
|
+
}
|
|
76
|
+
self.latch = value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
pub fn press_button(&mut self, button: Button) {
|
|
80
|
+
self.buttons[button_index(button)] = true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
pub fn release_button(&mut self, button: Button) {
|
|
84
|
+
self.buttons[button_index(button)] = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
pub mod register;
|
|
2
|
+
pub mod cpu;
|
|
3
|
+
pub mod ppu;
|
|
4
|
+
pub mod apu;
|
|
5
|
+
pub mod rom;
|
|
6
|
+
pub mod memory;
|
|
7
|
+
pub mod mapper;
|
|
8
|
+
pub mod button;
|
|
9
|
+
pub mod joypad;
|
|
10
|
+
pub mod input;
|
|
11
|
+
pub mod audio;
|
|
12
|
+
pub mod display;
|
|
13
|
+
pub mod default_input;
|
|
14
|
+
pub mod default_audio;
|
|
15
|
+
pub mod default_display;
|
|
16
|
+
|
|
17
|
+
use cpu::Cpu;
|
|
18
|
+
use rom::Rom;
|
|
19
|
+
use button::Button;
|
|
20
|
+
use input::Input;
|
|
21
|
+
use display::Display;
|
|
22
|
+
use audio::Audio;
|
|
23
|
+
|
|
24
|
+
/// NES emulator.
|
|
25
|
+
///
|
|
26
|
+
/// ```ignore
|
|
27
|
+
/// use std::fs::File;
|
|
28
|
+
/// use std::io::Read;
|
|
29
|
+
/// use std::time::Duration;
|
|
30
|
+
/// use nes_rust::Nes;
|
|
31
|
+
/// use nes_rust::rom::Rom;
|
|
32
|
+
/// use nes_rust::default_input::DefaultInput;
|
|
33
|
+
/// use nes_rust::default_audio::DefaultAudio;
|
|
34
|
+
/// use nes_rust::default_display::DefaultDisplay;
|
|
35
|
+
///
|
|
36
|
+
/// let input = Box::new(DefaultInput::new());
|
|
37
|
+
/// let display = Box::new(DefaultDisplay::new());
|
|
38
|
+
/// let audio = Box::new(DefaultAudio::new());
|
|
39
|
+
/// let mut nes = Nes::new(input, display, audio);
|
|
40
|
+
///
|
|
41
|
+
/// // Load and set Rom from rom image binary
|
|
42
|
+
/// let filename = &args[1];
|
|
43
|
+
/// let mut file = File::open(filename)?;
|
|
44
|
+
/// let mut contents = vec![];
|
|
45
|
+
/// file.read_to_end(&mut contents)?;
|
|
46
|
+
/// let rom = Rom::new(contents);
|
|
47
|
+
/// nes.set_rom(rom);
|
|
48
|
+
///
|
|
49
|
+
/// // Go!
|
|
50
|
+
/// nes.bootup();
|
|
51
|
+
/// let mut rgba_pixels = [256 * 240 * 4];
|
|
52
|
+
/// loop {
|
|
53
|
+
/// nes.step_frame();
|
|
54
|
+
/// nes.copy_pixels(rgba_pixels);
|
|
55
|
+
/// // Render rgba_pixels
|
|
56
|
+
/// // @TODO: Audio buffer sample code is T.B.D.
|
|
57
|
+
/// // Adjust sleep time for your platform
|
|
58
|
+
/// std::thread::sleep(Duration::from_millis(1));
|
|
59
|
+
/// }
|
|
60
|
+
/// ```
|
|
61
|
+
pub struct Nes {
|
|
62
|
+
cpu: Cpu
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
impl Nes {
|
|
66
|
+
/// Creates a new `Nes`.
|
|
67
|
+
/// You need to pass [`input::Input`](./input/trait.Input.html),
|
|
68
|
+
/// [`display::Display`](./display/trait.Display.html), and
|
|
69
|
+
/// [`audio::Audio`](./audio/trait.Audio.html) traits for your platform
|
|
70
|
+
/// specific Input/Output.
|
|
71
|
+
///
|
|
72
|
+
/// # Arguments
|
|
73
|
+
/// * `input` For pad input
|
|
74
|
+
/// * `display` For screen output
|
|
75
|
+
/// * `audio` For audio output
|
|
76
|
+
pub fn new(input: Box<dyn Input>, display: Box<dyn Display>,
|
|
77
|
+
audio: Box<dyn Audio>) -> Self {
|
|
78
|
+
Nes {
|
|
79
|
+
cpu: Cpu::new(
|
|
80
|
+
input,
|
|
81
|
+
display,
|
|
82
|
+
audio
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Sets up NES rom
|
|
88
|
+
///
|
|
89
|
+
/// # Arguments
|
|
90
|
+
/// * `rom`
|
|
91
|
+
pub fn set_rom(&mut self, rom: Rom) {
|
|
92
|
+
self.cpu.set_rom(rom);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// Boots up
|
|
96
|
+
pub fn bootup(&mut self) {
|
|
97
|
+
self.cpu.bootup();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Resets
|
|
101
|
+
pub fn reset(&mut self) {
|
|
102
|
+
self.cpu.reset();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/// Executes a CPU cycle
|
|
106
|
+
pub fn step(&mut self) {
|
|
107
|
+
self.cpu.step();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Executes a PPU (screen refresh) frame
|
|
111
|
+
pub fn step_frame(&mut self) {
|
|
112
|
+
self.cpu.step_frame();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// Copies RGB pixels of screen to passed pixels.
|
|
116
|
+
/// The length and result should be specific to `display` passed via the constructor.
|
|
117
|
+
///
|
|
118
|
+
/// # Arguments
|
|
119
|
+
/// * `pixels`
|
|
120
|
+
pub fn copy_pixels(&self, pixels: &mut [u8]) {
|
|
121
|
+
self.cpu.get_ppu().get_display().copy_to_rgba_pixels(pixels);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/// Copies audio buffer to passed buffer.
|
|
125
|
+
/// The length and result should be specific to `audio` passed via the constructor.
|
|
126
|
+
///
|
|
127
|
+
/// # Arguments
|
|
128
|
+
/// * `buffer`
|
|
129
|
+
pub fn copy_sample_buffer(&mut self, buffer: &mut [f32]) {
|
|
130
|
+
self.cpu.get_mut_apu().get_mut_audio().copy_sample_buffer(buffer);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Presses a pad button
|
|
134
|
+
///
|
|
135
|
+
/// # Arguments
|
|
136
|
+
/// * `button`
|
|
137
|
+
pub fn press_button(&mut self, button: Button) {
|
|
138
|
+
self.cpu.get_mut_input().press(button);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Releases a pad button
|
|
142
|
+
///
|
|
143
|
+
/// # Arguments
|
|
144
|
+
/// * `buffer`
|
|
145
|
+
pub fn release_button(&mut self, button: Button) {
|
|
146
|
+
self.cpu.get_mut_input().release(button);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
pub fn has_battery_backed_ram(&self) -> bool {
|
|
150
|
+
self.cpu.has_battery_backed_ram()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
pub fn get_sram(&self) -> Vec<u8> {
|
|
154
|
+
self.cpu.get_sram()
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
pub fn set_sram(&mut self, data: Vec<u8>) {
|
|
158
|
+
self.cpu.set_sram(&data);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pub fn is_sram_dirty(&self) -> bool {
|
|
162
|
+
self.cpu.is_sram_dirty()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
pub fn mark_sram_saved(&mut self) {
|
|
166
|
+
self.cpu.mark_sram_saved();
|
|
167
|
+
}
|
|
168
|
+
}
|