Compare commits
	
		
			175 Commits
		
	
	
		
			08bd33b4d4
			...
			main
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 8a352ca807 | |||
| 068e4124a6 | |||
| fedae65cc8 | |||
| 41586d8cc4 | |||
| 79740ac245 | |||
| d6bfde081e | |||
| d2f57ee66b | |||
| 3841d3a98c | |||
| 85940c8744 | |||
| c7adf54976 | |||
| f0f9003247 | |||
| 5eba097ad4 | |||
| 756c806c2a | |||
| 77af5453f0 | |||
| 41bc4f7afe | |||
| 36e46d3780 | |||
| 926c66746d | |||
| cf2357f917 | |||
| 3fb7edd333 | |||
| 3ab512d663 | |||
| 61156ca8a8 | |||
| 2c411d5792 | |||
| 4c657c9832 | |||
| e72b11f946 | |||
| bbcbbd8ce3 | |||
| c4226e6e76 | |||
| 409314a4e5 | |||
| 952bf68bd0 | |||
| 865c11d53c | |||
| 90d2da9272 | |||
| 4a1a21a08f | |||
| 9d23b571fb | |||
| 70e0b5868d | |||
| a0e3c7c602 | |||
| ef4f3d9ec6 | |||
| 7f872a554b | |||
| dda0257655 | |||
| c10816c048 | |||
| 2405fd027f | |||
| 939c25ce1a | |||
| 6f5e863645 | |||
| ce5d58e1d2 | |||
| ebcd5bec5c | |||
| f2c49b398c | |||
| 6f76571d6c | |||
| 55da5a29d8 | |||
| 7dfcd44820 | |||
| 80a15039e0 | |||
| 32405c0734 | |||
| 2ef8fefbb7 | |||
| 548945a4ee | |||
| 1526b4a09d | |||
| 4125ea5c74 | |||
| b9519d9b7a | |||
| 01278ca83f | |||
| 2bb8bd6d8f | |||
| 080c1e7518 | |||
| 765f9d8288 | |||
| 8780c4a59b | |||
| e19a540650 | |||
| 4cfd951ede | |||
| de8fe1a367 | |||
| c5fa41b20d | |||
| 552cfd4a18 | |||
| 4c516804e4 | |||
| 9b3ab73bb1 | |||
| d9f1d661ae | |||
| da83032e24 | |||
| 67e2447d3f | |||
| 37cf3d92e4 | |||
| 293e5762c3 | |||
| 999f661e6b | |||
| e573411890 | |||
| 352a65b705 | |||
| 64230973f1 | |||
| dbbf87af52 | |||
| 1440cd1fc7 | |||
| 9964b49ce1 | |||
| 142231d355 | |||
| 227928e8ca | |||
| 1001b0b124 | |||
| 71ce3f43e0 | |||
| ce121864d2 | |||
| e1fe00ab64 | |||
| 5882678bc5 | |||
| bcd67cb317 | |||
| 4516ca8477 | |||
| 6087e3b20b | |||
| 10ac579c40 | |||
| ee5504111b | |||
| a628f64d28 | |||
| 318a6e0386 | |||
| db012c7f4b | |||
| e42c87aeb7 | |||
| 9113e95fa0 | |||
| 9973dc8714 | |||
| e128025208 | |||
| 44ac0c8ebd | |||
| 01064bab69 | |||
| 634bc2d2c0 | |||
| d794a94b68 | |||
| b87e31d3f4 | |||
| 3c2456611e | |||
| b829f05a34 | |||
| afd2d16371 | |||
| 1f8fa48168 | |||
| c6fbb79189 | |||
| 8b78b2943e | |||
| 0af95a1dd3 | |||
| 9fa40f8584 | |||
| b10bc7b4fd | |||
| 4658a0d106 | |||
| f92b9d61ef | |||
| 360a9a7b65 | |||
| 5d64e539a7 | |||
| 22f96a10e7 | |||
| 8fea8eb1ff | |||
| 9b2c91445a | |||
| b9046bceba | |||
| aa22e93049 | |||
| 6215eccb2f | |||
| a77d0a0f62 | |||
| c2f2e2194b | |||
| d68257bb29 | |||
| e27d6dc25b | |||
| 1acb5de19d | |||
| 1b78b248a3 | |||
| 5d6df46a2d | |||
| 7e65d82fef | |||
| 8c9567b610 | |||
| 53dfaf0de2 | |||
| 16c2dd81fc | |||
| 79be38a1e6 | |||
| 8625bec059 | |||
| 0637b771e3 | |||
| 0107fa04c9 | |||
| 6265c8af04 | |||
| 5482a8e75f | |||
| 002dae6826 | |||
| c863dc835c | |||
| d4407cf849 | |||
| de0d147685 | |||
| dd8339e8de | |||
| 05d6475015 | |||
| 32b597a328 | |||
| 33be2e0e83 | |||
| 832e1b7633 | |||
| a549b9feef | |||
| 4d6fc95130 | |||
| 7112cd15e3 | |||
| 2af673d7c5 | |||
| e43cac8041 | |||
| 8724da824e | |||
| 903cfacad3 | |||
| 9d0e099a97 | |||
| d9a3a7b0dd | |||
| 1a4546f4a4 | |||
| bd685a99a4 | |||
| 31abd6dc5c | |||
| c7e3cb5b35 | |||
| e4d77d66ba | |||
| 9e36e86c14 | |||
| 6f6c308d84 | |||
| f429d72882 | |||
| 4fda7587ce | |||
| 3fb182b9cb | |||
| c1430594cf | |||
| 8e2ef58b81 | |||
| b4b84d0cc5 | |||
| 1d8fb8d32a | |||
| cc0cb0dd8a | |||
| fd9a5b51ba | |||
| b9a798d0ad | |||
| a1eadc0bc4 | |||
| ea53f97312 | 
							
								
								
									
										9
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					    "recommendations": [
 | 
				
			||||||
 | 
					        "rust-lang.rust-analyzer",
 | 
				
			||||||
 | 
					        "tamasfe.even-better-toml",
 | 
				
			||||||
 | 
					        "serayuzgur.crates",
 | 
				
			||||||
 | 
					        "vadimcn.vscode-lldb",
 | 
				
			||||||
 | 
					        "donaldhays.rgbds-z80"
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2937
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2937
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										30
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								Cargo.toml
									
									
									
									
									
								
							@@ -2,23 +2,27 @@
 | 
				
			|||||||
name = "gb"
 | 
					name = "gb"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
authors = ["Rekai Musuka <rekai@musuka.dev>"]
 | 
					authors = ["Rekai Musuka <rekai@musuka.dev>"]
 | 
				
			||||||
edition = "2018"
 | 
					edition = "2021"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
					# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies]
 | 
					[dependencies]
 | 
				
			||||||
anyhow = "^1.0"
 | 
					anyhow = "1.0"
 | 
				
			||||||
bitfield = "^0.13"
 | 
					bitfield = "0.17"
 | 
				
			||||||
clap = "^2.33"
 | 
					clap = { version = "4.0", features = ["cargo"] }
 | 
				
			||||||
gilrs = "^0.8"
 | 
					gilrs = "0.11"
 | 
				
			||||||
pixels = "^0.3"
 | 
					winit = "0.28"
 | 
				
			||||||
winit = "^0.24"
 | 
					egui = "0.21"
 | 
				
			||||||
winit_input_helper = "^0.9"
 | 
					egui_wgpu_backend = "0.22"
 | 
				
			||||||
egui = "^0.10"
 | 
					egui_winit_platform = "0.18"
 | 
				
			||||||
egui_wgpu_backend = { git="https://github.com/hasenbanck/egui_wgpu_backend.git", rev="9d03ad345d15d1e44165849b242d3562fdf3e859" }
 | 
					pollster = "0.3"
 | 
				
			||||||
egui_winit_platform = { git="https://github.com/hasenbanck/egui_winit_platform.git", rev="17298250e9721e8bf2c1d4a17b3e22777f8cb2e8" }
 | 
					rodio = "0.19"
 | 
				
			||||||
rodio = "^0.14"
 | 
					rtrb = "0.3"
 | 
				
			||||||
crossbeam-channel = "^0.5"
 | 
					directories-next = "2.0"
 | 
				
			||||||
 | 
					tracing = "0.1"
 | 
				
			||||||
 | 
					tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
 | 
				
			||||||
 | 
					thiserror = "1.0"
 | 
				
			||||||
 | 
					once_cell = "1.10"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[profile.release]
 | 
					[profile.release]
 | 
				
			||||||
debug = true
 | 
					debug = true
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										47
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								README.md
									
									
									
									
									
								
							@@ -1,14 +1,43 @@
 | 
				
			|||||||
# Rekai's Gameboy Emulator
 | 
					# Rekai's Gameboy Emulator
 | 
				
			||||||
[](https://ci.paoda.moe/paoda/gb)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Status
 | 
					### Status
 | 
				
			||||||
* Passes Blargg's cpu_instrs Test
 | 
					* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
 | 
				
			||||||
* Renders Background & Window Tiles 
 | 
					    * [x] cpu_instrs
 | 
				
			||||||
* Implements a PPU FIFO
 | 
					    * [x] instr_timing
 | 
				
			||||||
 | 
					    * [x] mem_timing
 | 
				
			||||||
 | 
					    * [x] mem_timing-2
 | 
				
			||||||
 | 
					    * [ ] dmg_sound (partial) 
 | 
				
			||||||
 | 
					* [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2)
 | 
				
			||||||
 | 
					* From [mooneye-gb](https://github.com/Gekkio/mooneye-gb):
 | 
				
			||||||
 | 
					    * Cartridges:
 | 
				
			||||||
 | 
					        * [x] MBC1
 | 
				
			||||||
 | 
					        * [ ] MBC1M
 | 
				
			||||||
 | 
					        * [x] MBC2
 | 
				
			||||||
 | 
					        * [x] MBC5
 | 
				
			||||||
 | 
					* Implements a cycle-accurate PPU FIFO
 | 
				
			||||||
 | 
					    * Doesn't \*exactly\* work just yet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Notes
 | 
					### Compiling
 | 
				
			||||||
* [gameboy-logs](https://github.com/wheremyfoodat/Gameboy-logs) suggests that there are still some underlying problems with the cpu implementation
 | 
					This project was last successfully built on [Rust 1.64.0](https://github.com/rust-lang/rust/tree/1.64.0)
 | 
				
			||||||
* The Sprite FIFO does not work as expected yet
 | 
					
 | 
				
			||||||
* Sound is neither emulated nor stubbed. Upon writing / reading to a APU related register the emulator will panic. 
 | 
					1. `git clone https://github.com/paoda/gb`
 | 
				
			||||||
* Code cleanup is pending completion of some minimum viable product of the emulator
 | 
					2. `cd gb`
 | 
				
			||||||
 | 
					3. `cargo run --release`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Controls
 | 
				
			||||||
 | 
					Controls are defined [here](https://github.com/paoda/gb/blob/85940c874460b9cb31bf9b8211bf7dda596d114a/src/joypad.rs#L114)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Key | Button
 | 
				
			||||||
 | 
					--- | ---
 | 
				
			||||||
 | 
					<kbd>X</kbd> | B
 | 
				
			||||||
 | 
					<kbd>Z</kbd> | A
 | 
				
			||||||
 | 
					<kbd>Enter</kbd> | START
 | 
				
			||||||
 | 
					<kbd>Shift</kbd> | SELECT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Then use the Arrow keys for the D-Pad
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Credits
 | 
				
			||||||
 | 
					The Boot ROM found in the `bin/` directory was made by [Optix](https://github.com/Hacktix) over [here](https://github.com/Hacktix/Bootix)
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								bin/bootix_dmg.bin
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								bin/bootix_dmg.bin
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1046
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1046
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										80
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					use rodio::Source;
 | 
				
			||||||
 | 
					use rtrb::{Consumer, Producer, PushError, RingBuffer};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz
 | 
				
			||||||
 | 
					const CHANNEL_COUNT: usize = 2;
 | 
				
			||||||
 | 
					const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init<T>() -> (SampleProducer<T>, SampleConsumer<T>) {
 | 
				
			||||||
 | 
					    let (prod, cons) = RingBuffer::new(BUFFER_CAPACITY);
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        SampleProducer { inner: prod },
 | 
				
			||||||
 | 
					        SampleConsumer { inner: cons },
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct SampleProducer<T> {
 | 
				
			||||||
 | 
					    inner: Producer<T>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> SampleProducer<T> {
 | 
				
			||||||
 | 
					    pub(crate) fn push(&mut self, value: T) -> Result<(), PushError<T>> {
 | 
				
			||||||
 | 
					        self.inner.push(value)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
 | 
					    pub(crate) fn available(&self) -> bool {
 | 
				
			||||||
 | 
					        self.inner.slots() > 2
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn available_blocking(&self) -> bool {
 | 
				
			||||||
 | 
					        loop {
 | 
				
			||||||
 | 
					            if self.inner.slots() > 2 {
 | 
				
			||||||
 | 
					                break true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl<T> std::fmt::Debug for SampleProducer<T> {
 | 
				
			||||||
 | 
					    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
				
			||||||
 | 
					        f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::<T>()))
 | 
				
			||||||
 | 
					            .finish_non_exhaustive()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct SampleConsumer<T> {
 | 
				
			||||||
 | 
					    inner: Consumer<T>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Iterator for SampleConsumer<f32> {
 | 
				
			||||||
 | 
					    type Item = f32;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn next(&mut self) -> Option<Self::Item> {
 | 
				
			||||||
 | 
					        // As of 2021-07-28, PopError can only be Empty
 | 
				
			||||||
 | 
					        Some(self.inner.pop().unwrap_or_default())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Source for SampleConsumer<f32> {
 | 
				
			||||||
 | 
					    fn current_frame_len(&self) -> Option<usize> {
 | 
				
			||||||
 | 
					        // A frame changes when the samples rate or
 | 
				
			||||||
 | 
					        // number of channels change. This will never happen, so
 | 
				
			||||||
 | 
					        // we return
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn channels(&self) -> u16 {
 | 
				
			||||||
 | 
					        // The Gameboy supports two channels
 | 
				
			||||||
 | 
					        CHANNEL_COUNT as u16
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn sample_rate(&self) -> u32 {
 | 
				
			||||||
 | 
					        SAMPLE_RATE
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn total_duration(&self) -> Option<std::time::Duration> {
 | 
				
			||||||
 | 
					        // The duration of this source is infinite
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										582
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										582
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,582 @@
 | 
				
			|||||||
 | 
					use bitfield::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) trait NRx4 {
 | 
				
			||||||
 | 
					    fn trigger(&self) -> bool;
 | 
				
			||||||
 | 
					    fn length_enable(&self) -> bool;
 | 
				
			||||||
 | 
					    fn set_length_enable(&mut self, value: bool);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(crate) mod ch1 {
 | 
				
			||||||
 | 
					    use super::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bitfield! {
 | 
				
			||||||
 | 
					        pub struct Sweep(u8);
 | 
				
			||||||
 | 
					        impl Debug;
 | 
				
			||||||
 | 
					        _period, _: 6, 4;
 | 
				
			||||||
 | 
					        from into SweepDirection, _direction, _: 3, 3;
 | 
				
			||||||
 | 
					        _shift_count, _: 2, 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Sweep {
 | 
				
			||||||
 | 
					        pub(crate) fn period(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._period()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn shift_count(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._shift_count()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn direction(&self) -> SweepDirection {
 | 
				
			||||||
 | 
					            self._direction()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Copy for Sweep {}
 | 
				
			||||||
 | 
					    impl Clone for Sweep {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					            *self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for Sweep {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self(0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for Sweep {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            Self(byte)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<Sweep> for u8 {
 | 
				
			||||||
 | 
					        fn from(sweep: Sweep) -> Self {
 | 
				
			||||||
 | 
					            sweep.0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					    pub(crate) enum SweepDirection {
 | 
				
			||||||
 | 
					        Increase = 0,
 | 
				
			||||||
 | 
					        Decrease = 1,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for SweepDirection {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            match byte & 0x01 {
 | 
				
			||||||
 | 
					                0b00 => Self::Increase,
 | 
				
			||||||
 | 
					                0b01 => Self::Decrease,
 | 
				
			||||||
 | 
					                _ => unreachable!("{:04X} is not a valid value for SweepChange", byte),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<SweepDirection> for u8 {
 | 
				
			||||||
 | 
					        fn from(change: SweepDirection) -> Self {
 | 
				
			||||||
 | 
					            change as u8
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(super) mod ch3 {
 | 
				
			||||||
 | 
					    #[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					    pub(crate) enum Volume {
 | 
				
			||||||
 | 
					        Mute = 0,
 | 
				
			||||||
 | 
					        Full = 1,
 | 
				
			||||||
 | 
					        Half = 2,
 | 
				
			||||||
 | 
					        Quarter = 3,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Volume {
 | 
				
			||||||
 | 
					        pub(crate) fn shift_count(&self) -> u8 {
 | 
				
			||||||
 | 
					            use Volume::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match *self {
 | 
				
			||||||
 | 
					                Mute => 4,
 | 
				
			||||||
 | 
					                Full => 0,
 | 
				
			||||||
 | 
					                Half => 1,
 | 
				
			||||||
 | 
					                Quarter => 2,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for Volume {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self::Mute
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(super) mod ch4 {
 | 
				
			||||||
 | 
					    use super::{bitfield, NRx4};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bitfield! {
 | 
				
			||||||
 | 
					        pub struct PolynomialCounter(u8);
 | 
				
			||||||
 | 
					        impl Debug;
 | 
				
			||||||
 | 
					        _shift_count, _: 7, 4;
 | 
				
			||||||
 | 
					        from into CounterWidth, _counter_width, _: 3, 3;
 | 
				
			||||||
 | 
					        _divisor_code, _: 2, 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl PolynomialCounter {
 | 
				
			||||||
 | 
					        pub(crate) fn divisor_code(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._divisor_code()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn counter_width(&self) -> CounterWidth {
 | 
				
			||||||
 | 
					            self._counter_width()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn shift_count(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._shift_count()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Copy for PolynomialCounter {}
 | 
				
			||||||
 | 
					    impl Clone for PolynomialCounter {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					            *self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for PolynomialCounter {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self(0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for PolynomialCounter {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            Self(byte)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<PolynomialCounter> for u8 {
 | 
				
			||||||
 | 
					        fn from(poly: PolynomialCounter) -> Self {
 | 
				
			||||||
 | 
					            poly.0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					    pub(crate) enum CounterWidth {
 | 
				
			||||||
 | 
					        Long,  // 15 bits long
 | 
				
			||||||
 | 
					        Short, // 7 bits long
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for CounterWidth {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            match byte & 0x01 {
 | 
				
			||||||
 | 
					                0b00 => Self::Short,
 | 
				
			||||||
 | 
					                0b01 => Self::Long,
 | 
				
			||||||
 | 
					                _ => unreachable!("{:#04X} is not a valid value for CounterWidth", byte),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<CounterWidth> for u8 {
 | 
				
			||||||
 | 
					        fn from(counter_width: CounterWidth) -> Self {
 | 
				
			||||||
 | 
					            counter_width as u8
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bitfield! {
 | 
				
			||||||
 | 
					        pub struct Frequency(u8);
 | 
				
			||||||
 | 
					        impl Debug;
 | 
				
			||||||
 | 
					        _trigger, _: 7;
 | 
				
			||||||
 | 
					        _length_enable, _set_length_enable: 6;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl NRx4 for Frequency {
 | 
				
			||||||
 | 
					        fn trigger(&self) -> bool {
 | 
				
			||||||
 | 
					            self._trigger()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn length_enable(&self) -> bool {
 | 
				
			||||||
 | 
					            self._length_enable()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn set_length_enable(&mut self, value: bool) {
 | 
				
			||||||
 | 
					            self._set_length_enable(value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Copy for Frequency {}
 | 
				
			||||||
 | 
					    impl Clone for Frequency {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					            *self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for Frequency {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self(0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for Frequency {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            Self(byte & 0xC0) // Only bits 7 and 6 hold anything of value
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<Frequency> for u8 {
 | 
				
			||||||
 | 
					        fn from(state: Frequency) -> Self {
 | 
				
			||||||
 | 
					            state.0 & 0x40 // Only bit 6 holds anything of value
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(super) mod common {
 | 
				
			||||||
 | 
					    use super::{bitfield, NRx4};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bitfield! {
 | 
				
			||||||
 | 
					        pub struct FrequencyHigh(u8);
 | 
				
			||||||
 | 
					        impl Debug;
 | 
				
			||||||
 | 
					        _trigger, _: 7;
 | 
				
			||||||
 | 
					        _length_enable, _set_length_enable: 6;
 | 
				
			||||||
 | 
					        pub freq_bits, set_freq_bits: 2, 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl NRx4 for FrequencyHigh {
 | 
				
			||||||
 | 
					        fn trigger(&self) -> bool {
 | 
				
			||||||
 | 
					            self._trigger()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn length_enable(&self) -> bool {
 | 
				
			||||||
 | 
					            self._length_enable()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn set_length_enable(&mut self, value: bool) {
 | 
				
			||||||
 | 
					            self._set_length_enable(value);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Copy for FrequencyHigh {}
 | 
				
			||||||
 | 
					    impl Clone for FrequencyHigh {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					            *self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for FrequencyHigh {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self(0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for FrequencyHigh {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            Self(byte)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<FrequencyHigh> for u8 {
 | 
				
			||||||
 | 
					        fn from(freq: FrequencyHigh) -> Self {
 | 
				
			||||||
 | 
					            freq.0 & 0x40 // Only bit 6 can be read
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bitfield! {
 | 
				
			||||||
 | 
					        pub struct VolumeEnvelope(u8);
 | 
				
			||||||
 | 
					        impl Debug;
 | 
				
			||||||
 | 
					        _init_vol, _: 7, 4;
 | 
				
			||||||
 | 
					        from into EnvelopeDirection, _direction, _: 3, 3;
 | 
				
			||||||
 | 
					        _period, _: 2, 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl VolumeEnvelope {
 | 
				
			||||||
 | 
					        pub(crate) fn init_vol(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._init_vol()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn direction(&self) -> EnvelopeDirection {
 | 
				
			||||||
 | 
					            self._direction()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn period(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._period()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Copy for VolumeEnvelope {}
 | 
				
			||||||
 | 
					    impl Clone for VolumeEnvelope {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					            *self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for VolumeEnvelope {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self(0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for VolumeEnvelope {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            Self(byte)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<VolumeEnvelope> for u8 {
 | 
				
			||||||
 | 
					        fn from(envelope: VolumeEnvelope) -> Self {
 | 
				
			||||||
 | 
					            envelope.0
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					    pub(crate) enum EnvelopeDirection {
 | 
				
			||||||
 | 
					        Decrease = 0,
 | 
				
			||||||
 | 
					        Increase = 1,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for EnvelopeDirection {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            match byte & 0b01 {
 | 
				
			||||||
 | 
					                0b00 => Self::Decrease,
 | 
				
			||||||
 | 
					                0b01 => Self::Increase,
 | 
				
			||||||
 | 
					                _ => unreachable!("{:#04X} is not a valid value for EnvelopeDirection", byte),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<EnvelopeDirection> for u8 {
 | 
				
			||||||
 | 
					        fn from(direction: EnvelopeDirection) -> Self {
 | 
				
			||||||
 | 
					            direction as u8
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for EnvelopeDirection {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self::Decrease
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bitfield! {
 | 
				
			||||||
 | 
					        pub struct SoundDuty(u8);
 | 
				
			||||||
 | 
					        impl Debug;
 | 
				
			||||||
 | 
					        from into WavePattern, _wave_pattern, _: 7, 6;
 | 
				
			||||||
 | 
					        _sound_length, _: 5, 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl SoundDuty {
 | 
				
			||||||
 | 
					        pub(crate) fn wave_pattern(&self) -> WavePattern {
 | 
				
			||||||
 | 
					            self._wave_pattern()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn sound_length(&self) -> u8 {
 | 
				
			||||||
 | 
					            self._sound_length()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Copy for SoundDuty {}
 | 
				
			||||||
 | 
					    impl Clone for SoundDuty {
 | 
				
			||||||
 | 
					        fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					            *self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for SoundDuty {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self(0)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for SoundDuty {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            Self(byte)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<SoundDuty> for u8 {
 | 
				
			||||||
 | 
					        fn from(duty: SoundDuty) -> Self {
 | 
				
			||||||
 | 
					            duty.0 & 0xC0 // Only bits 7 and 6 can be read
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Clone, Copy)]
 | 
				
			||||||
 | 
					    pub(crate) enum WavePattern {
 | 
				
			||||||
 | 
					        OneEighth = 0,     // 12.5% ( _-------_-------_------- )
 | 
				
			||||||
 | 
					        OneQuarter = 1,    // 25%   ( __------__------__------ )
 | 
				
			||||||
 | 
					        OneHalf = 2,       // 50%   ( ____----____----____---- ) (normal)
 | 
				
			||||||
 | 
					        ThreeQuarters = 3, // 75%   ( ______--______--______-- )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl WavePattern {
 | 
				
			||||||
 | 
					        pub(crate) const fn amplitude(&self, index: u8) -> u8 {
 | 
				
			||||||
 | 
					            use WavePattern::*;
 | 
				
			||||||
 | 
					            let i = 7 - index; // an index of 0 should get the highest bit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match *self {
 | 
				
			||||||
 | 
					                OneEighth => (0b00000001 >> i) & 0x01,
 | 
				
			||||||
 | 
					                OneQuarter => (0b10000001 >> i) & 0x01,
 | 
				
			||||||
 | 
					                OneHalf => (0b10000111 >> i) & 0x01,
 | 
				
			||||||
 | 
					                ThreeQuarters => (0b01111110 >> i) & 0x01,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<WavePattern> for u8 {
 | 
				
			||||||
 | 
					        fn from(pattern: WavePattern) -> Self {
 | 
				
			||||||
 | 
					            pattern as Self
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl From<u8> for WavePattern {
 | 
				
			||||||
 | 
					        fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					            match byte & 0b11 {
 | 
				
			||||||
 | 
					                0b00 => Self::OneEighth,
 | 
				
			||||||
 | 
					                0b01 => Self::OneQuarter,
 | 
				
			||||||
 | 
					                0b10 => Self::OneHalf,
 | 
				
			||||||
 | 
					                0b11 => Self::ThreeQuarters,
 | 
				
			||||||
 | 
					                _ => unreachable!("{:#04X} is not a valid value for WavePattern", byte),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bitfield! {
 | 
				
			||||||
 | 
					    pub struct SoundOutput(u8);
 | 
				
			||||||
 | 
					    impl Debug;
 | 
				
			||||||
 | 
					    pub ch4_left, _: 7;
 | 
				
			||||||
 | 
					    pub ch3_left, _: 6;
 | 
				
			||||||
 | 
					    pub ch2_left, _: 5;
 | 
				
			||||||
 | 
					    pub ch1_left, _: 4;
 | 
				
			||||||
 | 
					    pub ch4_right, _: 3;
 | 
				
			||||||
 | 
					    pub ch3_right, _: 2;
 | 
				
			||||||
 | 
					    pub ch2_right, _: 1;
 | 
				
			||||||
 | 
					    pub ch1_right, _: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl SoundOutput {
 | 
				
			||||||
 | 
					    pub(super) fn ch1(&self) -> (bool, bool) {
 | 
				
			||||||
 | 
					        (self.ch1_left(), self.ch1_right())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(super) fn ch2(&self) -> (bool, bool) {
 | 
				
			||||||
 | 
					        (self.ch2_left(), self.ch2_right())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(super) fn ch3(&self) -> (bool, bool) {
 | 
				
			||||||
 | 
					        (self.ch3_left(), self.ch3_right())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(super) fn ch4(&self) -> (bool, bool) {
 | 
				
			||||||
 | 
					        (self.ch4_left(), self.ch4_right())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Copy for SoundOutput {}
 | 
				
			||||||
 | 
					impl Clone for SoundOutput {
 | 
				
			||||||
 | 
					    fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					        *self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<u8> for SoundOutput {
 | 
				
			||||||
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					        Self(byte)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<SoundOutput> for u8 {
 | 
				
			||||||
 | 
					    fn from(output: SoundOutput) -> Self {
 | 
				
			||||||
 | 
					        output.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bitfield! {
 | 
				
			||||||
 | 
					    pub struct ChannelControl(u8);
 | 
				
			||||||
 | 
					    impl Debug;
 | 
				
			||||||
 | 
					    vin_left, _: 7;
 | 
				
			||||||
 | 
					    _left_volume, _: 6, 4;
 | 
				
			||||||
 | 
					    vin_right, _: 3;
 | 
				
			||||||
 | 
					    _right_volume, _: 2, 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ChannelControl {
 | 
				
			||||||
 | 
					    pub(crate) fn left_volume(&self) -> f32 {
 | 
				
			||||||
 | 
					        self._left_volume() as f32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn right_volume(&self) -> f32 {
 | 
				
			||||||
 | 
					        self._right_volume() as f32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Copy for ChannelControl {}
 | 
				
			||||||
 | 
					impl Clone for ChannelControl {
 | 
				
			||||||
 | 
					    fn clone(&self) -> Self {
 | 
				
			||||||
 | 
					        *self
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<u8> for ChannelControl {
 | 
				
			||||||
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
 | 
					        Self(byte)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl From<ChannelControl> for u8 {
 | 
				
			||||||
 | 
					    fn from(ctrl: ChannelControl) -> Self {
 | 
				
			||||||
 | 
					        ctrl.0
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub(super) mod fs {
 | 
				
			||||||
 | 
					    #[derive(Debug)]
 | 
				
			||||||
 | 
					    pub(crate) struct FrameSequencer {
 | 
				
			||||||
 | 
					        step: u8,
 | 
				
			||||||
 | 
					        state: State,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl Default for FrameSequencer {
 | 
				
			||||||
 | 
					        fn default() -> Self {
 | 
				
			||||||
 | 
					            Self {
 | 
				
			||||||
 | 
					                step: Default::default(),
 | 
				
			||||||
 | 
					                state: State::Length,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    impl FrameSequencer {
 | 
				
			||||||
 | 
					        pub(crate) fn next(&mut self) {
 | 
				
			||||||
 | 
					            use State::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            self.step = (self.step + 1) % 8;
 | 
				
			||||||
 | 
					            self.state = match self.step {
 | 
				
			||||||
 | 
					                1 | 3 | 5 => Nothing,
 | 
				
			||||||
 | 
					                0 | 4 => Length,
 | 
				
			||||||
 | 
					                2 | 6 => LengthAndSweep,
 | 
				
			||||||
 | 
					                7 => Envelope,
 | 
				
			||||||
 | 
					                _ => unreachable!("Step {} is invalid for the Frame Sequencer", self.step),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn next_clocks_length(&self) -> bool {
 | 
				
			||||||
 | 
					            use State::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match self.state {
 | 
				
			||||||
 | 
					                Length | LengthAndSweep => true,
 | 
				
			||||||
 | 
					                Nothing | Envelope => false,
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn state(&self) -> State {
 | 
				
			||||||
 | 
					            self.state
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        pub(crate) fn reset(&mut self) {
 | 
				
			||||||
 | 
					            self.step = Default::default();
 | 
				
			||||||
 | 
					            self.state = State::Length;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					    pub(crate) enum State {
 | 
				
			||||||
 | 
					        Length,
 | 
				
			||||||
 | 
					        Nothing,
 | 
				
			||||||
 | 
					        LengthAndSweep,
 | 
				
			||||||
 | 
					        Envelope,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										206
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										206
									
								
								src/bus.rs
									
									
									
									
									
								
							@@ -1,92 +1,64 @@
 | 
				
			|||||||
 | 
					use crate::apu::Apu;
 | 
				
			||||||
use crate::cartridge::Cartridge;
 | 
					use crate::cartridge::Cartridge;
 | 
				
			||||||
use crate::high_ram::HighRam;
 | 
					use crate::high_ram::HighRam;
 | 
				
			||||||
use crate::interrupt::{Interrupt, InterruptFlag};
 | 
					use crate::interrupt::{Interrupt, InterruptFlag};
 | 
				
			||||||
use crate::joypad::Joypad;
 | 
					use crate::joypad::Joypad;
 | 
				
			||||||
use crate::ppu::{Ppu, PpuMode};
 | 
					use crate::ppu::{Ppu, PpuMode};
 | 
				
			||||||
use crate::serial::Serial;
 | 
					use crate::serial::Serial;
 | 
				
			||||||
use crate::sound::{AudioSender, Sound};
 | 
					 | 
				
			||||||
use crate::timer::Timer;
 | 
					use crate::timer::Timer;
 | 
				
			||||||
use crate::work_ram::{VariableWorkRam, WorkRam};
 | 
					use crate::work_ram::{VariableWorkRam, WorkRam};
 | 
				
			||||||
use std::{fs::File, io::Read};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const BOOT_ROM_SIZE: usize = 0x100;
 | 
					pub(crate) const BOOT_SIZE: usize = 0x100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
pub struct Bus {
 | 
					pub struct Bus {
 | 
				
			||||||
    boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long
 | 
					    boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
 | 
				
			||||||
    cartridge: Option<Cartridge>,
 | 
					    pub(crate) cart: Option<Cartridge>,
 | 
				
			||||||
    pub ppu: Ppu,
 | 
					    pub(crate) ppu: Ppu,
 | 
				
			||||||
    work_ram: WorkRam,
 | 
					    work_ram: WorkRam,
 | 
				
			||||||
    var_ram: VariableWorkRam,
 | 
					    var_ram: VariableWorkRam,
 | 
				
			||||||
    pub(crate) timer: Timer,
 | 
					    timer: Timer,
 | 
				
			||||||
    int: Interrupt,
 | 
					    int: Interrupt,
 | 
				
			||||||
    snd: Sound,
 | 
					    pub(crate) apu: Apu,
 | 
				
			||||||
    high_ram: HighRam,
 | 
					    high_ram: HighRam,
 | 
				
			||||||
    serial: Serial,
 | 
					    serial: Serial,
 | 
				
			||||||
    pub(crate) joypad: Joypad,
 | 
					    pub(crate) joyp: Joypad,
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for Bus {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            boot: None,
 | 
					 | 
				
			||||||
            cartridge: None,
 | 
					 | 
				
			||||||
            ppu: Default::default(),
 | 
					 | 
				
			||||||
            work_ram: Default::default(),
 | 
					 | 
				
			||||||
            var_ram: Default::default(),
 | 
					 | 
				
			||||||
            timer: Default::default(),
 | 
					 | 
				
			||||||
            int: Default::default(),
 | 
					 | 
				
			||||||
            snd: Default::default(),
 | 
					 | 
				
			||||||
            high_ram: Default::default(),
 | 
					 | 
				
			||||||
            serial: Default::default(),
 | 
					 | 
				
			||||||
            joypad: Default::default(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Bus {
 | 
					impl Bus {
 | 
				
			||||||
    pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> {
 | 
					    pub(crate) fn with_boot(rom: [u8; 256]) -> Self {
 | 
				
			||||||
        let mut file = File::open(path)?;
 | 
					        Self {
 | 
				
			||||||
        let mut boot_rom = [0u8; 256];
 | 
					            boot: Some(rom),
 | 
				
			||||||
 | 
					 | 
				
			||||||
        file.read_exact(&mut boot_rom)?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Ok(Self {
 | 
					 | 
				
			||||||
            boot: Some(boot_rom),
 | 
					 | 
				
			||||||
            ..Default::default()
 | 
					            ..Default::default()
 | 
				
			||||||
        })
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
 | 
					    #[allow(dead_code)]
 | 
				
			||||||
        self.cartridge = Some(Cartridge::new(path)?);
 | 
					    pub(crate) fn boot_mapped(&self) -> bool {
 | 
				
			||||||
        Ok(())
 | 
					        self.boot.is_some()
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn rom_title(&self) -> Option<&str> {
 | 
					 | 
				
			||||||
        self.cartridge.as_ref()?.title()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn pass_audio_src(&mut self, sender: AudioSender<f32>) {
 | 
					 | 
				
			||||||
        self.snd.set_audio_src(sender)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn is_full(&self) -> bool {
 | 
					 | 
				
			||||||
        self.snd.is_full()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn flush_samples(&mut self) {
 | 
					 | 
				
			||||||
        self.snd.flush_samples()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
    pub(crate) fn clock(&mut self) {
 | 
					    pub(crate) fn clock(&mut self) {
 | 
				
			||||||
        self.ppu.clock();
 | 
					        self.tick(4);
 | 
				
			||||||
        self.timer.clock();
 | 
					 | 
				
			||||||
        self.snd.clock(self.timer.divider);
 | 
					 | 
				
			||||||
        self.clock_dma();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn clock_dma(&mut self) {
 | 
					    #[inline]
 | 
				
			||||||
        if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() {
 | 
					    fn tick(&mut self, limit: u8) {
 | 
				
			||||||
 | 
					        for _ in 0..limit {
 | 
				
			||||||
 | 
					            self.timer.tick();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if let Some(c) = self.cart.as_mut() {
 | 
				
			||||||
 | 
					                c.tick()
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            self.ppu.tick();
 | 
				
			||||||
 | 
					            self.apu.tick(self.timer.divider);
 | 
				
			||||||
 | 
					            self.dma_tick()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn dma_tick(&mut self) {
 | 
				
			||||||
 | 
					        if let Some((src_addr, dest_addr)) = self.ppu.dma.tick() {
 | 
				
			||||||
            let byte = self.oam_read_byte(src_addr);
 | 
					            let byte = self.oam_read_byte(src_addr);
 | 
				
			||||||
            self.oam_write_byte(dest_addr, byte);
 | 
					            self.oam_write_byte(dest_addr, byte);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -94,7 +66,7 @@ impl Bus {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Bus {
 | 
					impl Bus {
 | 
				
			||||||
    pub fn oam_read_byte(&self, addr: u16) -> u8 {
 | 
					    pub(crate) fn oam_read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        match addr {
 | 
					        match addr {
 | 
				
			||||||
            0x0000..=0x7FFF => {
 | 
					            0x0000..=0x7FFF => {
 | 
				
			||||||
                // 16KB ROM bank 00 (ends at 0x3FFF)
 | 
					                // 16KB ROM bank 00 (ends at 0x3FFF)
 | 
				
			||||||
@@ -105,16 +77,16 @@ impl Bus {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match self.cartridge.as_ref() {
 | 
					                match self.cart.as_ref() {
 | 
				
			||||||
                    Some(cart) => cart.read_byte(addr),
 | 
					                    Some(cart) => cart.read_byte(addr),
 | 
				
			||||||
                    None => panic!("Tried to read from a non-existent cartridge"),
 | 
					                    None => 0xFF,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
 | 
					            0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
 | 
				
			||||||
            0xA000..=0xBFFF => match self.cartridge.as_ref() {
 | 
					            0xA000..=0xBFFF => match self.cart.as_ref() {
 | 
				
			||||||
                // 8KB External RAM
 | 
					                // 8KB External RAM
 | 
				
			||||||
                Some(cart) => cart.read_byte(addr),
 | 
					                Some(cart) => cart.read_byte(addr),
 | 
				
			||||||
                None => panic!("Tried to read from a non-existent cartridge"),
 | 
					                None => 0xFF,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
 | 
					            0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
 | 
				
			||||||
            0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N
 | 
					            0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N
 | 
				
			||||||
@@ -141,7 +113,7 @@ impl Bus {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn oam_write_byte(&mut self, addr: u16, byte: u8) {
 | 
					    pub(crate) fn oam_write_byte(&mut self, addr: u16, byte: u8) {
 | 
				
			||||||
        self.ppu.oam.write_byte(addr, byte);
 | 
					        self.ppu.oam.write_byte(addr, byte);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -158,9 +130,9 @@ impl BusIo for Bus {
 | 
				
			|||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match self.cartridge.as_ref() {
 | 
					                match self.cart.as_ref() {
 | 
				
			||||||
                    Some(cart) => cart.read_byte(addr),
 | 
					                    Some(cart) => cart.read_byte(addr),
 | 
				
			||||||
                    None => panic!("Tried to read from a non-existent cartridge"),
 | 
					                    None => 0xFF,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0x8000..=0x9FFF => {
 | 
					            0x8000..=0x9FFF => {
 | 
				
			||||||
@@ -170,10 +142,10 @@ impl BusIo for Bus {
 | 
				
			|||||||
                    _ => self.ppu.read_byte(addr),
 | 
					                    _ => self.ppu.read_byte(addr),
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0xA000..=0xBFFF => match self.cartridge.as_ref() {
 | 
					            0xA000..=0xBFFF => match self.cart.as_ref() {
 | 
				
			||||||
                // 8KB External RAM
 | 
					                // 8KB External RAM
 | 
				
			||||||
                Some(cart) => cart.read_byte(addr),
 | 
					                Some(cart) => cart.read_byte(addr),
 | 
				
			||||||
                None => panic!("Tried to read from a non-existent cartridge"),
 | 
					                None => 0xFF,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
 | 
					            0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
 | 
				
			||||||
            0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N
 | 
					            0xD000..=0xDFFF => self.var_ram.read_byte(addr),  // 4KB Work RAM Bank 1 -> N
 | 
				
			||||||
@@ -220,33 +192,15 @@ impl BusIo for Bus {
 | 
				
			|||||||
                // Every address here starts with 0xFF so we can just check the
 | 
					                // Every address here starts with 0xFF so we can just check the
 | 
				
			||||||
                // low byte to figure out which register it is
 | 
					                // low byte to figure out which register it is
 | 
				
			||||||
                match addr & 0x00FF {
 | 
					                match addr & 0x00FF {
 | 
				
			||||||
                    0x00 => self.joypad.p1,
 | 
					                    0x00 => self.joyp.p1,
 | 
				
			||||||
                    0x01 => self.serial.next,
 | 
					                    0x01 => self.serial.next,
 | 
				
			||||||
                    0x02 => self.serial.ctrl.into(),
 | 
					                    0x02 => self.serial.ctrl.into(),
 | 
				
			||||||
                    0x04 => (self.timer.divider >> 8) as u8,
 | 
					                    0x04 => (self.timer.divider >> 8) as u8,
 | 
				
			||||||
                    0x05 => self.timer.counter,
 | 
					                    0x05 => self.timer.tima(),
 | 
				
			||||||
                    0x06 => self.timer.modulo,
 | 
					                    0x06 => self.timer.modulo,
 | 
				
			||||||
                    0x07 => self.timer.ctrl.into(),
 | 
					                    0x07 => self.timer.ctrl.into(),
 | 
				
			||||||
                    0x0F => self.interrupt_flag().into(),
 | 
					                    0x0F => self.interrupt_flag().into(),
 | 
				
			||||||
                    0x10 => self.snd.ch1.sweep.into(),
 | 
					                    0x10..=0x3F => self.apu.read_byte(addr),
 | 
				
			||||||
                    0x11 => self.snd.ch1.duty(),
 | 
					 | 
				
			||||||
                    0x12 => self.snd.ch1.envelope.into(),
 | 
					 | 
				
			||||||
                    0x14 => self.snd.ch1.freq_hi(),
 | 
					 | 
				
			||||||
                    0x16 => self.snd.ch2.duty(),
 | 
					 | 
				
			||||||
                    0x17 => self.snd.ch2.envelope.into(),
 | 
					 | 
				
			||||||
                    0x19 => self.snd.ch2.freq_hi(),
 | 
					 | 
				
			||||||
                    0x1A => self.snd.ch3.enabled(),
 | 
					 | 
				
			||||||
                    0x1B => self.snd.ch3.len(),
 | 
					 | 
				
			||||||
                    0x1C => self.snd.ch3.volume(),
 | 
					 | 
				
			||||||
                    0x1E => self.snd.ch3.freq_hi(),
 | 
					 | 
				
			||||||
                    0x20 => self.snd.ch4.len(),
 | 
					 | 
				
			||||||
                    0x21 => self.snd.ch4.envelope.into(),
 | 
					 | 
				
			||||||
                    0x22 => self.snd.ch4.poly.into(),
 | 
					 | 
				
			||||||
                    0x23 => self.snd.ch4.freq_data(),
 | 
					 | 
				
			||||||
                    0x24 => self.snd.ctrl.channel.into(),
 | 
					 | 
				
			||||||
                    0x25 => self.snd.ctrl.output.into(),
 | 
					 | 
				
			||||||
                    0x26 => self.snd.ctrl.status(&self.snd),
 | 
					 | 
				
			||||||
                    0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30],
 | 
					 | 
				
			||||||
                    0x40 => self.ppu.ctrl.into(),
 | 
					                    0x40 => self.ppu.ctrl.into(),
 | 
				
			||||||
                    0x41 => self.ppu.stat.into(),
 | 
					                    0x41 => self.ppu.stat.into(),
 | 
				
			||||||
                    0x42 => self.ppu.pos.scroll_y,
 | 
					                    0x42 => self.ppu.pos.scroll_y,
 | 
				
			||||||
@@ -259,8 +213,11 @@ impl BusIo for Bus {
 | 
				
			|||||||
                    0x49 => self.ppu.monochrome.obj_palette_1.into(),
 | 
					                    0x49 => self.ppu.monochrome.obj_palette_1.into(),
 | 
				
			||||||
                    0x4A => self.ppu.pos.window_y,
 | 
					                    0x4A => self.ppu.pos.window_y,
 | 
				
			||||||
                    0x4B => self.ppu.pos.window_x,
 | 
					                    0x4B => self.ppu.pos.window_x,
 | 
				
			||||||
                    0x4D => 0xFF, // CGB Specific Register
 | 
					                    0x4F => 0xFF, // CGB VRAM Bank Select
 | 
				
			||||||
                    _ => unimplemented!("Unable to read {:#06X} in I/O Registers", addr),
 | 
					                    _ => {
 | 
				
			||||||
 | 
					                        tracing::warn!("Attempted read from {:#06X} on IO", addr);
 | 
				
			||||||
 | 
					                        0xFF
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0xFF80..=0xFFFE => {
 | 
					            0xFF80..=0xFFFE => {
 | 
				
			||||||
@@ -279,9 +236,8 @@ impl BusIo for Bus {
 | 
				
			|||||||
            0x0000..=0x7FFF => {
 | 
					            0x0000..=0x7FFF => {
 | 
				
			||||||
                // 16KB ROM bank 00 (ends at 0x3FFF)
 | 
					                // 16KB ROM bank 00 (ends at 0x3FFF)
 | 
				
			||||||
                // and 16KB ROM Bank 01 -> NN (switchable via MB)
 | 
					                // and 16KB ROM Bank 01 -> NN (switchable via MB)
 | 
				
			||||||
                match self.cartridge.as_mut() {
 | 
					                if let Some(cart) = self.cart.as_mut() {
 | 
				
			||||||
                    Some(cart) => cart.write_byte(addr, byte),
 | 
					                    cart.write_byte(addr, byte);
 | 
				
			||||||
                    None => panic!("Tried to write into non-existent cartridge"),
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0x8000..=0x9FFF => {
 | 
					            0x8000..=0x9FFF => {
 | 
				
			||||||
@@ -293,9 +249,8 @@ impl BusIo for Bus {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
            0xA000..=0xBFFF => {
 | 
					            0xA000..=0xBFFF => {
 | 
				
			||||||
                // 8KB External RAM
 | 
					                // 8KB External RAM
 | 
				
			||||||
                match self.cartridge.as_mut() {
 | 
					                if let Some(cart) = self.cart.as_mut() {
 | 
				
			||||||
                    Some(cart) => cart.write_byte(addr, byte),
 | 
					                    cart.write_byte(addr, byte);
 | 
				
			||||||
                    None => panic!("Tried to write into non-existent cartridge"),
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0
 | 
					            0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0
 | 
				
			||||||
@@ -328,43 +283,22 @@ impl BusIo for Bus {
 | 
				
			|||||||
                    _ => {}
 | 
					                    _ => {}
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing.
 | 
					            0xFEA0..=0xFEFF => {} // FIXME: As far as I know, writes to here do nothing.
 | 
				
			||||||
            0xFF00..=0xFF7F => {
 | 
					            0xFF00..=0xFF7F => {
 | 
				
			||||||
                // IO Registers
 | 
					                // IO Registers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Every address here starts with 0xFF so we can just check the
 | 
					                // Every address here starts with 0xFF so we can just check the
 | 
				
			||||||
                // low byte to figure out which register it is
 | 
					                // low byte to figure out which register it is
 | 
				
			||||||
                match addr & 0x00FF {
 | 
					                match addr & 0x00FF {
 | 
				
			||||||
                    0x00 => self.joypad.update(byte),
 | 
					                    0x00 => self.joyp.update(byte),
 | 
				
			||||||
                    0x01 => self.serial.next = byte,
 | 
					                    0x01 => self.serial.next = byte,
 | 
				
			||||||
                    0x02 => self.serial.ctrl = byte.into(),
 | 
					                    0x02 => self.serial.ctrl = byte.into(),
 | 
				
			||||||
                    0x04 => self.timer.divider = 0x0000,
 | 
					                    0x04 => self.timer.divider = 0x0000,
 | 
				
			||||||
                    0x05 => self.timer.counter = byte,
 | 
					                    0x05 => self.timer.set_tima(byte),
 | 
				
			||||||
                    0x06 => self.timer.modulo = byte,
 | 
					                    0x06 => self.timer.modulo = byte,
 | 
				
			||||||
                    0x07 => self.timer.ctrl = byte.into(),
 | 
					                    0x07 => self.timer.ctrl = byte.into(),
 | 
				
			||||||
                    0x0F => self.set_interrupt_flag(byte),
 | 
					                    0x0F => self.set_interrupt_flag(byte),
 | 
				
			||||||
                    0x10 => self.snd.ch1.sweep = byte.into(),
 | 
					                    0x10..=0x3F => self.apu.write_byte(addr, byte),
 | 
				
			||||||
                    0x11 => self.snd.ch1.set_duty(byte),
 | 
					 | 
				
			||||||
                    0x12 => self.snd.ch1.envelope = byte.into(),
 | 
					 | 
				
			||||||
                    0x13 => self.snd.ch1.set_freq_lo(byte),
 | 
					 | 
				
			||||||
                    0x14 => self.snd.ch1.set_freq_hi(byte),
 | 
					 | 
				
			||||||
                    0x16 => self.snd.ch2.set_duty(byte),
 | 
					 | 
				
			||||||
                    0x17 => self.snd.ch2.envelope = byte.into(),
 | 
					 | 
				
			||||||
                    0x18 => self.snd.ch2.set_freq_lo(byte),
 | 
					 | 
				
			||||||
                    0x19 => self.snd.ch2.set_freq_hi(byte),
 | 
					 | 
				
			||||||
                    0x1A => self.snd.ch3.set_enabled(byte),
 | 
					 | 
				
			||||||
                    0x1B => self.snd.ch3.set_len(byte),
 | 
					 | 
				
			||||||
                    0x1C => self.snd.ch3.set_volume(byte),
 | 
					 | 
				
			||||||
                    0x1D => self.snd.ch3.set_freq_lo(byte),
 | 
					 | 
				
			||||||
                    0x1E => self.snd.ch3.set_freq_hi(byte),
 | 
					 | 
				
			||||||
                    0x20 => self.snd.ch4.set_len(byte),
 | 
					 | 
				
			||||||
                    0x21 => self.snd.ch4.envelope = byte.into(),
 | 
					 | 
				
			||||||
                    0x22 => self.snd.ch4.poly = byte.into(),
 | 
					 | 
				
			||||||
                    0x23 => self.snd.ch4.set_freq_data(byte),
 | 
					 | 
				
			||||||
                    0x24 => self.snd.ctrl.channel = byte.into(),
 | 
					 | 
				
			||||||
                    0x25 => self.snd.ctrl.output = byte.into(),
 | 
					 | 
				
			||||||
                    0x26 => self.snd.ctrl.set_status(byte), // FIXME: Should we control which bytes are written to here?
 | 
					 | 
				
			||||||
                    0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30] = byte,
 | 
					 | 
				
			||||||
                    0x40 => self.ppu.ctrl = byte.into(),
 | 
					                    0x40 => self.ppu.ctrl = byte.into(),
 | 
				
			||||||
                    0x41 => self.ppu.stat.update(byte),
 | 
					                    0x41 => self.ppu.stat.update(byte),
 | 
				
			||||||
                    0x42 => self.ppu.pos.scroll_y = byte,
 | 
					                    0x42 => self.ppu.pos.scroll_y = byte,
 | 
				
			||||||
@@ -390,14 +324,15 @@ impl BusIo for Bus {
 | 
				
			|||||||
                    0x4A => self.ppu.pos.window_y = byte,
 | 
					                    0x4A => self.ppu.pos.window_y = byte,
 | 
				
			||||||
                    0x4B => self.ppu.pos.window_x = byte,
 | 
					                    0x4B => self.ppu.pos.window_x = byte,
 | 
				
			||||||
                    0x4D => {} // CGB Specific Register
 | 
					                    0x4D => {} // CGB Specific Register
 | 
				
			||||||
 | 
					                    0x4F => {} // CGB VRAM Bank Select
 | 
				
			||||||
                    0x50 => {
 | 
					                    0x50 => {
 | 
				
			||||||
                        // Disable Boot ROM
 | 
					                        // Disable Boot ROM
 | 
				
			||||||
                        if byte != 0 {
 | 
					                        if byte != 0 {
 | 
				
			||||||
                            self.boot = None;
 | 
					                            self.boot = None;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                    0x7F => {} // Tetris tries to write to this non-existent IO Address
 | 
					                    0x70 => {} // CGB WRAM Bank Select
 | 
				
			||||||
                    _ => unimplemented!("Unable to write to {:#06X} in I/O Registers", addr),
 | 
					                    _ => tracing::warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr),
 | 
				
			||||||
                };
 | 
					                };
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            0xFF80..=0xFFFE => {
 | 
					            0xFF80..=0xFFFE => {
 | 
				
			||||||
@@ -412,17 +347,6 @@ impl BusIo for Bus {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Bus {
 | 
					 | 
				
			||||||
    pub(crate) fn read_word(&self, addr: u16) -> u16 {
 | 
					 | 
				
			||||||
        (self.read_byte(addr + 1) as u16) << 8 | self.read_byte(addr) as u16
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn write_word(&mut self, addr: u16, word: u16) {
 | 
					 | 
				
			||||||
        self.write_byte(addr + 1, (word >> 8) as u8);
 | 
					 | 
				
			||||||
        self.write_byte(addr, (word & 0x00FF) as u8);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Bus {
 | 
					impl Bus {
 | 
				
			||||||
    fn interrupt_flag(&self) -> InterruptFlag {
 | 
					    fn interrupt_flag(&self) -> InterruptFlag {
 | 
				
			||||||
        // Read the current interrupt information from the PPU
 | 
					        // Read the current interrupt information from the PPU
 | 
				
			||||||
@@ -430,7 +354,7 @@ impl Bus {
 | 
				
			|||||||
        let lcd_stat = self.ppu.int.lcd_stat();
 | 
					        let lcd_stat = self.ppu.int.lcd_stat();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Read the current interrupt information from the Joypad
 | 
					        // Read the current interrupt information from the Joypad
 | 
				
			||||||
        let joypad = self.joypad.interrupt();
 | 
					        let joypad = self.joyp.interrupt();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Read the current interrupt information from the Timer
 | 
					        // Read the current interrupt information from the Timer
 | 
				
			||||||
        let timer = self.timer.interrupt();
 | 
					        let timer = self.timer.interrupt();
 | 
				
			||||||
@@ -460,7 +384,7 @@ impl Bus {
 | 
				
			|||||||
        self.ppu.int.set_lcd_stat(lcd_stat);
 | 
					        self.ppu.int.set_lcd_stat(lcd_stat);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Update the Joypad's instance of the following interrupts
 | 
					        // Update the Joypad's instance of the following interrupts
 | 
				
			||||||
        self.joypad.set_interrupt(joypad);
 | 
					        self.joyp.set_interrupt(joypad);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Update the Timer's instance of the following interrupts
 | 
					        // Update the Timer's instance of the following interrupts
 | 
				
			||||||
        self.timer.set_interrupt(timer);
 | 
					        self.timer.set_interrupt(timer);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1040
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										1040
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										439
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										439
									
								
								src/cpu.rs
									
									
									
									
									
								
							@@ -1,54 +1,30 @@
 | 
				
			|||||||
use crate::bus::{Bus, BusIo};
 | 
					use crate::bus::{Bus, BusIo, BOOT_SIZE};
 | 
				
			||||||
use crate::instruction::{Cycle, Instruction};
 | 
					use crate::instruction::Instruction;
 | 
				
			||||||
use crate::interrupt::{InterruptEnable, InterruptFlag};
 | 
					use crate::interrupt::{InterruptEnable, InterruptFlag};
 | 
				
			||||||
use crate::joypad::Joypad;
 | 
					use crate::Cycle;
 | 
				
			||||||
use crate::ppu::Ppu;
 | 
					 | 
				
			||||||
use crate::sound::AudioSender;
 | 
					 | 
				
			||||||
use crate::timer::Timer;
 | 
					 | 
				
			||||||
use bitfield::bitfield;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
 | 
					use std::fmt::{Display, Formatter, Result as FmtResult};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Cpu {
 | 
					pub struct Cpu {
 | 
				
			||||||
    pub bus: Bus,
 | 
					    pub(crate) bus: Bus,
 | 
				
			||||||
    reg: Registers,
 | 
					    reg: Registers,
 | 
				
			||||||
    flags: Flags,
 | 
					    flags: Flags,
 | 
				
			||||||
    ime: ImeState,
 | 
					    ime: ImeState,
 | 
				
			||||||
    // TODO: Merge halted and state properties
 | 
					 | 
				
			||||||
    halted: Option<HaltState>,
 | 
					 | 
				
			||||||
    state: State,
 | 
					    state: State,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    pub fn new() -> Self {
 | 
					    pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            reg: Registers {
 | 
					            bus: Bus::with_boot(rom),
 | 
				
			||||||
                a: 0x01,
 | 
					            reg: Default::default(),
 | 
				
			||||||
                b: 0x00,
 | 
					            flags: Flags(0),
 | 
				
			||||||
                c: 0x13,
 | 
					            ime: ImeState::Disabled,
 | 
				
			||||||
                d: 0x00,
 | 
					            state: State::Execute,
 | 
				
			||||||
                e: 0xD8,
 | 
					 | 
				
			||||||
                h: 0x01,
 | 
					 | 
				
			||||||
                l: 0x4D,
 | 
					 | 
				
			||||||
                sp: 0xFFFE,
 | 
					 | 
				
			||||||
                pc: 0x0100,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            flags: 0xb0.into(),
 | 
					 | 
				
			||||||
            ..Default::default()
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn boot_new(path: &str) -> anyhow::Result<Self> {
 | 
					 | 
				
			||||||
        Ok(Self {
 | 
					 | 
				
			||||||
            bus: Bus::with_boot(path)?,
 | 
					 | 
				
			||||||
            ..Default::default()
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn set_audio_src(&mut self, sender: AudioSender<f32>) {
 | 
					 | 
				
			||||||
        self.bus.pass_audio_src(sender)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn ime(&self) -> ImeState {
 | 
					    pub(crate) fn ime(&self) -> ImeState {
 | 
				
			||||||
        self.ime
 | 
					        self.ime
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -57,88 +33,100 @@ impl Cpu {
 | 
				
			|||||||
        self.ime = state;
 | 
					        self.ime = state;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn halt(&mut self, state: HaltState) {
 | 
					    pub(crate) fn halt_cpu(&mut self, kind: HaltKind) {
 | 
				
			||||||
        self.halted = Some(state);
 | 
					        self.state = State::Halt(kind);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn resume(&mut self) {
 | 
					    fn resume_execution(&mut self) {
 | 
				
			||||||
        self.halted = None;
 | 
					        self.state = State::Execute;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn halted(&self) -> Option<HaltState> {
 | 
					    pub(crate) fn is_halted(&self) -> bool {
 | 
				
			||||||
        self.halted
 | 
					        matches!(self.state, State::Halt(_))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn inc_pc(&mut self) {
 | 
					    pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
 | 
				
			||||||
        self.reg.pc += 1;
 | 
					        match self.state {
 | 
				
			||||||
 | 
					            State::Halt(kind) => Some(kind),
 | 
				
			||||||
 | 
					            _ => None,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
 | 
					impl Default for Cpu {
 | 
				
			||||||
        self.bus.load_cartridge(path)
 | 
					    fn default() -> Self {
 | 
				
			||||||
    }
 | 
					        Cpu::new(*include_bytes!("../bin/bootix_dmg.bin"))
 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn rom_title(&self) -> Option<&str> {
 | 
					 | 
				
			||||||
        self.bus.rom_title()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    fn fetch(&self) -> u8 {
 | 
					    /// Fetch an [Instruction] from the memory bus
 | 
				
			||||||
        self.bus.read_byte(self.reg.pc)
 | 
					    /// (4 cycles)
 | 
				
			||||||
 | 
					    fn fetch(&mut self) -> u8 {
 | 
				
			||||||
 | 
					        let byte = self.read_byte(self.reg.pc);
 | 
				
			||||||
 | 
					        self.bus.clock();
 | 
				
			||||||
 | 
					        self.reg.pc += 1;
 | 
				
			||||||
 | 
					        byte
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Decode a byte into an [SM83](Cpu) [Instruction]
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// If opcode == 0xCB, then decoding costs 4 cycles.
 | 
				
			||||||
 | 
					    /// Otherwise, decoding is free
 | 
				
			||||||
    pub(crate) fn decode(&mut self, opcode: u8) -> Instruction {
 | 
					    pub(crate) fn decode(&mut self, opcode: u8) -> Instruction {
 | 
				
			||||||
        Instruction::from_byte(self, opcode)
 | 
					        if opcode == 0xCB {
 | 
				
			||||||
 | 
					            Instruction::decode(self.fetch(), true)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Instruction::decode(opcode, false)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Execute an [Instruction].
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// The amount of cycles necessary to execute an instruction range from
 | 
				
			||||||
 | 
					    /// 0 to 20 T-cycles
 | 
				
			||||||
    fn execute(&mut self, instruction: Instruction) -> Cycle {
 | 
					    fn execute(&mut self, instruction: Instruction) -> Cycle {
 | 
				
			||||||
        Instruction::execute(self, instruction)
 | 
					        Instruction::execute(self, instruction)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub fn step(&mut self) -> Cycle {
 | 
					    /// Perform the [`Cpu::fetch()`] [`Cpu::decode()`] [`Cpu::execute()`]
 | 
				
			||||||
        let cycles = match self.halted() {
 | 
					    /// routine.
 | 
				
			||||||
            Some(state) => {
 | 
					    ///
 | 
				
			||||||
                use HaltState::*;
 | 
					    /// Handle HALT and interrupts.
 | 
				
			||||||
 | 
					    pub(crate) fn step(&mut self) -> Cycle {
 | 
				
			||||||
 | 
					        // Log instructions
 | 
				
			||||||
 | 
					        // if self.reg.pc > 0xFF {
 | 
				
			||||||
 | 
					        //     let out = std::io::stdout();
 | 
				
			||||||
 | 
					        //     let _ = self._print_logs(out.lock());
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                match state {
 | 
					        if let Some(elapsed) = self.handle_interrupt() {
 | 
				
			||||||
                    ImeEnabled | NonePending => Cycle::new(4),
 | 
					            return elapsed;
 | 
				
			||||||
                    SomePending => todo!("Implement HALT bug"),
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            None => {
 | 
					 | 
				
			||||||
                let opcode = self.fetch();
 | 
					 | 
				
			||||||
                self.inc_pc();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let instr = self.decode(opcode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                // let out = std::io::stdout();
 | 
					 | 
				
			||||||
                // let _ = self._debug_log(out.lock(), &instr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let cycles = self.execute(instr);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                self.check_ime();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                cycles
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // TODO: With how we currently handle audio
 | 
					 | 
				
			||||||
        // this --while being correct-- incurs a performance penalty
 | 
					 | 
				
			||||||
        // as our emu is audio-bound.
 | 
					 | 
				
			||||||
        let mut elapsed = 0x00;
 | 
					 | 
				
			||||||
        let pending: u32 = cycles.into();
 | 
					 | 
				
			||||||
        while elapsed < pending {
 | 
					 | 
				
			||||||
            if !self.bus.is_full() {
 | 
					 | 
				
			||||||
                self.bus.clock();
 | 
					 | 
				
			||||||
                elapsed += 1;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                self.bus.flush_samples();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.handle_interrupts();
 | 
					        if let Some(kind) = self.halt_kind() {
 | 
				
			||||||
 | 
					            use HaltKind::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        cycles
 | 
					            self.bus.clock();
 | 
				
			||||||
 | 
					            return match kind {
 | 
				
			||||||
 | 
					                ImeEnabled | NonePending => 4,
 | 
				
			||||||
 | 
					                SomePending => todo!("Implement HALT bug"),
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let opcode = self.fetch();
 | 
				
			||||||
 | 
					        let instr = self.decode(opcode);
 | 
				
			||||||
 | 
					        let elapsed = self.execute(instr);
 | 
				
			||||||
 | 
					        self.handle_ei();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // For use in Blargg's Test ROMs
 | 
				
			||||||
 | 
					        // if self.read_byte(0xFF02) == 0x81 {
 | 
				
			||||||
 | 
					        //     let c = self.read_byte(0xFF01) as char;
 | 
				
			||||||
 | 
					        //     self.write_byte(0xFF02, 0x00);
 | 
				
			||||||
 | 
					        //     eprint!("{}", c);
 | 
				
			||||||
 | 
					        // }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        elapsed
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -153,133 +141,103 @@ impl BusIo for Cpu {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    pub(crate) fn read_imm_byte(&mut self, addr: u16) -> u8 {
 | 
					    fn handle_ei(&mut self) {
 | 
				
			||||||
        self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call
 | 
					 | 
				
			||||||
        self.bus.read_byte(addr)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn read_imm_word(&mut self, addr: u16) -> u16 {
 | 
					 | 
				
			||||||
        self.inc_pc();
 | 
					 | 
				
			||||||
        self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 2 after this function call
 | 
					 | 
				
			||||||
        self.bus.read_word(addr)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn write_word(&mut self, addr: u16, word: u16) {
 | 
					 | 
				
			||||||
        self.bus.write_word(addr, word)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Cpu {
 | 
					 | 
				
			||||||
    pub fn ppu(&mut self) -> &Ppu {
 | 
					 | 
				
			||||||
        &self.bus.ppu
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn joypad_mut(&mut self) -> &mut Joypad {
 | 
					 | 
				
			||||||
        &mut self.bus.joypad
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub(crate) fn timer(&self) -> &Timer {
 | 
					 | 
				
			||||||
        &self.bus.timer
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn check_ime(&mut self) {
 | 
					 | 
				
			||||||
        match self.ime {
 | 
					        match self.ime {
 | 
				
			||||||
            ImeState::Pending => {
 | 
					            ImeState::EiExecuted => self.ime = ImeState::Pending,
 | 
				
			||||||
                // This is within the context of the EI instruction, we need to not update EI until the end of the
 | 
					            ImeState::Pending => self.ime = ImeState::Enabled,
 | 
				
			||||||
                // next executed Instruction
 | 
					            ImeState::Disabled | ImeState::Enabled => {}
 | 
				
			||||||
                self.ime = ImeState::PendingEnd;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ImeState::PendingEnd => {
 | 
					 | 
				
			||||||
                // The Instruction after EI has now been executed, so we want to enable the IME flag here
 | 
					 | 
				
			||||||
                self.ime = ImeState::Enabled;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            ImeState::Disabled | ImeState::Enabled => {} // Do Nothing
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn handle_interrupts(&mut self) {
 | 
					    pub(crate) fn int_request(&self) -> u8 {
 | 
				
			||||||
        let req = self.read_byte(0xFF0F);
 | 
					        self.read_byte(0xFF0F)
 | 
				
			||||||
        let enabled = self.read_byte(0xFFFF);
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if self.halted.is_some() {
 | 
					    pub(crate) fn int_enable(&self) -> u8 {
 | 
				
			||||||
 | 
					        self.read_byte(0xFFFF)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn handle_interrupt(&mut self) -> Option<Cycle> {
 | 
				
			||||||
 | 
					        let irq = self.int_request();
 | 
				
			||||||
 | 
					        let enable = self.int_enable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO: Ensure that this behaviour is correct
 | 
				
			||||||
 | 
					        if self.is_halted() {
 | 
				
			||||||
            // When we're here either a HALT with IME set or
 | 
					            // When we're here either a HALT with IME set or
 | 
				
			||||||
            // a HALT with IME not set and No pending Interrupts was called
 | 
					            // a HALT with IME not set and No pending Interrupts was called
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if req & enabled != 0 {
 | 
					            if irq & enable != 0 {
 | 
				
			||||||
                // The if self.ime() below correctly follows the "resuming from HALT" behaviour so
 | 
					                // The if self.ime() below correctly follows the "resuming from HALT" behaviour so
 | 
				
			||||||
                // nothing actually needs to be added here. This is just documentation
 | 
					                // nothing actually needs to be added here. This is just documentation
 | 
				
			||||||
                // since it's a bit weird why nothing is being done
 | 
					                // since it's a bit weird why nothing is being done
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                self.resume()
 | 
					                self.resume_execution();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let ImeState::Enabled = self.ime() {
 | 
					        match self.ime() {
 | 
				
			||||||
            let mut req: InterruptFlag = req.into();
 | 
					            ImeState::Enabled => {
 | 
				
			||||||
            let enabled: InterruptEnable = enabled.into();
 | 
					                let mut irq: InterruptFlag = irq.into();
 | 
				
			||||||
 | 
					                let enable: InterruptEnable = enable.into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let vector = if req.vblank() && enabled.vblank() {
 | 
					                let rst_vector = if irq.vblank() && enable.vblank() {
 | 
				
			||||||
                // Handle VBlank Interrupt
 | 
					                    // Handle VBlank Interrupt
 | 
				
			||||||
                req.set_vblank(false);
 | 
					                    irq.set_vblank(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // INT 40h
 | 
					                    // INT 40h
 | 
				
			||||||
                Some(0x40)
 | 
					                    Some(0x40)
 | 
				
			||||||
            } else if req.lcd_stat() && enabled.lcd_stat() {
 | 
					                } else if irq.lcd_stat() && enable.lcd_stat() {
 | 
				
			||||||
                // Handle LCD STAT Interrupt
 | 
					                    // Handle LCD STAT Interrupt
 | 
				
			||||||
                req.set_lcd_stat(false);
 | 
					                    irq.set_lcd_stat(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // INT 48h
 | 
					                    // INT 48h
 | 
				
			||||||
                Some(0x48)
 | 
					                    Some(0x48)
 | 
				
			||||||
            } else if req.timer() && enabled.timer() {
 | 
					                } else if irq.timer() && enable.timer() {
 | 
				
			||||||
                // Handle Timer Interrupt
 | 
					                    // Handle Timer Interrupt
 | 
				
			||||||
                req.set_timer(false);
 | 
					                    irq.set_timer(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // INT 50h
 | 
					                    // INT 50h
 | 
				
			||||||
                Some(0x50)
 | 
					                    Some(0x50)
 | 
				
			||||||
            } else if req.serial() && enabled.serial() {
 | 
					                } else if irq.serial() && enable.serial() {
 | 
				
			||||||
                // Handle Serial Interrupt
 | 
					                    // Handle Serial Interrupt
 | 
				
			||||||
                req.set_serial(false);
 | 
					                    irq.set_serial(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // INT 58h
 | 
					                    // INT 58h
 | 
				
			||||||
                Some(0x58)
 | 
					                    Some(0x58)
 | 
				
			||||||
            } else if req.joypad() && enabled.joypad() {
 | 
					                } else if irq.joypad() && enable.joypad() {
 | 
				
			||||||
                // Handle Joypad Interrupt
 | 
					                    // Handle Joypad Interrupt
 | 
				
			||||||
                req.set_joypad(false);
 | 
					                    irq.set_joypad(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // INT 60h
 | 
					                    // INT 60h
 | 
				
			||||||
                Some(0x60)
 | 
					                    Some(0x60)
 | 
				
			||||||
            } else {
 | 
					                } else {
 | 
				
			||||||
                None
 | 
					                    None
 | 
				
			||||||
            };
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let _ = match vector {
 | 
					                match rst_vector {
 | 
				
			||||||
                Some(address) => {
 | 
					                    Some(vector) => {
 | 
				
			||||||
                    //  Write the Changes to 0xFF0F and 0xFFFF registers
 | 
					                        //  Write the Changes to 0xFF0F and 0xFFFF registers
 | 
				
			||||||
                    self.write_byte(0xFF0F, req.into());
 | 
					                        self.write_byte(0xFF0F, irq.into());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    // Disable all future interrupts
 | 
					                        // Disable all future interrupts
 | 
				
			||||||
                    self.set_ime(ImeState::Disabled);
 | 
					                        self.set_ime(ImeState::Disabled);
 | 
				
			||||||
                    Instruction::reset(self, address)
 | 
					                        Some(Instruction::reset(self, vector))
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    None => None,
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                None => Cycle::new(0), // NO Interrupts were enabled and / or requested
 | 
					            }
 | 
				
			||||||
            };
 | 
					            _ => None,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Copy, Clone)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
enum State {
 | 
					enum State {
 | 
				
			||||||
    Execute,
 | 
					    Execute,
 | 
				
			||||||
    // Halt,
 | 
					    Halt(HaltKind),
 | 
				
			||||||
    // Stop,
 | 
					    // Stop,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for State {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Execute
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    pub(crate) fn set_register(&mut self, register: Register, value: u8) {
 | 
					    pub(crate) fn set_register(&mut self, register: Register, value: u8) {
 | 
				
			||||||
        use Register::*;
 | 
					        use Register::*;
 | 
				
			||||||
@@ -292,7 +250,6 @@ impl Cpu {
 | 
				
			|||||||
            E => self.reg.e = value,
 | 
					            E => self.reg.e = value,
 | 
				
			||||||
            H => self.reg.h = value,
 | 
					            H => self.reg.h = value,
 | 
				
			||||||
            L => self.reg.l = value,
 | 
					            L => self.reg.l = value,
 | 
				
			||||||
            Flag => self.flags = value.into(),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -307,7 +264,6 @@ impl Cpu {
 | 
				
			|||||||
            E => self.reg.e,
 | 
					            E => self.reg.e,
 | 
				
			||||||
            H => self.reg.h,
 | 
					            H => self.reg.h,
 | 
				
			||||||
            L => self.reg.l,
 | 
					            L => self.reg.l,
 | 
				
			||||||
            Flag => self.flags.into(),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -356,13 +312,20 @@ impl Cpu {
 | 
				
			|||||||
        &self.flags
 | 
					        &self.flags
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn update_flags(&mut self, z: bool, n: bool, h: bool, c: bool) {
 | 
				
			||||||
 | 
					        self.flags.set_z(z);
 | 
				
			||||||
 | 
					        self.flags.set_n(n);
 | 
				
			||||||
 | 
					        self.flags.set_h(h);
 | 
				
			||||||
 | 
					        self.flags.set_c(c);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub(crate) fn set_flags(&mut self, flags: Flags) {
 | 
					    pub(crate) fn set_flags(&mut self, flags: Flags) {
 | 
				
			||||||
        self.flags = flags;
 | 
					        self.flags = flags;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Cpu {
 | 
					impl Cpu {
 | 
				
			||||||
    fn _debug_log(&self, mut w: impl std::io::Write, instr: &Instruction) -> std::io::Result<()> {
 | 
					    fn _print_debug(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
 | 
				
			||||||
        write!(w, "A: {:02X} ", self.reg.a)?;
 | 
					        write!(w, "A: {:02X} ", self.reg.a)?;
 | 
				
			||||||
        write!(w, "F: {:02X} ", u8::from(self.flags))?;
 | 
					        write!(w, "F: {:02X} ", u8::from(self.flags))?;
 | 
				
			||||||
        write!(w, "B: {:02X} ", self.reg.b)?;
 | 
					        write!(w, "B: {:02X} ", self.reg.b)?;
 | 
				
			||||||
@@ -376,31 +339,40 @@ impl Cpu {
 | 
				
			|||||||
        write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
					        write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
				
			||||||
        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
					        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
				
			||||||
        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
					        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
				
			||||||
        write!(w, "{:02X}) ", self.read_byte(self.reg.pc + 3))?;
 | 
					        write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?;
 | 
				
			||||||
        writeln!(w, "| {:?}", instr)?;
 | 
					        writeln!(w, "| {:?}", self._dbg_instr())?;
 | 
				
			||||||
        w.flush()
 | 
					        w.flush()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn _log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> {
 | 
					    fn _print_logs(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
 | 
				
			||||||
        write!(writer, "A: {:02X} ", self.reg.a)?;
 | 
					        write!(w, "A: {:02X} ", self.reg.a)?;
 | 
				
			||||||
        write!(writer, "F: {:02X} ", u8::from(self.flags))?;
 | 
					        write!(w, "F: {:02X} ", u8::from(self.flags))?;
 | 
				
			||||||
        write!(writer, "B: {:02X} ", self.reg.b)?;
 | 
					        write!(w, "B: {:02X} ", self.reg.b)?;
 | 
				
			||||||
        write!(writer, "C: {:02X} ", self.reg.c)?;
 | 
					        write!(w, "C: {:02X} ", self.reg.c)?;
 | 
				
			||||||
        write!(writer, "D: {:02X} ", self.reg.d)?;
 | 
					        write!(w, "D: {:02X} ", self.reg.d)?;
 | 
				
			||||||
        write!(writer, "E: {:02X} ", self.reg.e)?;
 | 
					        write!(w, "E: {:02X} ", self.reg.e)?;
 | 
				
			||||||
        write!(writer, "H: {:02X} ", self.reg.h)?;
 | 
					        write!(w, "H: {:02X} ", self.reg.h)?;
 | 
				
			||||||
        write!(writer, "L: {:02X} ", self.reg.l)?;
 | 
					        write!(w, "L: {:02X} ", self.reg.l)?;
 | 
				
			||||||
        write!(writer, "SP: {:04X} ", self.reg.sp)?;
 | 
					        write!(w, "SP: {:04X} ", self.reg.sp)?;
 | 
				
			||||||
        write!(writer, "PC: 00:{:04X} ", self.reg.pc)?;
 | 
					        write!(w, "PC: 00:{:04X} ", self.reg.pc)?;
 | 
				
			||||||
        write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
					        write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
 | 
				
			||||||
        write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
					        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
 | 
				
			||||||
        write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
					        write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
 | 
				
			||||||
        writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?;
 | 
					        writeln!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?;
 | 
				
			||||||
        writer.flush()
 | 
					        w.flush()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn _dbg_instr(&self) -> Instruction {
 | 
				
			||||||
 | 
					        let byte = self.read_byte(self.reg.pc);
 | 
				
			||||||
 | 
					        if byte == 0xCB {
 | 
				
			||||||
 | 
					            Instruction::decode(self.read_byte(self.reg.pc + 1), true)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            Instruction::decode(byte, false)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Copy, Clone)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub(crate) enum Register {
 | 
					pub(crate) enum Register {
 | 
				
			||||||
    A,
 | 
					    A,
 | 
				
			||||||
    B,
 | 
					    B,
 | 
				
			||||||
@@ -409,10 +381,9 @@ pub(crate) enum Register {
 | 
				
			|||||||
    E,
 | 
					    E,
 | 
				
			||||||
    H,
 | 
					    H,
 | 
				
			||||||
    L,
 | 
					    L,
 | 
				
			||||||
    Flag,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Copy, Clone)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub(crate) enum RegisterPair {
 | 
					pub(crate) enum RegisterPair {
 | 
				
			||||||
    AF,
 | 
					    AF,
 | 
				
			||||||
    BC,
 | 
					    BC,
 | 
				
			||||||
@@ -422,7 +393,7 @@ pub(crate) enum RegisterPair {
 | 
				
			|||||||
    PC,
 | 
					    PC,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Copy, Clone, Default)]
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
struct Registers {
 | 
					struct Registers {
 | 
				
			||||||
    a: u8,
 | 
					    a: u8,
 | 
				
			||||||
    b: u8,
 | 
					    b: u8,
 | 
				
			||||||
@@ -460,12 +431,6 @@ impl Clone for Flags {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Flags {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Display for Flags {
 | 
					impl Display for Flags {
 | 
				
			||||||
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
 | 
					    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
 | 
				
			||||||
        if self.z() {
 | 
					        if self.z() {
 | 
				
			||||||
@@ -507,7 +472,7 @@ impl From<u8> for Flags {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub(crate) enum HaltState {
 | 
					pub(crate) enum HaltKind {
 | 
				
			||||||
    ImeEnabled,
 | 
					    ImeEnabled,
 | 
				
			||||||
    NonePending,
 | 
					    NonePending,
 | 
				
			||||||
    SomePending,
 | 
					    SomePending,
 | 
				
			||||||
@@ -516,13 +481,43 @@ pub(crate) enum HaltState {
 | 
				
			|||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub(crate) enum ImeState {
 | 
					pub(crate) enum ImeState {
 | 
				
			||||||
    Disabled,
 | 
					    Disabled,
 | 
				
			||||||
 | 
					    EiExecuted,
 | 
				
			||||||
    Pending,
 | 
					    Pending,
 | 
				
			||||||
    PendingEnd,
 | 
					 | 
				
			||||||
    Enabled,
 | 
					    Enabled,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ImeState {
 | 
					pub(crate) mod dbg {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    use super::{Cpu, ImeState, RegisterPair};
 | 
				
			||||||
        Self::Disabled
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn flags(cpu: &Cpu) -> u8 {
 | 
				
			||||||
 | 
					        cpu.flags.into()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn af(cpu: &Cpu) -> u16 {
 | 
				
			||||||
 | 
					        cpu.register_pair(RegisterPair::AF)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn bc(cpu: &Cpu) -> u16 {
 | 
				
			||||||
 | 
					        cpu.register_pair(RegisterPair::BC)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn de(cpu: &Cpu) -> u16 {
 | 
				
			||||||
 | 
					        cpu.register_pair(RegisterPair::DE)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn hl(cpu: &Cpu) -> u16 {
 | 
				
			||||||
 | 
					        cpu.register_pair(RegisterPair::HL)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn sp(cpu: &Cpu) -> u16 {
 | 
				
			||||||
 | 
					        cpu.register_pair(RegisterPair::SP)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn pc(cpu: &Cpu) -> u16 {
 | 
				
			||||||
 | 
					        cpu.register_pair(RegisterPair::PC)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub(crate) fn ime(cpu: &Cpu) -> bool {
 | 
				
			||||||
 | 
					        matches!(cpu.ime, ImeState::Enabled)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										187
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										187
									
								
								src/emu.rs
									
									
									
									
									
								
							@@ -1,60 +1,163 @@
 | 
				
			|||||||
use crate::cpu::Cpu as SM83;
 | 
					use crate::apu::gen::SampleProducer;
 | 
				
			||||||
use crate::instruction::Cycle;
 | 
					use crate::bus::BOOT_SIZE;
 | 
				
			||||||
use crate::joypad;
 | 
					use crate::cartridge::Cartridge;
 | 
				
			||||||
use crate::ppu::Ppu;
 | 
					use crate::cpu::Cpu;
 | 
				
			||||||
use anyhow::Result;
 | 
					use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
 | 
				
			||||||
 | 
					use clap::crate_name;
 | 
				
			||||||
use gilrs::Gilrs;
 | 
					use gilrs::Gilrs;
 | 
				
			||||||
 | 
					use std::fs::File;
 | 
				
			||||||
 | 
					use std::io::{Read, Write};
 | 
				
			||||||
 | 
					use std::path::{Path, PathBuf};
 | 
				
			||||||
use std::time::Duration;
 | 
					use std::time::Duration;
 | 
				
			||||||
use winit_input_helper::WinitInputHelper;
 | 
					use thiserror::Error;
 | 
				
			||||||
 | 
					use winit::event::KeyboardInput;
 | 
				
			||||||
 | 
					use winit::event_loop::ControlFlow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
 | 
					pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
 | 
				
			||||||
pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines
 | 
					pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines
 | 
				
			||||||
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
 | 
					pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
 | 
				
			||||||
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
 | 
					const DEFAULT_TITLE: &str = "Game Boy Screen";
 | 
				
			||||||
const GAMEPAD_ENABLED: bool = false;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> {
 | 
					#[inline]
 | 
				
			||||||
    let mut cpu = match boot_path {
 | 
					pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) {
 | 
				
			||||||
        Some(path) => SM83::boot_new(path)?,
 | 
					    run(cpu, gamepad, key, CYCLES_IN_FRAME)
 | 
				
			||||||
        None => SM83::new(),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    eprintln!("Initialized GB Emulator");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    cpu.load_cartridge(rom_path)?;
 | 
					 | 
				
			||||||
    Ok(cpu)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn rom_title(game_boy: &SM83) -> &str {
 | 
					#[inline]
 | 
				
			||||||
    game_boy.rom_title().unwrap_or(DEFAULT_TITLE)
 | 
					pub fn run(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput, cycles: Cycle) {
 | 
				
			||||||
}
 | 
					    let mut elapsed = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn run(
 | 
					    if let Some(event) = gamepad.next_event() {
 | 
				
			||||||
    game_boy: &mut SM83,
 | 
					        crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event);
 | 
				
			||||||
    gamepad: &mut Gilrs,
 | 
					 | 
				
			||||||
    input: &WinitInputHelper,
 | 
					 | 
				
			||||||
    pending: Cycle,
 | 
					 | 
				
			||||||
) -> Cycle {
 | 
					 | 
				
			||||||
    let mut elapsed = Cycle::new(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while elapsed < pending {
 | 
					 | 
				
			||||||
        elapsed += run_unsynced(game_boy, gamepad, input);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    elapsed
 | 
					    while elapsed < cycles {
 | 
				
			||||||
 | 
					        elapsed += cpu.step();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle {
 | 
					pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) {
 | 
				
			||||||
    if GAMEPAD_ENABLED {
 | 
					    write_save(cpu);
 | 
				
			||||||
        if let Some(event) = gamepad.next_event() {
 | 
					    *control_flow = ControlFlow::Exit;
 | 
				
			||||||
            joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
 | 
				
			||||||
 | 
					    use crate::ppu::Device;
 | 
				
			||||||
 | 
					    cpu.bus.ppu.frame_buf.get(Device::Host)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
 | 
				
			||||||
 | 
					    Ok(Cpu::new(read_boot(path)?))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> {
 | 
				
			||||||
 | 
					    cpu.bus.cart = Some(Cartridge::new(std::fs::read(path.as_ref())?));
 | 
				
			||||||
 | 
					    Ok(())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn set_audio_prod(cpu: &mut Cpu, prod: SampleProducer<f32>) {
 | 
				
			||||||
 | 
					    cpu.bus.apu.prod = Some(prod);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn rom_title(cpu: &Cpu) -> &str {
 | 
				
			||||||
 | 
					    cpu.bus
 | 
				
			||||||
 | 
					        .cart
 | 
				
			||||||
 | 
					        .as_ref()
 | 
				
			||||||
 | 
					        .and_then(|c| c.title.as_deref())
 | 
				
			||||||
 | 
					        .unwrap_or(DEFAULT_TITLE)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn write_save(cpu: &Cpu) {
 | 
				
			||||||
 | 
					    match cpu.bus.cart.as_ref() {
 | 
				
			||||||
 | 
					        Some(cart) => match write_save_to_file(cart) {
 | 
				
			||||||
 | 
					            Ok(path) => tracing::info!("Wrote to save at {:?}", path),
 | 
				
			||||||
 | 
					            Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err),
 | 
				
			||||||
 | 
					            Err(SaveError::DiffSize) => unreachable!(),
 | 
				
			||||||
 | 
					            Err(SaveError::Io(err)) => tracing::error!("{:?}", err),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None => tracing::error!("No cartridge is currently present"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn load_save(cpu: &mut Cpu) {
 | 
				
			||||||
 | 
					    match cpu.bus.cart.as_mut() {
 | 
				
			||||||
 | 
					        Some(cart) => match read_save_from_file(cart) {
 | 
				
			||||||
 | 
					            Ok(path) => tracing::info!("Loaded save from {:?}", path),
 | 
				
			||||||
 | 
					            Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {}", err),
 | 
				
			||||||
 | 
					            Err(err @ SaveError::DiffSize) => tracing::error!("Unable to load save: {}", err),
 | 
				
			||||||
 | 
					            Err(SaveError::Io(err)) => match err.kind() {
 | 
				
			||||||
 | 
					                std::io::ErrorKind::NotFound => tracing::warn!("Save not found"),
 | 
				
			||||||
 | 
					                _ => tracing::error!("{:?}", err),
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None => tracing::error!("No cartridge is currently present"),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn write_save_to_file(cart: &Cartridge) -> Result<PathBuf, SaveError> {
 | 
				
			||||||
 | 
					    match cart.title.as_ref().zip(cart.ext_ram()) {
 | 
				
			||||||
 | 
					        Some((title, ram)) => {
 | 
				
			||||||
 | 
					            let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
 | 
				
			||||||
 | 
					            save_path.push(title);
 | 
				
			||||||
 | 
					            save_path.set_extension("sav");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let mut file = File::create(&save_path)?;
 | 
				
			||||||
 | 
					            file.write_all(ram)?;
 | 
				
			||||||
 | 
					            Ok(save_path)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        None => Err(SaveError::NotApplicable),
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
 | 
					 | 
				
			||||||
    game_boy.step()
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn draw(ppu: &Ppu, frame: &mut [u8]) {
 | 
					fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
 | 
				
			||||||
    ppu.copy_to_gui(frame);
 | 
					    match cart.title.clone().zip(cart.ext_ram_mut()) {
 | 
				
			||||||
 | 
					        Some((title, ext_ram)) => {
 | 
				
			||||||
 | 
					            let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
 | 
				
			||||||
 | 
					            save_path.push(title);
 | 
				
			||||||
 | 
					            save_path.set_extension("sav");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let mut file = File::open(&save_path)?;
 | 
				
			||||||
 | 
					            let mut memory = Vec::new();
 | 
				
			||||||
 | 
					            file.read_to_end(&mut memory)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if ext_ram.len() != memory.len() {
 | 
				
			||||||
 | 
					                return Err(SaveError::DiffSize);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            ext_ram.copy_from_slice(&memory);
 | 
				
			||||||
 | 
					            Ok(save_path)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        None => Err(SaveError::NotApplicable),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
 | 
				
			||||||
 | 
					    let mut buf = [0; BOOT_SIZE];
 | 
				
			||||||
 | 
					    let mut file = File::open(path.as_ref())?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file.read_exact(&mut buf)?;
 | 
				
			||||||
 | 
					    Ok(buf)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn data_path() -> Option<PathBuf> {
 | 
				
			||||||
 | 
					    match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
 | 
				
			||||||
 | 
					        Some(dirs) => {
 | 
				
			||||||
 | 
					            let data_local = dirs.data_local_dir();
 | 
				
			||||||
 | 
					            std::fs::create_dir_all(data_local).ok()?;
 | 
				
			||||||
 | 
					            Some(data_local.to_path_buf())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        None => None,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Error)]
 | 
				
			||||||
 | 
					pub enum SaveError {
 | 
				
			||||||
 | 
					    #[error("cartridge lacks title and/or external ram")]
 | 
				
			||||||
 | 
					    NotApplicable,
 | 
				
			||||||
 | 
					    #[error(transparent)]
 | 
				
			||||||
 | 
					    Io(#[from] std::io::Error),
 | 
				
			||||||
 | 
					    #[error("save file size differs from external ram")]
 | 
				
			||||||
 | 
					    DiffSize,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										717
									
								
								src/gui.rs
									
									
									
									
									
								
							
							
						
						
									
										717
									
								
								src/gui.rs
									
									
									
									
									
								
							@@ -1,321 +1,472 @@
 | 
				
			|||||||
use crate::bus::BusIo;
 | 
					use egui::{Context, TextureId};
 | 
				
			||||||
use crate::cpu::{Cpu as SM83, Register, RegisterPair};
 | 
					use egui_wgpu_backend::{wgpu, RenderPass, ScreenDescriptor};
 | 
				
			||||||
use egui::{ClippedMesh, FontDefinitions};
 | 
					use egui_winit_platform::Platform;
 | 
				
			||||||
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
 | 
					use wgpu::{
 | 
				
			||||||
use egui_winit_platform::{Platform, PlatformDescriptor};
 | 
					    Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
 | 
				
			||||||
use pixels::{wgpu, PixelsContext};
 | 
					    Surface, SurfaceCapabilities, SurfaceConfiguration, Texture, TextureView,
 | 
				
			||||||
use std::time::Instant;
 | 
					    TextureViewDescriptor,
 | 
				
			||||||
use wgpu::TextureFormat::Bgra8UnormSrgb;
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Boilerplate code from: https://github.com/parasyte/pixels/blob/0.3.0/examples/egui-winit/src/gui.rs
 | 
					use winit::dpi::PhysicalSize;
 | 
				
			||||||
 | 
					use winit::error::OsError;
 | 
				
			||||||
 | 
					use winit::event::{ElementState, Event, KeyboardInput};
 | 
				
			||||||
 | 
					use winit::event_loop::{ControlFlow, EventLoop};
 | 
				
			||||||
 | 
					use winit::window::Window;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Manages all state required for rendering egui over `Pixels`.
 | 
					use crate::cpu::Cpu;
 | 
				
			||||||
pub struct Egui {
 | 
					use crate::{emu, GB_HEIGHT, GB_WIDTH};
 | 
				
			||||||
    // State for egui.
 | 
					
 | 
				
			||||||
    start_time: Instant,
 | 
					const EGUI_DIMENSIONS: (usize, usize) = (1280, 720);
 | 
				
			||||||
 | 
					const FILTER_MODE: FilterMode = FilterMode::Nearest;
 | 
				
			||||||
 | 
					const WINDOW_TITLE: &str = "DMG-01 Emulator";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SCALE: f32 = 3.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Gui {
 | 
				
			||||||
 | 
					    pub should_quit: bool,
 | 
				
			||||||
 | 
					    pub title: String,
 | 
				
			||||||
 | 
					    pub mode: EmuMode,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window: Window,
 | 
				
			||||||
    platform: Platform,
 | 
					    platform: Platform,
 | 
				
			||||||
    screen_desc: ScreenDescriptor,
 | 
					
 | 
				
			||||||
 | 
					    surface: Surface,
 | 
				
			||||||
 | 
					    device: Device,
 | 
				
			||||||
 | 
					    queue: Queue,
 | 
				
			||||||
 | 
					    surface_config: SurfaceConfiguration,
 | 
				
			||||||
    render_pass: RenderPass,
 | 
					    render_pass: RenderPass,
 | 
				
			||||||
    paint_jobs: Vec<ClippedMesh>,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub config: Configuration,
 | 
					    texture: Texture,
 | 
				
			||||||
 | 
					    texture_size: Extent3d,
 | 
				
			||||||
    show_flags: bool,
 | 
					    texture_id: TextureId,
 | 
				
			||||||
    show_cpu_info: bool,
 | 
					 | 
				
			||||||
    show_registers: bool,
 | 
					 | 
				
			||||||
    show_int: bool,
 | 
					 | 
				
			||||||
    show_timer: bool,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Egui {
 | 
					impl Gui {
 | 
				
			||||||
    /// Create egui.
 | 
					    pub fn new<T>(title: String, event_loop: &EventLoop<T>, cpu: &Cpu) -> Self {
 | 
				
			||||||
    pub fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self {
 | 
					        use wgpu::InstanceDescriptor;
 | 
				
			||||||
        let platform = Platform::new(PlatformDescriptor {
 | 
					 | 
				
			||||||
            physical_width: width,
 | 
					 | 
				
			||||||
            physical_height: height,
 | 
					 | 
				
			||||||
            scale_factor,
 | 
					 | 
				
			||||||
            font_definitions: FontDefinitions::default(),
 | 
					 | 
				
			||||||
            style: Default::default(),
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let screen_desc = ScreenDescriptor {
 | 
					        let window = build_window(event_loop).expect("build window");
 | 
				
			||||||
            physical_width: width,
 | 
					
 | 
				
			||||||
            physical_height: height,
 | 
					        let instance = Instance::new(InstanceDescriptor::default());
 | 
				
			||||||
            scale_factor: scale_factor as f32,
 | 
					        let surface = unsafe {
 | 
				
			||||||
 | 
					            instance
 | 
				
			||||||
 | 
					                .create_surface(&window)
 | 
				
			||||||
 | 
					                .expect("create wgpu instance surface")
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let render_pass = RenderPass::new(&context.device, Bgra8UnormSrgb);
 | 
					        let adapter = request_adapter(&instance, &surface).expect("request adaptor");
 | 
				
			||||||
 | 
					        let (device, queue) = request_device(&adapter).expect("request device");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let capabilities = surface.get_capabilities(&adapter);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let surface_config = surface_config(&window, capabilities);
 | 
				
			||||||
 | 
					        surface.configure(&device, &surface_config);
 | 
				
			||||||
 | 
					        let platform = platform(&window);
 | 
				
			||||||
 | 
					        let mut render_pass = RenderPass::new(&device, surface_config.format, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let texture_size = texture_size();
 | 
				
			||||||
 | 
					        let texture = create_texture(&device, texture_size);
 | 
				
			||||||
 | 
					        write_to_texture(&queue, &texture, emu::pixel_buf(cpu), texture_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let view = texture.create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					        let texture_id = expose_texture_to_egui(&mut render_pass, &device, &view);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            start_time: Instant::now(),
 | 
					            should_quit: Default::default(),
 | 
				
			||||||
 | 
					            title,
 | 
				
			||||||
 | 
					            mode: EmuMode::Running,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            window,
 | 
				
			||||||
            platform,
 | 
					            platform,
 | 
				
			||||||
            screen_desc,
 | 
					
 | 
				
			||||||
 | 
					            surface,
 | 
				
			||||||
 | 
					            device,
 | 
				
			||||||
 | 
					            queue,
 | 
				
			||||||
 | 
					            surface_config,
 | 
				
			||||||
            render_pass,
 | 
					            render_pass,
 | 
				
			||||||
            paint_jobs: Vec::new(),
 | 
					
 | 
				
			||||||
            config: Default::default(),
 | 
					            texture,
 | 
				
			||||||
            show_flags: false,
 | 
					            texture_size,
 | 
				
			||||||
            show_cpu_info: false,
 | 
					            texture_id,
 | 
				
			||||||
            show_registers: false,
 | 
					 | 
				
			||||||
            show_int: false,
 | 
					 | 
				
			||||||
            show_timer: false,
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Handle input events from the window manager.
 | 
					    pub fn maybe_quit(&self, cpu: &Cpu, control_flow: &mut ControlFlow) {
 | 
				
			||||||
    pub fn handle_event(&mut self, event: &winit::event::Event<'_, ()>) {
 | 
					        if self.should_quit {
 | 
				
			||||||
 | 
					            emu::save_and_exit(cpu, control_flow);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn request_redraw(&self) {
 | 
				
			||||||
 | 
					        self.window.request_redraw();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn handle_event<T>(&mut self, event: &Event<T>) {
 | 
				
			||||||
        self.platform.handle_event(event);
 | 
					        self.platform.handle_event(event);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Resize egui.
 | 
					    pub fn update_time(&mut self, elapsed_seconds: f64) {
 | 
				
			||||||
    pub fn resize(&mut self, width: u32, height: u32) {
 | 
					        self.platform.update_time(elapsed_seconds);
 | 
				
			||||||
        self.screen_desc.physical_width = width;
 | 
					 | 
				
			||||||
        self.screen_desc.physical_height = height;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Update scaling factor.
 | 
					    pub fn resize(&mut self, size: PhysicalSize<u32>) {
 | 
				
			||||||
    pub fn scale_factor(&mut self, scale_factor: f64) {
 | 
					        // See: https://github.com/rust-windowing/winit/issues/208
 | 
				
			||||||
        self.screen_desc.scale_factor = scale_factor as f32;
 | 
					        if size.width > 0 && size.height > 0 {
 | 
				
			||||||
 | 
					            self.surface_config.width = size.width;
 | 
				
			||||||
 | 
					            self.surface_config.height = size.height;
 | 
				
			||||||
 | 
					            self.surface.configure(&self.device, &self.surface_config);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Prepare egui.
 | 
					    pub fn paint(&mut self, cpu: &Cpu) {
 | 
				
			||||||
    pub fn prepare(&mut self, game_boy: &SM83) {
 | 
					        use wgpu::{Color, SurfaceError};
 | 
				
			||||||
        self.platform
 | 
					 | 
				
			||||||
            .update_time(self.start_time.elapsed().as_secs_f64());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Begin the egui frame.
 | 
					        let data = emu::pixel_buf(cpu);
 | 
				
			||||||
 | 
					        write_to_texture(&self.queue, &self.texture, data, self.texture_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output_frame = match self.surface.get_current_texture() {
 | 
				
			||||||
 | 
					            Ok(frame) => frame,
 | 
				
			||||||
 | 
					            Err(SurfaceError::Outdated) => return, // Occurs on minimization on Windows
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                eprintln!("Dropped frame with error: {}", e);
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let output_view = output_frame
 | 
				
			||||||
 | 
					            .texture
 | 
				
			||||||
 | 
					            .create_view(&TextureViewDescriptor::default());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Begin to draw Egui components
 | 
				
			||||||
        self.platform.begin_frame();
 | 
					        self.platform.begin_frame();
 | 
				
			||||||
 | 
					        self.draw_egui(cpu, &self.platform.context(), self.texture_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Draw the demo application.
 | 
					        // End the UI frame. We could now handle the output and draw the UI with the backend.
 | 
				
			||||||
        self.ui(&self.platform.context(), game_boy);
 | 
					        let full_output = self.platform.end_frame(Some(&self.window));
 | 
				
			||||||
 | 
					        let paint_jobs = self.platform.context().tessellate(full_output.shapes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // End the egui frame and create all paint jobs to prepare for rendering.
 | 
					        let mut encoder = create_command_encoder(&self.device);
 | 
				
			||||||
        let (_output, paint_commands) = self.platform.end_frame();
 | 
					        let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config);
 | 
				
			||||||
        self.paint_jobs = self.platform.context().tessellate(paint_commands);
 | 
					        let tdelta = full_output.textures_delta;
 | 
				
			||||||
    }
 | 
					        // Upload all resources for the GPU.
 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Create the UI using egui.
 | 
					 | 
				
			||||||
    fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &SM83) {
 | 
					 | 
				
			||||||
        egui::TopPanel::top("menubar_container").show(ctx, |ui| {
 | 
					 | 
				
			||||||
            egui::menu::bar(ui, |ui| {
 | 
					 | 
				
			||||||
                egui::menu::menu(ui, "File", |ui| {
 | 
					 | 
				
			||||||
                    if ui.button("Configuration").clicked() {
 | 
					 | 
				
			||||||
                        self.config.show = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                egui::menu::menu(ui, "Status", |ui| {
 | 
					 | 
				
			||||||
                    if ui.button("Flags").clicked() {
 | 
					 | 
				
			||||||
                        self.show_flags = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if ui.button("CPU Information").clicked() {
 | 
					 | 
				
			||||||
                        self.show_cpu_info = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if ui.button("Registers").clicked() {
 | 
					 | 
				
			||||||
                        self.show_registers = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if ui.button("Interrupts").clicked() {
 | 
					 | 
				
			||||||
                        self.show_int = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    if ui.button("Timer").clicked() {
 | 
					 | 
				
			||||||
                        self.show_timer = true;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        egui::Window::new("Cpu Flags")
 | 
					 | 
				
			||||||
            .open(&mut self.show_flags)
 | 
					 | 
				
			||||||
            .show(ctx, |ui| {
 | 
					 | 
				
			||||||
                let flags = game_boy.flags();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label(flags.z(), "Zero");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label(flags.n(), "Negative");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label(flags.h(), "Half-Carry");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label(flags.c(), "Carry");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        egui::Window::new("Timer")
 | 
					 | 
				
			||||||
            .open(&mut self.show_timer)
 | 
					 | 
				
			||||||
            .show(ctx, |ui| {
 | 
					 | 
				
			||||||
                let timer = game_boy.timer();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("DIV");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", timer.divider));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("TIMA");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", timer.counter));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("TMA");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", timer.modulo));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("TAC");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:?}", timer.ctrl));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        egui::Window::new("Registers")
 | 
					 | 
				
			||||||
            .open(&mut self.show_registers)
 | 
					 | 
				
			||||||
            .show(ctx, |ui| {
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("A");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::A)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    ui.label("F");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::Flag)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("B");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::B)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    ui.label("C");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::C)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("D");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::D)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    ui.label("E");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::E)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("H");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::H)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    ui.label("L");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#04X}", game_boy.register(Register::L)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("AF");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::AF)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("BC");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::BC)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("DE");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::DE)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("HL");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::HL)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        egui::Window::new("Cpu Information")
 | 
					 | 
				
			||||||
            .open(&mut self.show_cpu_info)
 | 
					 | 
				
			||||||
            .show(ctx, |ui| {
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("PC");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::PC)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("SP");
 | 
					 | 
				
			||||||
                    ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::SP)));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("IME");
 | 
					 | 
				
			||||||
                    ui.label(format!("{:?}", game_boy.ime()));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    ui.label("HALT");
 | 
					 | 
				
			||||||
                    ui.label(format!("{:?}", game_boy.halted()));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        egui::Window::new("IRQ Information")
 | 
					 | 
				
			||||||
            .open(&mut self.show_int)
 | 
					 | 
				
			||||||
            .show(ctx, |ui| {
 | 
					 | 
				
			||||||
                let req = game_boy.read_byte(0xFF0F);
 | 
					 | 
				
			||||||
                let enabled = game_boy.read_byte(0xFFFF);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.heading("Interrupt Requests");
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label(req & 0x01 == 0x01, "VBLANK");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((req >> 1) & 0x01 == 0x01, "LCD STAT");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((req >> 2) & 0x01 == 0x01, "TIMER");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((req >> 3) & 0x01 == 0x01, "SERIAL");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((req >> 4) & 0x01 == 0x01, "JOYPAD");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                ui.heading("Interrupt Enable");
 | 
					 | 
				
			||||||
                ui.horizontal(|ui| {
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label(enabled & 0x01 == 0x01, "VBLANK");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((enabled >> 1) & 0x01 == 0x01, "LCD STAT");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((enabled >> 2) & 0x01 == 0x01, "TIMER");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((enabled >> 3) & 0x01 == 0x01, "SERIAL");
 | 
					 | 
				
			||||||
                    let _ = ui.selectable_label((enabled >> 4) & 0x01 == 0x01, "JOYPAD");
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        egui::Window::new("Configuration")
 | 
					 | 
				
			||||||
            .open(&mut self.config.show)
 | 
					 | 
				
			||||||
            .show(ctx, |_ui| {});
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Render egui.
 | 
					 | 
				
			||||||
    pub fn render(
 | 
					 | 
				
			||||||
        &mut self,
 | 
					 | 
				
			||||||
        encoder: &mut wgpu::CommandEncoder,
 | 
					 | 
				
			||||||
        render_target: &wgpu::TextureView,
 | 
					 | 
				
			||||||
        context: &PixelsContext,
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        // Upload all resources to the GPU.
 | 
					 | 
				
			||||||
        self.render_pass.update_texture(
 | 
					 | 
				
			||||||
            &context.device,
 | 
					 | 
				
			||||||
            &context.queue,
 | 
					 | 
				
			||||||
            &self.platform.context().texture(),
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
        self.render_pass
 | 
					        self.render_pass
 | 
				
			||||||
            .update_user_textures(&context.device, &context.queue);
 | 
					            .add_textures(&self.device, &self.queue, &tdelta)
 | 
				
			||||||
        self.render_pass.update_buffers(
 | 
					            .expect("add texture ok");
 | 
				
			||||||
            &context.device,
 | 
					        self.render_pass
 | 
				
			||||||
            &context.queue,
 | 
					            .update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
 | 
				
			||||||
            &self.paint_jobs,
 | 
					 | 
				
			||||||
            &self.screen_desc,
 | 
					 | 
				
			||||||
        );
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Record all render passes.
 | 
					        // Record all render passes.
 | 
				
			||||||
        self.render_pass.execute(
 | 
					        self.render_pass
 | 
				
			||||||
            encoder,
 | 
					            .execute(
 | 
				
			||||||
            render_target,
 | 
					                &mut encoder,
 | 
				
			||||||
            &self.paint_jobs,
 | 
					                &output_view,
 | 
				
			||||||
            &self.screen_desc,
 | 
					                &paint_jobs,
 | 
				
			||||||
            None,
 | 
					                &screen_descriptor,
 | 
				
			||||||
        );
 | 
					                Some(Color::BLACK),
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .expect("execute render pass");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Submit the commands.
 | 
				
			||||||
 | 
					        self.queue.submit(std::iter::once(encoder.finish()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Redraw egui
 | 
				
			||||||
 | 
					        output_frame.present();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.render_pass
 | 
				
			||||||
 | 
					            .remove_textures(tdelta)
 | 
				
			||||||
 | 
					            .expect("remove texture delta");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    pub fn draw_egui(&mut self, cpu: &Cpu, ctx: &Context, texture_id: TextureId) {
 | 
				
			||||||
 | 
					        use crate::{cpu, instruction, ppu};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response {
 | 
				
			||||||
 | 
					            ui.add(egui::TextEdit::multiline(&mut text).code_editor())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
 | 
				
			||||||
 | 
					            ui.menu_button("File", |ui| {
 | 
				
			||||||
 | 
					                if ui.button("Quit").clicked() {
 | 
				
			||||||
 | 
					                    self.should_quit = true;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            egui::Window::new(&self.title).show(ctx, |ui| {
 | 
				
			||||||
 | 
					                ui.image(
 | 
				
			||||||
 | 
					                    texture_id,
 | 
				
			||||||
 | 
					                    [GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            egui::Window::new("Disassembly").show(ctx, |ui| {
 | 
				
			||||||
 | 
					                selectable_text(ui, &instruction::dbg::tmp_disasm(cpu, 20));
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            egui::Window::new("Settings").show(ctx, |ui| {
 | 
				
			||||||
 | 
					                egui::ComboBox::from_label("Emulation Mode")
 | 
				
			||||||
 | 
					                    .selected_text(format!("{:?}", self.mode))
 | 
				
			||||||
 | 
					                    .show_ui(ui, |ui| {
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Running, "Running");
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Stopped, "Stopped");
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::StepFrame, "Step Frame");
 | 
				
			||||||
 | 
					                        ui.selectable_value(&mut self.mode, EmuMode::Step, "Step");
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            egui::Window::new("GB Info").show(ctx, |ui| {
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    ui.vertical(|ui| {
 | 
				
			||||||
 | 
					                        ui.heading("CPU");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ui.monospace(format!("AF: {:#06X}", cpu::dbg::af(cpu)));
 | 
				
			||||||
 | 
					                        ui.monospace(format!("BC: {:#06X}", cpu::dbg::bc(cpu)));
 | 
				
			||||||
 | 
					                        ui.monospace(format!("DE: {:#06X}", cpu::dbg::de(cpu)));
 | 
				
			||||||
 | 
					                        ui.monospace(format!("HL: {:#06X}", cpu::dbg::hl(cpu)));
 | 
				
			||||||
 | 
					                        ui.add_space(10.0);
 | 
				
			||||||
 | 
					                        ui.monospace(format!("SP: {:#06X}", cpu::dbg::sp(cpu)));
 | 
				
			||||||
 | 
					                        ui.monospace(format!("PC: {:#06X}", cpu::dbg::pc(cpu)));
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ui.vertical(|ui| {
 | 
				
			||||||
 | 
					                        let ppu = &cpu.bus.ppu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ui.heading("PPU");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ui.monospace(format!("LY: {}", ppu::dbg::ly(ppu)));
 | 
				
			||||||
 | 
					                        ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                            ui.monospace(format!("SCX: {}", ppu::dbg::scx(ppu)));
 | 
				
			||||||
 | 
					                            ui.monospace(format!("SCY: {}", ppu::dbg::scy(ppu)));
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					                        ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                            ui.monospace(format!("WX: {}", ppu::dbg::wx(ppu)));
 | 
				
			||||||
 | 
					                            ui.monospace(format!("WY: {}", ppu::dbg::wy(ppu)));
 | 
				
			||||||
 | 
					                        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        ui.monospace(format!(
 | 
				
			||||||
 | 
					                            "Mode: {:?} {}",
 | 
				
			||||||
 | 
					                            ppu::dbg::mode(ppu),
 | 
				
			||||||
 | 
					                            ppu::dbg::dot(ppu)
 | 
				
			||||||
 | 
					                        ))
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.add_space(10.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let _ = ui.selectable_label(cpu::dbg::ime(cpu), "IME");
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    let irq = cpu.int_request();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ui.label("IRQ:");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(irq & 0b01 == 0x01, "VBlank");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(irq >> 1 & 0x01 == 0x01, "LCD STAT");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(irq >> 2 & 0x01 == 0x01, "Timer");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(irq >> 3 & 0x01 == 0x01, "Serial");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(irq >> 4 & 0x01 == 0x01, "Joypad");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    let ie = cpu.int_enable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // TODO: Reimplement this
 | 
				
			||||||
 | 
					                    // let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R');
 | 
				
			||||||
 | 
					                    // let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E');
 | 
				
			||||||
 | 
					                    // let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    ui.label("IE:");
 | 
				
			||||||
 | 
					                    // ui.add_space(q_len - (e_len - r_len));
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(ie >> 3 & 0x01 == 0x01, "Serial");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label(ie >> 4 & 0x01 == 0x01, "Joypad");
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.add_space(10.0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                ui.horizontal(|ui| {
 | 
				
			||||||
 | 
					                    let flags = cpu::dbg::flags(cpu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label((flags >> 7 & 0x01) == 0x01, "Zero");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label((flags >> 6 & 0x01) == 0x01, "Negative");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label((flags >> 5 & 0x01) == 0x01, "Half-Carry");
 | 
				
			||||||
 | 
					                    let _ = ui.selectable_label((flags >> 4 & 0x01) == 0x01, "Carry");
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct Configuration {
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
    /// Show Configuration egui menu
 | 
					pub enum EmuMode {
 | 
				
			||||||
    show: bool,
 | 
					    Running,
 | 
				
			||||||
 | 
					    StepFrame,
 | 
				
			||||||
 | 
					    Stopped,
 | 
				
			||||||
 | 
					    Step,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Configuration {
 | 
					/// To avoid using an [Option<KeyboardInput>] to keep track of user input from winit,
 | 
				
			||||||
    fn default() -> Self {
 | 
					/// we can use a "default" value. However, in order for this to work the chosen "default"
 | 
				
			||||||
        Self { show: false }
 | 
					/// value must be an **unused** key, so that it is ignored by the emulator.
 | 
				
			||||||
 | 
					pub fn unused_key() -> KeyboardInput {
 | 
				
			||||||
 | 
					    #![allow(deprecated)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    KeyboardInput {
 | 
				
			||||||
 | 
					        scancode: Default::default(),
 | 
				
			||||||
 | 
					        state: ElementState::Released,
 | 
				
			||||||
 | 
					        virtual_keycode: Default::default(),
 | 
				
			||||||
 | 
					        modifiers: Default::default(), // this argument is deprecated
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
 | 
				
			||||||
 | 
					    use winit::window::WindowBuilder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    WindowBuilder::new()
 | 
				
			||||||
 | 
					        .with_decorations(true)
 | 
				
			||||||
 | 
					        .with_resizable(true)
 | 
				
			||||||
 | 
					        .with_transparent(false)
 | 
				
			||||||
 | 
					        .with_title(WINDOW_TITLE)
 | 
				
			||||||
 | 
					        .with_inner_size(PhysicalSize {
 | 
				
			||||||
 | 
					            width: EGUI_DIMENSIONS.0 as f32,
 | 
				
			||||||
 | 
					            height: EGUI_DIMENSIONS.1 as f32,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        .build(event_loop)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
 | 
				
			||||||
 | 
					    use wgpu::{PowerPreference, RequestAdapterOptions};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
 | 
				
			||||||
 | 
					        power_preference: PowerPreference::HighPerformance,
 | 
				
			||||||
 | 
					        compatible_surface: Some(surface),
 | 
				
			||||||
 | 
					        force_fallback_adapter: false, // TODO: What do I want to do with this?
 | 
				
			||||||
 | 
					    }))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
 | 
				
			||||||
 | 
					    use wgpu::{DeviceDescriptor, Features, Limits};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pollster::block_on(adapter.request_device(
 | 
				
			||||||
 | 
					        &DeviceDescriptor {
 | 
				
			||||||
 | 
					            features: Features::default(),
 | 
				
			||||||
 | 
					            limits: Limits::default(),
 | 
				
			||||||
 | 
					            label: None,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        None,
 | 
				
			||||||
 | 
					    ))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn surface_config(window: &Window, capabilities: SurfaceCapabilities) -> SurfaceConfiguration {
 | 
				
			||||||
 | 
					    use egui_wgpu_backend::wgpu::{PresentMode, TextureFormat, TextureUsages};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let size = window.inner_size();
 | 
				
			||||||
 | 
					    SurfaceConfiguration {
 | 
				
			||||||
 | 
					        usage: TextureUsages::RENDER_ATTACHMENT,
 | 
				
			||||||
 | 
					        format: capabilities.formats[0],
 | 
				
			||||||
 | 
					        width: size.width as u32,
 | 
				
			||||||
 | 
					        height: size.height as u32,
 | 
				
			||||||
 | 
					        present_mode: PresentMode::Fifo,
 | 
				
			||||||
 | 
					        alpha_mode: capabilities.alpha_modes[0],
 | 
				
			||||||
 | 
					        view_formats: vec![TextureFormat::Rgba8UnormSrgb],
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn platform(window: &Window) -> Platform {
 | 
				
			||||||
 | 
					    use egui::FontDefinitions;
 | 
				
			||||||
 | 
					    use egui_winit_platform::PlatformDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let size = window.inner_size();
 | 
				
			||||||
 | 
					    Platform::new(PlatformDescriptor {
 | 
				
			||||||
 | 
					        physical_width: size.width as u32,
 | 
				
			||||||
 | 
					        physical_height: size.height as u32,
 | 
				
			||||||
 | 
					        scale_factor: window.scale_factor(),
 | 
				
			||||||
 | 
					        font_definitions: FontDefinitions::default(),
 | 
				
			||||||
 | 
					        ..Default::default()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn texture_size() -> Extent3d {
 | 
				
			||||||
 | 
					    Extent3d {
 | 
				
			||||||
 | 
					        width: GB_WIDTH as u32,
 | 
				
			||||||
 | 
					        height: GB_HEIGHT as u32,
 | 
				
			||||||
 | 
					        ..Default::default()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn create_texture(device: &Device, size: Extent3d) -> Texture {
 | 
				
			||||||
 | 
					    use wgpu::{TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device.create_texture(&TextureDescriptor {
 | 
				
			||||||
 | 
					        size,
 | 
				
			||||||
 | 
					        mip_level_count: 1,
 | 
				
			||||||
 | 
					        sample_count: 1,
 | 
				
			||||||
 | 
					        dimension: TextureDimension::D2,
 | 
				
			||||||
 | 
					        format: TextureFormat::Bgra8Unorm,
 | 
				
			||||||
 | 
					        usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
 | 
				
			||||||
 | 
					        label: Some("gb_pixel_buffer"),
 | 
				
			||||||
 | 
					        view_formats: &[TextureFormat::Bgra8Unorm],
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn write_to_texture(
 | 
				
			||||||
 | 
					    queue: &Queue,
 | 
				
			||||||
 | 
					    texture: &Texture,
 | 
				
			||||||
 | 
					    data: &[u8; GB_WIDTH * 4 * GB_HEIGHT],
 | 
				
			||||||
 | 
					    size: Extent3d,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    use std::num::NonZeroU32;
 | 
				
			||||||
 | 
					    use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureAspect};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    queue.write_texture(
 | 
				
			||||||
 | 
					        ImageCopyTexture {
 | 
				
			||||||
 | 
					            texture,
 | 
				
			||||||
 | 
					            mip_level: 0,
 | 
				
			||||||
 | 
					            origin: Origin3d::ZERO,
 | 
				
			||||||
 | 
					            aspect: TextureAspect::All,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        data,
 | 
				
			||||||
 | 
					        ImageDataLayout {
 | 
				
			||||||
 | 
					            offset: 0,
 | 
				
			||||||
 | 
					            bytes_per_row: NonZeroU32::new(4 * GB_WIDTH as u32),
 | 
				
			||||||
 | 
					            rows_per_image: NonZeroU32::new(GB_HEIGHT as u32),
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        size,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn expose_texture_to_egui(
 | 
				
			||||||
 | 
					    render_pass: &mut RenderPass,
 | 
				
			||||||
 | 
					    device: &Device,
 | 
				
			||||||
 | 
					    view: &TextureView,
 | 
				
			||||||
 | 
					) -> TextureId {
 | 
				
			||||||
 | 
					    render_pass.egui_texture_from_wgpu_texture(device, view, FILTER_MODE)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn create_command_encoder(device: &Device) -> CommandEncoder {
 | 
				
			||||||
 | 
					    use wgpu::CommandEncoderDescriptor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    device.create_command_encoder(&CommandEncoderDescriptor {
 | 
				
			||||||
 | 
					        label: Some("encoder"),
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					fn create_screen_descriptor(window: &Window, config: &SurfaceConfiguration) -> ScreenDescriptor {
 | 
				
			||||||
 | 
					    ScreenDescriptor {
 | 
				
			||||||
 | 
					        physical_width: config.width,
 | 
				
			||||||
 | 
					        physical_height: config.height,
 | 
				
			||||||
 | 
					        scale_factor: window.scale_factor() as f32,
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub mod kbd {
 | 
				
			||||||
 | 
					    use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn space_released(input: &KeyboardInput) -> bool {
 | 
				
			||||||
 | 
					        let keycode = input.virtual_keycode;
 | 
				
			||||||
 | 
					        matches!(input.state, ElementState::Released if keycode == Some(VirtualKeyCode::Space))
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,11 +17,11 @@ impl Default for HighRam {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl BusIo for HighRam {
 | 
					impl BusIo for HighRam {
 | 
				
			||||||
    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
					 | 
				
			||||||
        self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        self.buf[addr as usize - HIGH_RAM_START_ADDRESS]
 | 
					        self.buf[addr as usize - HIGH_RAM_START_ADDRESS]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
				
			||||||
 | 
					        self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3283
									
								
								src/instruction.rs
									
									
									
									
									
								
							
							
						
						
									
										3283
									
								
								src/instruction.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,11 +1,20 @@
 | 
				
			|||||||
use bitfield::bitfield;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct Interrupt {
 | 
					pub(crate) struct Interrupt {
 | 
				
			||||||
    pub(crate) flag: InterruptFlag,
 | 
					    pub(crate) flag: InterruptFlag,
 | 
				
			||||||
    pub(crate) enable: InterruptEnable,
 | 
					    pub(crate) enable: InterruptEnable,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Interrupt {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            flag: InterruptFlag(0),
 | 
				
			||||||
 | 
					            enable: InterruptEnable(0),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
   pub struct InterruptEnable(u8);
 | 
					   pub struct InterruptEnable(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -23,12 +32,6 @@ impl Clone for InterruptEnable {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for InterruptEnable {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for InterruptEnable {
 | 
					impl From<u8> for InterruptEnable {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -58,12 +61,6 @@ impl Clone for InterruptFlag {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for InterruptFlag {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for InterruptFlag {
 | 
					impl From<u8> for InterruptFlag {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
 | 
					use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
 | 
				
			||||||
use winit_input_helper::WinitInputHelper;
 | 
					use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub struct Joypad {
 | 
					pub struct Joypad {
 | 
				
			||||||
    /// 0xFF00 | P1/JOYP - Player 1 Joypad
 | 
					    /// 0xFF00 | P1/JOYP - Player 1 Joypad
 | 
				
			||||||
    pub(crate) p1: u8,
 | 
					    pub(crate) p1: u8,
 | 
				
			||||||
@@ -48,7 +48,7 @@ impl Joypad {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, Default)]
 | 
					#[derive(Debug, Default)]
 | 
				
			||||||
struct JoypadState {
 | 
					struct JoypadState {
 | 
				
			||||||
    // Direction Row
 | 
					    // Direction Row
 | 
				
			||||||
    dpad_down: ButtonEvent,
 | 
					    dpad_down: ButtonEvent,
 | 
				
			||||||
@@ -110,64 +110,39 @@ impl ButtonEvent {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
 | 
					#[inline]
 | 
				
			||||||
    use winit::event::VirtualKeyCode;
 | 
					pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TODO: What do I have to do to get a match statement here?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let state = &mut pad.ext;
 | 
					    let state = &mut pad.ext;
 | 
				
			||||||
    let irq = &mut pad.interrupt;
 | 
					    let irq = &mut pad.interrupt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Down) {
 | 
					    match key.state {
 | 
				
			||||||
        state.dpad_down.update(true, irq);
 | 
					        ElementState::Pressed => match key.virtual_keycode {
 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::Down) {
 | 
					            Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq),
 | 
				
			||||||
        state.dpad_down.update(false, irq);
 | 
					            Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq),
 | 
				
			||||||
    }
 | 
					            Some(VirtualKeyCode::Left) => state.dpad_left.update(true, irq),
 | 
				
			||||||
 | 
					            Some(VirtualKeyCode::Right) => state.dpad_right.update(true, irq),
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Up) {
 | 
					            Some(VirtualKeyCode::Return) => state.start.update(true, irq),
 | 
				
			||||||
        state.dpad_up.update(true, irq);
 | 
					            Some(VirtualKeyCode::RShift) => state.select.update(true, irq),
 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::Up) {
 | 
					            Some(VirtualKeyCode::Z) => state.south.update(true, irq),
 | 
				
			||||||
        state.dpad_up.update(false, irq);
 | 
					            Some(VirtualKeyCode::X) => state.east.update(true, irq),
 | 
				
			||||||
    }
 | 
					            None | Some(_) => {}
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Left) {
 | 
					        ElementState::Released => match key.virtual_keycode {
 | 
				
			||||||
        state.dpad_left.update(true, irq);
 | 
					            Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq),
 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::Left) {
 | 
					            Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq),
 | 
				
			||||||
        state.dpad_left.update(false, irq);
 | 
					            Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq),
 | 
				
			||||||
    }
 | 
					            Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq),
 | 
				
			||||||
 | 
					            Some(VirtualKeyCode::Return) => state.start.update(false, irq),
 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Right) {
 | 
					            Some(VirtualKeyCode::RShift) => state.select.update(false, irq),
 | 
				
			||||||
        state.dpad_right.update(true, irq);
 | 
					            Some(VirtualKeyCode::Z) => state.south.update(false, irq),
 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::Right) {
 | 
					            Some(VirtualKeyCode::X) => state.east.update(false, irq),
 | 
				
			||||||
        state.dpad_right.update(false, irq);
 | 
					            None | Some(_) => {}
 | 
				
			||||||
    }
 | 
					        },
 | 
				
			||||||
 | 
					 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::T) {
 | 
					 | 
				
			||||||
        state.start.update(true, irq);
 | 
					 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::T) {
 | 
					 | 
				
			||||||
        state.start.update(false, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Y) {
 | 
					 | 
				
			||||||
        state.select.update(true, irq);
 | 
					 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::Y) {
 | 
					 | 
				
			||||||
        state.select.update(false, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::Z) {
 | 
					 | 
				
			||||||
        state.south.update(true, irq);
 | 
					 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::Z) {
 | 
					 | 
				
			||||||
        state.south.update(false, irq);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if input.key_pressed(VirtualKeyCode::X) {
 | 
					 | 
				
			||||||
        state.east.update(true, irq);
 | 
					 | 
				
			||||||
    } else if input.key_released(VirtualKeyCode::X) {
 | 
					 | 
				
			||||||
        state.east.update(false, irq);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
 | 
					#[inline]
 | 
				
			||||||
 | 
					pub(crate) fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
 | 
				
			||||||
    use Button::*;
 | 
					    use Button::*;
 | 
				
			||||||
    use GamepadEventType::*;
 | 
					    use GamepadEventType::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/lib.rs
									
									
									
									
									
								
							@@ -1,21 +1,22 @@
 | 
				
			|||||||
pub use gui::Egui;
 | 
					#![allow(clippy::derivable_impls)] // Will remove this if bitfield-rs allows default impls
 | 
				
			||||||
pub use instruction::Cycle;
 | 
					
 | 
				
			||||||
pub use sound::AudioMPSC;
 | 
					pub use apu::gen::init as spsc_init;
 | 
				
			||||||
 | 
					pub type Cycle = u64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const GB_WIDTH: usize = 160;
 | 
					pub const GB_WIDTH: usize = 160;
 | 
				
			||||||
pub const GB_HEIGHT: usize = 144;
 | 
					pub const GB_HEIGHT: usize = 144;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mod apu;
 | 
				
			||||||
mod bus;
 | 
					mod bus;
 | 
				
			||||||
mod cartridge;
 | 
					mod cartridge;
 | 
				
			||||||
mod cpu;
 | 
					mod cpu;
 | 
				
			||||||
pub mod emu;
 | 
					pub mod emu;
 | 
				
			||||||
mod gui;
 | 
					pub mod gui;
 | 
				
			||||||
mod high_ram;
 | 
					mod high_ram;
 | 
				
			||||||
mod instruction;
 | 
					mod instruction;
 | 
				
			||||||
mod interrupt;
 | 
					mod interrupt;
 | 
				
			||||||
mod joypad;
 | 
					mod joypad;
 | 
				
			||||||
mod ppu;
 | 
					mod ppu;
 | 
				
			||||||
mod serial;
 | 
					mod serial;
 | 
				
			||||||
mod sound;
 | 
					 | 
				
			||||||
mod timer;
 | 
					mod timer;
 | 
				
			||||||
mod work_ram;
 | 
					mod work_ram;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										218
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										218
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,154 +1,126 @@
 | 
				
			|||||||
use anyhow::{anyhow, Result};
 | 
					use std::path::PathBuf;
 | 
				
			||||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
 | 
					 | 
				
			||||||
use gb::{AudioMPSC, Cycle, Egui, GB_HEIGHT, GB_WIDTH};
 | 
					 | 
				
			||||||
use gilrs::Gilrs;
 | 
					 | 
				
			||||||
use pixels::{Pixels, SurfaceTexture};
 | 
					 | 
				
			||||||
use rodio::{OutputStream, Sink};
 | 
					 | 
				
			||||||
use std::time::Instant;
 | 
					use std::time::Instant;
 | 
				
			||||||
use winit::dpi::LogicalSize;
 | 
					 | 
				
			||||||
use winit::event::{Event, VirtualKeyCode};
 | 
					 | 
				
			||||||
use winit::event_loop::{ControlFlow, EventLoop};
 | 
					 | 
				
			||||||
use winit::window::{Window, WindowBuilder};
 | 
					 | 
				
			||||||
use winit_input_helper::WinitInputHelper;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SCALE: f64 = 2.0;
 | 
					use clap::{arg, command, value_parser};
 | 
				
			||||||
 | 
					use gb::gui::{EmuMode, Gui};
 | 
				
			||||||
 | 
					use gb::{emu, gui};
 | 
				
			||||||
 | 
					use gilrs::Gilrs;
 | 
				
			||||||
 | 
					use rodio::{OutputStream, Sink};
 | 
				
			||||||
 | 
					use tracing_subscriber::EnvFilter;
 | 
				
			||||||
 | 
					use winit::event::{Event, WindowEvent};
 | 
				
			||||||
 | 
					use winit::event_loop::{EventLoop, EventLoopBuilder};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn main() -> Result<()> {
 | 
					const AUDIO_ENABLED: bool = true;
 | 
				
			||||||
    let app = App::new(crate_name!())
 | 
					 | 
				
			||||||
        .version(crate_version!())
 | 
					 | 
				
			||||||
        .author(crate_authors!())
 | 
					 | 
				
			||||||
        .about(crate_description!());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let m = app
 | 
					fn main() {
 | 
				
			||||||
 | 
					    let m = command!()
 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
            Arg::with_name("rom")
 | 
					            arg!(-b --boot <FILE> "path to boot ROM")
 | 
				
			||||||
                .value_name("ROM_FILE")
 | 
					                .required(false)
 | 
				
			||||||
                .takes_value(true)
 | 
					                .value_parser(value_parser!(PathBuf)),
 | 
				
			||||||
                .required(true)
 | 
					 | 
				
			||||||
                .index(1)
 | 
					 | 
				
			||||||
                .help("Path to the Game ROM"),
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .arg(
 | 
					        .arg(
 | 
				
			||||||
            Arg::with_name("boot")
 | 
					            arg!([ROM_FILE] "path to game ROM")
 | 
				
			||||||
                .short("b")
 | 
					                .required(true)
 | 
				
			||||||
                .long("boot")
 | 
					                .value_parser(value_parser!(PathBuf)),
 | 
				
			||||||
                .value_name("FILE")
 | 
					 | 
				
			||||||
                .takes_value(true)
 | 
					 | 
				
			||||||
                .help("Path to Boot ROM"),
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .get_matches();
 | 
					        .get_matches();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // `rom` is a required value in every situation so this will
 | 
					    // Set up subscriber
 | 
				
			||||||
    // always exist.
 | 
					    if std::env::var("RUST_LOG").is_err() {
 | 
				
			||||||
    let rom_path = m
 | 
					        std::env::set_var("RUST_LOG", "gb=info");
 | 
				
			||||||
        .value_of("rom")
 | 
					    }
 | 
				
			||||||
        .expect("Required value 'rom' was provided");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut game_boy =
 | 
					    tracing_subscriber::fmt::fmt()
 | 
				
			||||||
        gb::emu::init(m.value_of("boot"), rom_path).expect("Initialized DMG-01 Emulator");
 | 
					        .with_env_filter(EnvFilter::from_default_env())
 | 
				
			||||||
    let cartridge_title = gb::emu::rom_title(&game_boy);
 | 
					        .init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize Gamepad Support
 | 
					    // Init CPU
 | 
				
			||||||
    let mut gamepad = Gilrs::new().expect("Initialized Gilrs for Controller Input");
 | 
					    let mut cpu = match m.get_one::<PathBuf>("boot") {
 | 
				
			||||||
 | 
					        Some(path) => {
 | 
				
			||||||
    // Initialize GUI
 | 
					            tracing::info!("User-provided boot ROM");
 | 
				
			||||||
    let event_loop = EventLoop::new();
 | 
					            emu::from_boot_rom(path).expect("initialize emulator with custom boot rom")
 | 
				
			||||||
    let mut input = WinitInputHelper::new();
 | 
					        }
 | 
				
			||||||
    let window = create_window(&event_loop, cartridge_title)?;
 | 
					        None => {
 | 
				
			||||||
 | 
					            tracing::info!("Built-in boot ROM");
 | 
				
			||||||
    let (mut pixels, mut egui) = {
 | 
					            Default::default()
 | 
				
			||||||
        let size = window.inner_size();
 | 
					        }
 | 
				
			||||||
        let scale_factor = window.scale_factor();
 | 
					 | 
				
			||||||
        let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
 | 
					 | 
				
			||||||
        let pixels = Pixels::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)?;
 | 
					 | 
				
			||||||
        let egui = Egui::new(size.width, size.height, scale_factor, pixels.context());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        (pixels, egui)
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (send, recv) = AudioMPSC::new();
 | 
					    // Load ROM if filepath was provided
 | 
				
			||||||
    game_boy.set_audio_src(send);
 | 
					    if let Some(path) = m.get_one::<PathBuf>("ROM_FILE") {
 | 
				
			||||||
 | 
					        tracing::info!("User-provided cartridge ROM");
 | 
				
			||||||
 | 
					        emu::read_game_rom(&mut cpu, path).expect("read game rom from path");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    emu::load_save(&mut cpu);
 | 
				
			||||||
 | 
					    let rom_title = emu::rom_title(&cpu).to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tracing::info!("Initialize Gamepad");
 | 
				
			||||||
 | 
					    let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Initialize Audio
 | 
					    // Initialize Audio
 | 
				
			||||||
    let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
 | 
					    let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
 | 
				
			||||||
    let sink = Sink::try_new(&stream_handle).expect("Initialize Audio Sink");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    std::thread::spawn(move || {
 | 
					    if AUDIO_ENABLED {
 | 
				
			||||||
        sink.append(recv);
 | 
					        let (prod, cons) = gb::spsc_init();
 | 
				
			||||||
 | 
					        let sink = {
 | 
				
			||||||
 | 
					            let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle");
 | 
				
			||||||
 | 
					            s.append(cons);
 | 
				
			||||||
 | 
					            s.set_volume(0.1);
 | 
				
			||||||
 | 
					            s
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sink.sleep_until_end();
 | 
					        emu::set_audio_prod(&mut cpu, prod);
 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut now = Instant::now();
 | 
					        tracing::info!("Spawn Audio Thread");
 | 
				
			||||||
    let mut cycle_count: Cycle = Default::default();
 | 
					        std::thread::spawn(move || {
 | 
				
			||||||
 | 
					            sink.sleep_until_end();
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Set up state for the Immediate-mode GUI
 | 
				
			||||||
 | 
					    let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut app = Gui::new(rom_title, &event_loop, &cpu);
 | 
				
			||||||
 | 
					    let mut last_key = gui::unused_key(); // TODO: Fix this awful impl
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // used for egui animations
 | 
				
			||||||
 | 
					    let start_time = Instant::now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    event_loop.run(move |event, _, control_flow| {
 | 
					    event_loop.run(move |event, _, control_flow| {
 | 
				
			||||||
        // Update egui
 | 
					        app.handle_event(&event);
 | 
				
			||||||
        egui.handle_event(&event);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Event::RedrawRequested(_) = event {
 | 
					        match event {
 | 
				
			||||||
            // Prepare egui
 | 
					            Event::MainEventsCleared => {
 | 
				
			||||||
            egui.prepare(&game_boy);
 | 
					                app.maybe_quit(&cpu, control_flow);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Render everything together
 | 
					                match app.mode {
 | 
				
			||||||
            let render_result = pixels.render_with(|encoder, target, ctx| {
 | 
					                    EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key),
 | 
				
			||||||
                // Render the texture
 | 
					                    EmuMode::StepFrame if gui::kbd::space_released(&last_key) => {
 | 
				
			||||||
                ctx.scaling_renderer.render(encoder, target);
 | 
					                        emu::run_frame(&mut cpu, &mut gamepad, last_key)
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    EmuMode::Step if gui::kbd::space_released(&last_key) => {
 | 
				
			||||||
 | 
					                        emu::run(&mut cpu, &mut gamepad, last_key, 4);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                    _ => {}
 | 
				
			||||||
 | 
					                };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // Render egui
 | 
					                // Input has been consumed, reset it
 | 
				
			||||||
                egui.render(encoder, target, ctx);
 | 
					                last_key = gui::unused_key();
 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if render_result
 | 
					                app.request_redraw();
 | 
				
			||||||
                .map_err(|e| anyhow!("pixels.render() failed: {}", e))
 | 
					 | 
				
			||||||
                .is_err()
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                *control_flow = ControlFlow::Exit;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					            Event::RedrawRequested(..) => {
 | 
				
			||||||
 | 
					                app.update_time(start_time.elapsed().as_secs_f64());
 | 
				
			||||||
        if input.update(&event) {
 | 
					                app.paint(&cpu);
 | 
				
			||||||
            if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
 | 
					 | 
				
			||||||
                *control_flow = ControlFlow::Exit;
 | 
					 | 
				
			||||||
                return;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Some(scale_factor) = input.scale_factor() {
 | 
					 | 
				
			||||||
                egui.scale_factor(scale_factor);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Some(size) = input.window_resized() {
 | 
					 | 
				
			||||||
                pixels.resize_surface(size.width, size.height);
 | 
					 | 
				
			||||||
                egui.resize(size.width, size.height);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let delta = now.elapsed().subsec_nanos();
 | 
					 | 
				
			||||||
            now = Instant::now();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let pending = Cycle::new(delta / gb::emu::SM83_CYCLE_TIME.subsec_nanos());
 | 
					 | 
				
			||||||
            cycle_count += gb::emu::run(&mut game_boy, &mut gamepad, &input, pending);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if cycle_count >= gb::emu::CYCLES_IN_FRAME {
 | 
					 | 
				
			||||||
                // Draw Frame
 | 
					 | 
				
			||||||
                cycle_count = Cycle::new(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                gb::emu::draw(game_boy.ppu(), pixels.get_frame());
 | 
					 | 
				
			||||||
                window.request_redraw();
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            Event::WindowEvent { event, .. } => match event {
 | 
				
			||||||
 | 
					                WindowEvent::Resized(size) => app.resize(size),
 | 
				
			||||||
 | 
					                WindowEvent::CloseRequested => emu::save_and_exit(&cpu, control_flow),
 | 
				
			||||||
 | 
					                WindowEvent::KeyboardInput { input, .. } => last_key = input,
 | 
				
			||||||
 | 
					                _ => {}
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            _ => {}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
 | 
					 | 
				
			||||||
    let size = LogicalSize::new((GB_WIDTH as f64) * SCALE, (GB_HEIGHT as f64) * SCALE);
 | 
					 | 
				
			||||||
    Ok(WindowBuilder::new()
 | 
					 | 
				
			||||||
        .with_title(title)
 | 
					 | 
				
			||||||
        .with_inner_size(size)
 | 
					 | 
				
			||||||
        .with_min_inner_size(size)
 | 
					 | 
				
			||||||
        .with_resizable(true)
 | 
					 | 
				
			||||||
        .with_decorations(true)
 | 
					 | 
				
			||||||
        .with_transparent(false)
 | 
					 | 
				
			||||||
        // .with_drag_and_drop(false) // OleInitialize failed error if this is set to true
 | 
					 | 
				
			||||||
        .build(event_loop)?)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										877
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										877
									
								
								src/ppu.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
use crate::instruction::Cycle;
 | 
					use crate::Cycle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Default, Clone)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct DirectMemoryAccess {
 | 
					pub(crate) struct DirectMemoryAccess {
 | 
				
			||||||
    pub(crate) state: DmaState,
 | 
					    pub(crate) state: DmaState,
 | 
				
			||||||
    cycle: Cycle,
 | 
					    cycle: Cycle,
 | 
				
			||||||
@@ -8,8 +8,18 @@ pub(crate) struct DirectMemoryAccess {
 | 
				
			|||||||
    pub(crate) start: DmaAddress,
 | 
					    pub(crate) start: DmaAddress,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for DirectMemoryAccess {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            state: DmaState::Disabled,
 | 
				
			||||||
 | 
					            cycle: Default::default(),
 | 
				
			||||||
 | 
					            start: Default::default(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl DirectMemoryAccess {
 | 
					impl DirectMemoryAccess {
 | 
				
			||||||
    pub(crate) fn clock(&mut self) -> Option<(u16, u16)> {
 | 
					    pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
 | 
				
			||||||
        match self.state {
 | 
					        match self.state {
 | 
				
			||||||
            DmaState::Pending => {
 | 
					            DmaState::Pending => {
 | 
				
			||||||
                self.cycle += 1;
 | 
					                self.cycle += 1;
 | 
				
			||||||
@@ -28,7 +38,7 @@ impl DirectMemoryAccess {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                let src_addr = self
 | 
					                let src_addr = self
 | 
				
			||||||
                    .start
 | 
					                    .start
 | 
				
			||||||
                    .addr
 | 
					                    .0
 | 
				
			||||||
                    .as_mut()
 | 
					                    .as_mut()
 | 
				
			||||||
                    .expect("Source Address present during DMA Transfer");
 | 
					                    .expect("Source Address present during DMA Transfer");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,9 +66,9 @@ impl DirectMemoryAccess {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn reset(&mut self) {
 | 
					    fn reset(&mut self) {
 | 
				
			||||||
        self.cycle = Cycle::new(0);
 | 
					        self.cycle = 0;
 | 
				
			||||||
        self.state = DmaState::Disabled;
 | 
					        self.state = DmaState::Disabled;
 | 
				
			||||||
        self.start.addr = None;
 | 
					        self.start.0 = None;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -69,32 +79,21 @@ pub(crate) enum DmaState {
 | 
				
			|||||||
    Transferring,
 | 
					    Transferring,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for DmaState {
 | 
					#[derive(Debug, Clone, Copy, Default)]
 | 
				
			||||||
    fn default() -> Self {
 | 
					pub(crate) struct DmaAddress(Option<u16>);
 | 
				
			||||||
        Self::Disabled
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Default, Clone, Copy)]
 | 
					 | 
				
			||||||
pub(crate) struct DmaAddress {
 | 
					 | 
				
			||||||
    /// The current *source* address of the DMA Transfer
 | 
					 | 
				
			||||||
    ///
 | 
					 | 
				
			||||||
    /// NB: Will be None if no DMA Transfer is in progress
 | 
					 | 
				
			||||||
    addr: Option<u16>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl DmaAddress {
 | 
					impl DmaAddress {
 | 
				
			||||||
    pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
 | 
					    pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
 | 
				
			||||||
        let start = (byte as u16) << 8;
 | 
					        let start = (byte as u16) << 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.addr = Some(start);
 | 
					        self.0 = Some(start);
 | 
				
			||||||
        *state = DmaState::Pending;
 | 
					        *state = DmaState::Pending;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<DmaAddress> for u8 {
 | 
					impl From<DmaAddress> for u8 {
 | 
				
			||||||
    fn from(ctrl: DmaAddress) -> Self {
 | 
					    fn from(ctrl: DmaAddress) -> Self {
 | 
				
			||||||
        match ctrl.addr {
 | 
					        match ctrl.0 {
 | 
				
			||||||
            Some(addr) => (addr >> 8) as u8,
 | 
					            Some(addr) => (addr >> 8) as u8,
 | 
				
			||||||
            None => 0xFF, // TODO: What garbage value should be here?
 | 
					            None => 0xFF, // TODO: What garbage value should be here?
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,12 +29,6 @@ impl Clone for LCDStatus {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for LCDStatus {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0x80) // bit 7 is always 1
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<LCDStatus> for u8 {
 | 
					impl From<LCDStatus> for u8 {
 | 
				
			||||||
    fn from(status: LCDStatus) -> Self {
 | 
					    fn from(status: LCDStatus) -> Self {
 | 
				
			||||||
        status.0
 | 
					        status.0
 | 
				
			||||||
@@ -67,12 +61,6 @@ impl From<PpuMode> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for PpuMode {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::HBlank
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
    pub struct LCDControl(u8);
 | 
					    pub struct LCDControl(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -93,12 +81,6 @@ impl Clone for LCDControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for LCDControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for LCDControl {
 | 
					impl From<u8> for LCDControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -142,12 +124,6 @@ impl From<TileMapAddress> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for TileMapAddress {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::X9800
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum TileDataAddress {
 | 
					pub enum TileDataAddress {
 | 
				
			||||||
    X8800 = 0,
 | 
					    X8800 = 0,
 | 
				
			||||||
@@ -170,12 +146,6 @@ impl From<TileDataAddress> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for TileDataAddress {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::X8800
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum ObjectSize {
 | 
					pub enum ObjectSize {
 | 
				
			||||||
    Eight = 0,
 | 
					    Eight = 0,
 | 
				
			||||||
@@ -183,7 +153,7 @@ pub enum ObjectSize {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl ObjectSize {
 | 
					impl ObjectSize {
 | 
				
			||||||
    pub(crate) fn as_u8(&self) -> u8 {
 | 
					    pub(crate) fn size(&self) -> u8 {
 | 
				
			||||||
        use ObjectSize::*;
 | 
					        use ObjectSize::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
@@ -209,12 +179,6 @@ impl From<ObjectSize> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectSize {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Eight
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
    pub struct BackgroundPalette(u8);
 | 
					    pub struct BackgroundPalette(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -243,12 +207,6 @@ impl Clone for BackgroundPalette {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for BackgroundPalette {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for BackgroundPalette {
 | 
					impl From<u8> for BackgroundPalette {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -288,12 +246,6 @@ impl Clone for ObjectPalette {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectPalette {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ObjectPalette {
 | 
					impl From<u8> for ObjectPalette {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -332,7 +284,7 @@ bitfield! {
 | 
				
			|||||||
    pub from into RenderPriority, priority, set_priority: 7, 7;
 | 
					    pub from into RenderPriority, priority, set_priority: 7, 7;
 | 
				
			||||||
    pub y_flip, set_y_flip: 6;
 | 
					    pub y_flip, set_y_flip: 6;
 | 
				
			||||||
    pub x_flip, set_x_flip: 5;
 | 
					    pub x_flip, set_x_flip: 5;
 | 
				
			||||||
    pub from into ObjectPaletteId, palette, set_palette: 4, 4;
 | 
					    pub from into ObjectPaletteKind, palette, set_palette: 4, 4;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Eq for ObjectFlags {}
 | 
					impl Eq for ObjectFlags {}
 | 
				
			||||||
@@ -361,35 +313,29 @@ impl From<ObjectFlags> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ObjectFlags {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum ObjectPaletteId {
 | 
					pub enum ObjectPaletteKind {
 | 
				
			||||||
    Zero = 0,
 | 
					    Zero = 0,
 | 
				
			||||||
    One = 1,
 | 
					    One = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<u8> for ObjectPaletteId {
 | 
					impl From<u8> for ObjectPaletteKind {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b01 {
 | 
					        match byte & 0b01 {
 | 
				
			||||||
            0b00 => ObjectPaletteId::Zero,
 | 
					            0b00 => ObjectPaletteKind::Zero,
 | 
				
			||||||
            0b01 => ObjectPaletteId::One,
 | 
					            0b01 => ObjectPaletteKind::One,
 | 
				
			||||||
            _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte),
 | 
					            _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<ObjectPaletteId> for u8 {
 | 
					impl From<ObjectPaletteKind> for u8 {
 | 
				
			||||||
    fn from(palette_num: ObjectPaletteId) -> Self {
 | 
					    fn from(palette_num: ObjectPaletteKind) -> Self {
 | 
				
			||||||
        palette_num as u8
 | 
					        palette_num as u8
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, PartialEq)]
 | 
					#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 | 
				
			||||||
pub enum RenderPriority {
 | 
					pub enum RenderPriority {
 | 
				
			||||||
    Object = 0,
 | 
					    Object = 0,
 | 
				
			||||||
    BackgroundAndWindow = 1,
 | 
					    BackgroundAndWindow = 1,
 | 
				
			||||||
@@ -411,12 +357,6 @@ impl From<RenderPriority> for u8 {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for RenderPriority {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Object
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug, Clone, Copy)]
 | 
				
			||||||
pub enum GrayShade {
 | 
					pub enum GrayShade {
 | 
				
			||||||
    White = 0,
 | 
					    White = 0,
 | 
				
			||||||
@@ -436,12 +376,6 @@ impl GrayShade {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for GrayShade {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::White
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for GrayShade {
 | 
					impl From<u8> for GrayShade {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b11 {
 | 
					        match byte & 0b11 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,6 @@
 | 
				
			|||||||
use bitfield::bitfield;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy, Default)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct Serial {
 | 
					pub(crate) struct Serial {
 | 
				
			||||||
    /// 0xFF01 | SB - Serial Transfer Data
 | 
					    /// 0xFF01 | SB - Serial Transfer Data
 | 
				
			||||||
    pub(crate) next: u8,
 | 
					    pub(crate) next: u8,
 | 
				
			||||||
@@ -8,6 +8,15 @@ pub(crate) struct Serial {
 | 
				
			|||||||
    pub(crate) ctrl: SerialControl,
 | 
					    pub(crate) ctrl: SerialControl,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for Serial {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            next: Default::default(),
 | 
				
			||||||
 | 
					            ctrl: SerialControl(0),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bitfield! {
 | 
					bitfield! {
 | 
				
			||||||
    pub struct SerialControl(u8);
 | 
					    pub struct SerialControl(u8);
 | 
				
			||||||
    impl Debug;
 | 
					    impl Debug;
 | 
				
			||||||
@@ -23,12 +32,6 @@ impl Clone for SerialControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for SerialControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for SerialControl {
 | 
					impl From<u8> for SerialControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -47,12 +50,6 @@ enum ShiftClock {
 | 
				
			|||||||
    Internal = 1,
 | 
					    Internal = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ShiftClock {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::External
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ShiftClock {
 | 
					impl From<u8> for ShiftClock {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b01 {
 | 
					        match byte & 0b01 {
 | 
				
			||||||
@@ -69,12 +66,6 @@ enum ClockSpeed {
 | 
				
			|||||||
    Fast = 1,
 | 
					    Fast = 1,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for ClockSpeed {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self::Normal
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for ClockSpeed {
 | 
					impl From<u8> for ClockSpeed {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        match byte & 0b01 {
 | 
					        match byte & 0b01 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1269
									
								
								src/sound.rs
									
									
									
									
									
								
							
							
						
						
									
										1269
									
								
								src/sound.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										94
									
								
								src/timer.rs
									
									
									
									
									
								
							
							
						
						
									
										94
									
								
								src/timer.rs
									
									
									
									
									
								
							@@ -1,21 +1,24 @@
 | 
				
			|||||||
use bitfield::bitfield;
 | 
					use bitfield::bitfield;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct Timer {
 | 
					pub(crate) struct Timer {
 | 
				
			||||||
    /// 0xFF07 | TAC - Timer Control
 | 
					    /// 0xFF07 | TAC - Timer Control
 | 
				
			||||||
    pub(crate) ctrl: TimerControl,
 | 
					    pub(crate) ctrl: TimerControl,
 | 
				
			||||||
    /// 0xFF05 | TIMA - Timer Counter
 | 
					    /// 0xFF05 | TIMA - Timer Counter
 | 
				
			||||||
    pub(crate) counter: u8,
 | 
					    counter: u8,
 | 
				
			||||||
    /// 0xFF06 | TMA - Timer Modulo
 | 
					    /// 0xFF06 | TMA - Timer Modulo
 | 
				
			||||||
    pub(crate) modulo: u8,
 | 
					    pub(crate) modulo: u8,
 | 
				
			||||||
    /// 0xFF04 | DIV - Divider Register
 | 
					    /// 0xFF04 | DIV - Divider Register
 | 
				
			||||||
    pub(crate) divider: u16,
 | 
					    pub(crate) divider: u16,
 | 
				
			||||||
    prev_and_result: Option<u8>,
 | 
					
 | 
				
			||||||
 | 
					    and_result: Option<u8>,
 | 
				
			||||||
    interrupt: bool,
 | 
					    interrupt: bool,
 | 
				
			||||||
 | 
					    state: State,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Timer {
 | 
					impl Timer {
 | 
				
			||||||
    pub(crate) fn clock(&mut self) {
 | 
					    pub(crate) fn tick(&mut self) {
 | 
				
			||||||
 | 
					        use State::*;
 | 
				
			||||||
        use TimerSpeed::*;
 | 
					        use TimerSpeed::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.divider = self.divider.wrapping_add(1);
 | 
					        self.divider = self.divider.wrapping_add(1);
 | 
				
			||||||
@@ -29,27 +32,45 @@ impl Timer {
 | 
				
			|||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let bit = (self.divider >> bit) as u8 & 0x01;
 | 
					        let bit = (self.divider >> bit) as u8 & 0x01;
 | 
				
			||||||
        let timer_enable = self.ctrl.enabled() as u8;
 | 
					        let new_result = bit & self.ctrl.enabled() as u8;
 | 
				
			||||||
        let and_result = bit & timer_enable;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(0x01) = self.prev_and_result {
 | 
					        if let Some(0x01) = self.and_result {
 | 
				
			||||||
            if and_result == 0x00 {
 | 
					            if new_result == 0x00 {
 | 
				
			||||||
                // Falling Edge, increase TIMA Register
 | 
					                // Falling Edge, increase TIMA Register
 | 
				
			||||||
                self.increment_tima();
 | 
					                self.inc_counter();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.prev_and_result = Some(and_result);
 | 
					        self.and_result = Some(new_result);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match self.state {
 | 
				
			||||||
 | 
					            TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
 | 
				
			||||||
 | 
					            LoadTIMA => {
 | 
				
			||||||
 | 
					                self.counter = self.modulo;
 | 
				
			||||||
 | 
					                self.interrupt = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                self.next();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            Normal => {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn increment_tima(&mut self) {
 | 
					    /// 0xFF05 | TIMA - Timer Counter
 | 
				
			||||||
        let (result, did_overflow) = self.counter.overflowing_add(1);
 | 
					    pub(crate) fn tima(&self) -> u8 {
 | 
				
			||||||
 | 
					        self.counter
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.counter = if did_overflow {
 | 
					    /// 0xFF05 | TIMA - Timer Counter
 | 
				
			||||||
            self.interrupt = true;
 | 
					    pub(crate) fn set_tima(&mut self, byte: u8) {
 | 
				
			||||||
            self.modulo
 | 
					        use State::*;
 | 
				
			||||||
        } else {
 | 
					
 | 
				
			||||||
            result
 | 
					        match self.state {
 | 
				
			||||||
 | 
					            Normal | AbortedTIMAOverflow(_) => self.counter = byte,
 | 
				
			||||||
 | 
					            TIMAOverflow(step) => {
 | 
				
			||||||
 | 
					                self.counter = byte;
 | 
				
			||||||
 | 
					                self.state = AbortedTIMAOverflow(step);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            LoadTIMA => { /* Ignored */ }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -60,17 +81,38 @@ impl Timer {
 | 
				
			|||||||
    pub(crate) fn set_interrupt(&mut self, value: bool) {
 | 
					    pub(crate) fn set_interrupt(&mut self, value: bool) {
 | 
				
			||||||
        self.interrupt = value;
 | 
					        self.interrupt = value;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn inc_counter(&mut self) {
 | 
				
			||||||
 | 
					        let (sum, did_overflow) = self.counter.overflowing_add(1);
 | 
				
			||||||
 | 
					        self.counter = if did_overflow { 0 } else { sum };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if did_overflow {
 | 
				
			||||||
 | 
					            self.state = State::TIMAOverflow(0);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn next(&mut self) {
 | 
				
			||||||
 | 
					        use State::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.state = match self.state {
 | 
				
			||||||
 | 
					            Normal | LoadTIMA | AbortedTIMAOverflow(3) => Normal,
 | 
				
			||||||
 | 
					            TIMAOverflow(3) => LoadTIMA,
 | 
				
			||||||
 | 
					            AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
 | 
				
			||||||
 | 
					            TIMAOverflow(step) => TIMAOverflow(step + 1),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for Timer {
 | 
					impl Default for Timer {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            ctrl: Default::default(),
 | 
					            ctrl: TimerControl(0),
 | 
				
			||||||
            counter: 0,
 | 
					            counter: 0,
 | 
				
			||||||
            modulo: 0,
 | 
					            modulo: 0,
 | 
				
			||||||
            divider: 0,
 | 
					            divider: 0,
 | 
				
			||||||
            interrupt: false,
 | 
					            interrupt: false,
 | 
				
			||||||
            prev_and_result: None,
 | 
					            and_result: None,
 | 
				
			||||||
 | 
					            state: State::Normal,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -115,12 +157,6 @@ impl Clone for TimerControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for TimerControl {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self(0)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl From<u8> for TimerControl {
 | 
					impl From<u8> for TimerControl {
 | 
				
			||||||
    fn from(byte: u8) -> Self {
 | 
					    fn from(byte: u8) -> Self {
 | 
				
			||||||
        Self(byte)
 | 
					        Self(byte)
 | 
				
			||||||
@@ -132,3 +168,11 @@ impl From<TimerControl> for u8 {
 | 
				
			|||||||
        ctrl.0
 | 
					        ctrl.0
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 | 
				
			||||||
 | 
					enum State {
 | 
				
			||||||
 | 
					    TIMAOverflow(u8),
 | 
				
			||||||
 | 
					    AbortedTIMAOverflow(u8),
 | 
				
			||||||
 | 
					    Normal,
 | 
				
			||||||
 | 
					    LoadTIMA,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,19 +5,19 @@ const VARIABLE_WORK_RAM_SIZE: usize = WORK_RAM_SIZE;
 | 
				
			|||||||
const WORK_RAM_START_ADDRESS: usize = 0xC000;
 | 
					const WORK_RAM_START_ADDRESS: usize = 0xC000;
 | 
				
			||||||
const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000;
 | 
					const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
pub(crate) struct WorkRam {
 | 
					pub(crate) struct WorkRam {
 | 
				
			||||||
    bank: Box<[u8; WORK_RAM_SIZE]>,
 | 
					    bank: Box<[u8; WORK_RAM_SIZE]>,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl BusIo for WorkRam {
 | 
					impl BusIo for WorkRam {
 | 
				
			||||||
    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
					 | 
				
			||||||
        self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        self.bank[addr as usize - WORK_RAM_START_ADDRESS]
 | 
					        self.bank[addr as usize - WORK_RAM_START_ADDRESS]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
				
			||||||
 | 
					        self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for WorkRam {
 | 
					impl Default for WorkRam {
 | 
				
			||||||
@@ -28,48 +28,25 @@ impl Default for WorkRam {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Debug, Clone, Copy)]
 | 
					#[derive(Debug)]
 | 
				
			||||||
enum BankNumber {
 | 
					 | 
				
			||||||
    One = 1,
 | 
					 | 
				
			||||||
    Two = 2,
 | 
					 | 
				
			||||||
    Three = 3,
 | 
					 | 
				
			||||||
    Four = 4,
 | 
					 | 
				
			||||||
    Five = 5,
 | 
					 | 
				
			||||||
    Six = 6,
 | 
					 | 
				
			||||||
    Seven = 7,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug, Clone)]
 | 
					 | 
				
			||||||
pub(crate) struct VariableWorkRam {
 | 
					pub(crate) struct VariableWorkRam {
 | 
				
			||||||
    current: BankNumber,
 | 
					    buf: Box<[u8; VARIABLE_WORK_RAM_SIZE]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour
 | 
				
			||||||
    bank_n: Box<[[u8; VARIABLE_WORK_RAM_SIZE]; 7]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Default for VariableWorkRam {
 | 
					impl Default for VariableWorkRam {
 | 
				
			||||||
    fn default() -> Self {
 | 
					    fn default() -> Self {
 | 
				
			||||||
        Self {
 | 
					        Self {
 | 
				
			||||||
            current: BankNumber::One,
 | 
					            buf: Box::new([0u8; VARIABLE_WORK_RAM_SIZE]),
 | 
				
			||||||
            bank_n: Box::new([[0u8; VARIABLE_WORK_RAM_SIZE]; 7]),
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl VariableWorkRam {
 | 
					 | 
				
			||||||
    fn set_current_bank(&mut self, bank: BankNumber) {
 | 
					 | 
				
			||||||
        self.current = bank;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fn get_current_bank(&self) -> BankNumber {
 | 
					 | 
				
			||||||
        self.current
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl BusIo for VariableWorkRam {
 | 
					impl BusIo for VariableWorkRam {
 | 
				
			||||||
    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
					    fn read_byte(&self, addr: u16) -> u8 {
 | 
				
			||||||
        self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
 | 
					        self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn read_byte(&self, addr: u16) -> u8 {
 | 
					    fn write_byte(&mut self, addr: u16, byte: u8) {
 | 
				
			||||||
        self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
 | 
					        self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user