@tmustier/pi-nes 0.2.4 → 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/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-core.ts +27 -4
- package/package.json +1 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
use memory::Memory;
|
|
2
|
+
use mapper::{Mapper, MapperFactory};
|
|
3
|
+
|
|
4
|
+
pub struct Rom {
|
|
5
|
+
header: RomHeader,
|
|
6
|
+
memory: Memory,
|
|
7
|
+
mapper: Box<dyn Mapper>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
pub static HEADER_SIZE: usize = 16;
|
|
11
|
+
|
|
12
|
+
pub enum Mirrorings {
|
|
13
|
+
SingleScreen,
|
|
14
|
+
Horizontal,
|
|
15
|
+
Vertical,
|
|
16
|
+
FourScreen
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl Rom {
|
|
20
|
+
pub fn new(data: Vec<u8>) -> Self {
|
|
21
|
+
let header = RomHeader::new(data[0..HEADER_SIZE].to_vec());
|
|
22
|
+
let mapper = MapperFactory::create(&header);
|
|
23
|
+
Rom {
|
|
24
|
+
header: header,
|
|
25
|
+
memory: Memory::new(data[HEADER_SIZE..].to_vec()),
|
|
26
|
+
mapper: mapper
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* CPU memory address:
|
|
32
|
+
* 0x0000 - 0x1FFF: Character ROM access
|
|
33
|
+
* 0x8000 - 0xFFFF: Program ROM access
|
|
34
|
+
*
|
|
35
|
+
* To access wide range ROM data with limited CPU memory address space
|
|
36
|
+
* Mapper maps CPU memory address to ROM's.
|
|
37
|
+
* In general writing control registers in Mapper via .store() switches bank.
|
|
38
|
+
*/
|
|
39
|
+
pub fn load(&self, address: u32) -> u8 {
|
|
40
|
+
let mut address_in_rom = 0 as u32;
|
|
41
|
+
if address < 0x2000 {
|
|
42
|
+
// load from character rom
|
|
43
|
+
address_in_rom += self.header.prg_rom_bank_num() as u32 * 0x4000;
|
|
44
|
+
address_in_rom += self.mapper.map_for_chr_rom(address);
|
|
45
|
+
} else {
|
|
46
|
+
address_in_rom += self.mapper.map(address);
|
|
47
|
+
}
|
|
48
|
+
self.memory.load(address_in_rom)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn load_without_mapping(&self, address: u32) -> u8 {
|
|
52
|
+
self.memory.load(address)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* In general writing with ROM address space updates control registers in Mapper.
|
|
57
|
+
*/
|
|
58
|
+
pub fn store(&mut self, address: u32, value: u8) {
|
|
59
|
+
self.mapper.store(address, value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn valid(&self) -> bool {
|
|
63
|
+
self.header.is_nes()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn has_chr_rom(&self) -> bool {
|
|
67
|
+
self.header.has_chr_rom()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn has_battery_backed_ram(&self) -> bool {
|
|
71
|
+
self.header.has_battery_backed_ram()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
pub fn mirroring_type(&self) -> Mirrorings {
|
|
75
|
+
match self.mapper.has_mirroring_type() {
|
|
76
|
+
true => self.mapper.mirroring_type(),
|
|
77
|
+
false => self.header.mirroring_type()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// @TODO: MMC3Mapper specific. Should this method be here?
|
|
82
|
+
pub fn irq_interrupted(&mut self) -> bool {
|
|
83
|
+
self.mapper.drive_irq_counter()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// @TODO: Cache
|
|
88
|
+
pub struct RomHeader {
|
|
89
|
+
data: Vec<u8>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
impl RomHeader {
|
|
93
|
+
fn new(vec: Vec<u8>) -> Self {
|
|
94
|
+
let mut header = RomHeader {
|
|
95
|
+
data: Vec::new()
|
|
96
|
+
};
|
|
97
|
+
for i in 0..HEADER_SIZE {
|
|
98
|
+
header.data.push(vec[i]);
|
|
99
|
+
}
|
|
100
|
+
header
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
fn load(&self, address: u32) -> u8 {
|
|
104
|
+
self.data[address as usize]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
fn is_nes(&self) -> bool {
|
|
108
|
+
if self.signature() == "NES" && self.magic_number() == 0x1a {
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
false
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn signature(&self) -> String {
|
|
115
|
+
let mut vec = Vec::new();
|
|
116
|
+
for i in 0..3 as u32 {
|
|
117
|
+
vec.push(self.load(i));
|
|
118
|
+
}
|
|
119
|
+
String::from_utf8(vec).unwrap()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn magic_number(&self) -> u8 {
|
|
123
|
+
self.load(3)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub fn prg_rom_bank_num(&self) -> u8 {
|
|
127
|
+
self.load(4)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pub fn chr_rom_bank_num(&self) -> u8 {
|
|
131
|
+
self.load(5)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn has_chr_rom(&self) -> bool {
|
|
135
|
+
self.chr_rom_bank_num() > 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn control_byte1(&self) -> u8 {
|
|
139
|
+
self.load(6)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
fn control_byte2(&self) -> u8 {
|
|
143
|
+
self.load(7)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
fn _ram_bank_num(&self) -> u8 {
|
|
147
|
+
self.load(8)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
fn _unused_field(&self) -> u64 {
|
|
151
|
+
let mut value = 0 as u64;
|
|
152
|
+
for i in 0..7 as u32 {
|
|
153
|
+
value = (value << 8) | self.load(9 + i) as u64;
|
|
154
|
+
}
|
|
155
|
+
value
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fn extract_bits(&self, value: u8, offset: u8, size: u8) -> u8 {
|
|
159
|
+
(value >> offset) & ((1 << size) - 1)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
fn mirroring_type(&self) -> Mirrorings {
|
|
163
|
+
match self.four_screen_mirroring() {
|
|
164
|
+
true => Mirrorings::FourScreen,
|
|
165
|
+
false => match self.extract_bits(self.control_byte1(), 0, 1) {
|
|
166
|
+
0 => Mirrorings::Horizontal,
|
|
167
|
+
_ /* 1 */ => Mirrorings::Vertical
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fn _is_horizontal_mirroring(&self) -> bool {
|
|
173
|
+
match self.mirroring_type() {
|
|
174
|
+
Mirrorings::Horizontal => true,
|
|
175
|
+
_ => false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
pub fn has_battery_backed_ram(&self) -> bool {
|
|
180
|
+
self.extract_bits(self.control_byte1(), 1, 1) == 1
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
fn _trainer_512_bytes(&self) -> u8 {
|
|
184
|
+
self.extract_bits(self.control_byte1(), 2, 1)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
fn four_screen_mirroring(&self) -> bool {
|
|
188
|
+
self.extract_bits(self.control_byte1(), 3, 1) == 1
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
pub fn mapper_num(&self) -> u8 {
|
|
192
|
+
let lower_bits = self.extract_bits(self.control_byte1(), 4, 4);
|
|
193
|
+
let higher_bits = self.extract_bits(self.control_byte2(), 4, 4);
|
|
194
|
+
(higher_bits << 4) | lower_bits
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[cfg(test)]
|
|
199
|
+
mod tests_rom {
|
|
200
|
+
use super::*;
|
|
201
|
+
|
|
202
|
+
#[test]
|
|
203
|
+
fn initialize() {
|
|
204
|
+
let r = Rom::new(vec![0; 17]);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[test]
|
|
208
|
+
fn load() {
|
|
209
|
+
let r = Rom::new(vec![0; 17]);
|
|
210
|
+
assert_eq!(0, r.load(0));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[test]
|
|
214
|
+
fn store() {
|
|
215
|
+
let mut r = Rom::new(vec![0; 17]);
|
|
216
|
+
r.store(0, 0);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#[test]
|
|
220
|
+
fn valid() {
|
|
221
|
+
let r = Rom::new(vec![0; 64]);
|
|
222
|
+
assert_eq!(false, r.valid());
|
|
223
|
+
let mut v = vec![0; 64];
|
|
224
|
+
v[0] = 0x4e; // N
|
|
225
|
+
v[1] = 0x45; // E
|
|
226
|
+
v[2] = 0x53; // S
|
|
227
|
+
v[3] = 0x1a; // magic number
|
|
228
|
+
let r2 = Rom::new(v);
|
|
229
|
+
assert_eq!(true, r2.valid());
|
|
230
|
+
}
|
|
231
|
+
}
|
|
@@ -34,6 +34,11 @@ interface NativeNesInstance {
|
|
|
34
34
|
reset(): void;
|
|
35
35
|
pressButton(button: number): void;
|
|
36
36
|
releaseButton(button: number): void;
|
|
37
|
+
hasBatteryBackedRam(): boolean;
|
|
38
|
+
getSram(): Uint8Array;
|
|
39
|
+
setSram(data: Uint8Array): void;
|
|
40
|
+
isSramDirty(): boolean;
|
|
41
|
+
markSramSaved(): void;
|
|
37
42
|
getFramebuffer(): Uint8Array;
|
|
38
43
|
}
|
|
39
44
|
|
|
@@ -73,6 +78,7 @@ class NativeNesCore implements NesCore {
|
|
|
73
78
|
private readonly nes: NativeNesInstance;
|
|
74
79
|
private readonly audioWarning: string | null;
|
|
75
80
|
private readonly frameBuffer: Uint8Array;
|
|
81
|
+
private hasSram = false;
|
|
76
82
|
|
|
77
83
|
constructor(enableAudio: boolean) {
|
|
78
84
|
this.audioWarning = enableAudio
|
|
@@ -88,6 +94,7 @@ class NativeNesCore implements NesCore {
|
|
|
88
94
|
|
|
89
95
|
loadRom(rom: Uint8Array): void {
|
|
90
96
|
this.nes.setRom(rom);
|
|
97
|
+
this.hasSram = this.nes.hasBatteryBackedRam();
|
|
91
98
|
this.nes.bootup();
|
|
92
99
|
}
|
|
93
100
|
|
|
@@ -110,16 +117,32 @@ class NativeNesCore implements NesCore {
|
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
getSram(): Uint8Array | null {
|
|
113
|
-
|
|
120
|
+
if (!this.hasSram) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return this.nes.getSram();
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
setSram(
|
|
126
|
+
setSram(sram: Uint8Array): void {
|
|
127
|
+
if (!this.hasSram) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
this.nes.setSram(sram);
|
|
131
|
+
}
|
|
117
132
|
|
|
118
133
|
isSramDirty(): boolean {
|
|
119
|
-
|
|
134
|
+
if (!this.hasSram) {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
return this.nes.isSramDirty();
|
|
120
138
|
}
|
|
121
139
|
|
|
122
|
-
markSramSaved(): void {
|
|
140
|
+
markSramSaved(): void {
|
|
141
|
+
if (!this.hasSram) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.nes.markSramSaved();
|
|
145
|
+
}
|
|
123
146
|
|
|
124
147
|
getAudioWarning(): string | null {
|
|
125
148
|
return this.audioWarning;
|