mizu_core/
lib.rs

1mod apu;
2mod cartridge;
3pub mod cpu;
4mod joypad;
5pub mod memory;
6mod ppu;
7mod printer;
8mod save_error;
9mod serial;
10mod timer;
11
12#[cfg(test)]
13mod tests;
14
15use std::cell::RefCell;
16use std::fs::File;
17use std::io::{Cursor, Read, Seek, SeekFrom, Write};
18use std::path::{Path, PathBuf};
19use std::rc::Rc;
20
21use save_state::Savable;
22
23use cartridge::Cartridge;
24use cpu::Cpu;
25use memory::Bus;
26
27pub use apu::AudioBuffers;
28pub use cartridge::CartridgeError;
29pub use joypad::JoypadButton;
30pub use printer::Printer;
31pub use save_error::SaveError;
32pub use serial::SerialDevice;
33
34/// The current version of state saved/loaded by
35/// [`GameBoy::save_state`] / [`GameBoy::load_state`].
36///
37/// Loading a state that is not compatible with this version, results
38/// in [`SaveError::UnmatchedSaveErrorVersion`]
39pub const SAVE_STATE_VERSION: usize = 2;
40const SAVE_STATE_MAGIC: &[u8; 4] = b"MST\xee";
41const SAVE_STATE_ZSTD_COMPRESSION_LEVEL: i32 = 0; // default compression
42
43/// Custom configuration for the [`GameBoy`] emulation inner workings
44#[derive(Debug, Default, Clone, Copy, Savable)]
45pub struct GameBoyConfig {
46    /// Should the gameboy run in DMG mode? default is in CGB mode
47    pub is_dmg: bool,
48}
49
50impl GameBoyConfig {
51    pub fn boot_rom_len(&self) -> usize {
52        if self.is_dmg {
53            0x100
54        } else {
55            0x900
56        }
57    }
58}
59
60/// Builder struct container for [`GameBoy`] configurations and options.
61pub struct GameBoyBuilder {
62    config: GameBoyConfig,
63    rom_file: PathBuf,
64    boot_rom_file: Option<PathBuf>,
65    sram_file: Option<PathBuf>,
66    save_on_shutdown: bool,
67}
68
69impl GameBoyBuilder {
70    /// Add custom [`GameBoyConfig`]
71    pub fn config(mut self, config: GameBoyConfig) -> Self {
72        self.config = config;
73        self
74    }
75
76    /// Add boot rom file
77    pub fn boot_rom_file<P: AsRef<Path>>(mut self, boot_rom_file: P) -> Self {
78        self.boot_rom_file = Some(boot_rom_file.as_ref().to_path_buf());
79        self
80    }
81
82    /// Add custom sram file,
83    /// if this is not specified, the sram will be stored in the same directory
84    /// as the rom file.
85    pub fn sram_file<P: AsRef<Path>>(mut self, save_file: P) -> Self {
86        self.sram_file = Some(save_file.as_ref().to_path_buf());
87        self
88    }
89
90    /// Should the SRAM be saved on shutdown? (default: true)
91    pub fn save_on_shutdown(mut self, save_on_shutdown: bool) -> Self {
92        self.save_on_shutdown = save_on_shutdown;
93        self
94    }
95
96    /// Builds a [`GameBoy`] instance.
97    pub fn build(self) -> Result<GameBoy, CartridgeError> {
98        GameBoy::build(self)
99    }
100}
101
102/// The GameBoy is the main interface to the emulator.
103///
104/// Everything regarding emulation can be controlled from here.
105pub struct GameBoy {
106    cpu: Cpu,
107    bus: Bus,
108}
109
110impl GameBoy {
111    /// Initiate a builder object with a cartridge file.
112    pub fn builder<RomP: AsRef<Path>>(rom_file: RomP) -> GameBoyBuilder {
113        GameBoyBuilder {
114            config: GameBoyConfig::default(),
115            rom_file: rom_file.as_ref().to_path_buf(),
116            boot_rom_file: None,
117            sram_file: None,
118            save_on_shutdown: true,
119        }
120    }
121
122    fn build(builder: GameBoyBuilder) -> Result<Self, CartridgeError> {
123        let file_path = builder.rom_file;
124        let sram_file_path = builder.sram_file;
125        let boot_rom_file_path = builder.boot_rom_file;
126        let config = builder.config;
127        let save_on_shutdown = builder.save_on_shutdown;
128
129        let cartridge = Cartridge::from_file(file_path, sram_file_path, save_on_shutdown)?;
130
131        let (bus, cpu) = if let Some(boot_rom_file) = boot_rom_file_path {
132            let mut boot_rom_file = File::open(boot_rom_file)?;
133            let mut data = vec![0; config.boot_rom_len()];
134
135            // make sure the boot_rom is the exact same size
136            assert_eq!(
137                boot_rom_file.metadata()?.len(),
138                data.len() as u64,
139                "boot_rom file size is not correct"
140            );
141
142            boot_rom_file.read_exact(&mut data)?;
143
144            (
145                Bus::new_with_boot_rom(cartridge, data, config),
146                Cpu::new(config),
147            )
148        } else {
149            let is_cartridge_color = cartridge.is_cartridge_color();
150            (
151                Bus::new_without_boot_rom(cartridge, config),
152                Cpu::new_without_boot_rom(config, is_cartridge_color),
153            )
154        };
155
156        Ok(Self { bus, cpu })
157    }
158
159    /// Clocks the Gameboy clock for the duration of one PPU frame.
160    ///
161    /// This is good for timing emulation, you can call this function once
162    /// and then render it.
163    pub fn clock_for_frame(&mut self) {
164        const PPU_CYCLES_PER_FRAME: u32 = 456 * 154;
165        let mut cycles = 0u32;
166        while cycles < PPU_CYCLES_PER_FRAME {
167            self.cpu.next_instruction(&mut self.bus).unwrap();
168            cycles += self.bus.elapsed_ppu_cycles();
169        }
170    }
171
172    /// Return the game title string extracted from the cartridge.
173    pub fn game_title(&self) -> &str {
174        self.bus.cartridge().game_title()
175    }
176
177    /// The cartridge file path.
178    pub fn file_path(&self) -> &Path {
179        self.bus.cartridge().file_path()
180    }
181
182    /// Return the pixels buffer of the PPU at the current state.
183    ///
184    /// The format of the pixel buffer is RGB, i.e. 3 bytes per pixel.
185    pub fn screen_buffer(&self) -> &[u8] {
186        self.bus.screen_buffer()
187    }
188
189    /// Return the audio buffer of the APU at the current state.
190    ///
191    /// We use `&mut` as it will also reset the buffers after using them
192    pub fn audio_buffers(&mut self) -> AudioBuffers<'_> {
193        self.bus.audio_buffers()
194    }
195
196    /// Change the state of the joypad button to `pressed`.
197    pub fn press_joypad(&mut self, button: JoypadButton) {
198        self.bus.press_joypad(button);
199    }
200
201    /// Change the state of the joypad button to `released`.
202    pub fn release_joypad(&mut self, button: JoypadButton) {
203        self.bus.release_joypad(button);
204    }
205
206    // TODO: Not sure if using RefCell is the best option here
207    /// Connect a serial device to the Gameboy.
208    ///
209    /// Currently the gameboy can only be `master`, so the other device
210    /// must be implemented as `slave`.
211    pub fn connect_device(&mut self, device: Rc<RefCell<dyn SerialDevice>>) {
212        self.bus.connect_device(device);
213    }
214
215    /// Disconnects the serial device if any is connected, else, nothing is done
216    pub fn disconnect_device(&mut self) {
217        self.bus.disconnect_device();
218    }
219
220    /// Saves the whole current state of the emulator.
221    pub fn save_state<W: Write>(&self, mut writer: W) -> Result<(), SaveError> {
222        SAVE_STATE_MAGIC.save(&mut writer)?;
223        SAVE_STATE_VERSION.save(&mut writer)?;
224        let cartridge_hash: &[u8; 32] = self.bus.cartridge().hash();
225        cartridge_hash.save(&mut writer)?;
226
227        let mut writer = zstd::Encoder::new(&mut writer, SAVE_STATE_ZSTD_COMPRESSION_LEVEL)?;
228
229        self.cpu.save(&mut writer)?;
230        self.bus.save(&mut writer)?;
231
232        let _writer = writer.finish()?;
233
234        Ok(())
235    }
236
237    /// Loads the whole state of the emulator, if an error happened in the middle
238    /// the emulator will keep functioning like normal, as it stores a backup recovery state before
239    /// loading the new state.
240    pub fn load_state<R: Read + Seek>(&mut self, mut reader: R) -> Result<(), SaveError> {
241        // save state, so that if an error occured we will restore it back.
242        let mut recovery_save_state = Vec::new();
243        self.cpu
244            .save(&mut recovery_save_state)
245            .expect("recovery save cpu");
246        self.bus
247            .save(&mut recovery_save_state)
248            .expect("recovery save bus");
249
250        let mut load_routine = || {
251            let mut magic = [0u8; 4];
252            let mut version = 0usize;
253            let mut hash = [0u8; 32];
254
255            magic.load(&mut reader)?;
256            if &magic != SAVE_STATE_MAGIC {
257                return Err(SaveError::InvalidSaveStateHeader);
258            }
259
260            // since there might be some possibility to migrate from different
261            // versions, we will not check here.
262            version.load(&mut reader)?;
263
264            hash.load(&mut reader)?;
265            if &hash != self.bus.cartridge().hash() {
266                return Err(SaveError::InvalidCartridgeHash);
267            }
268
269            {
270                // use a box on read because there are two types of readers
271                // that we might use, compressed or not compressed based on the version
272                // of the save_state file
273                let mut second_stage_reader: Box<dyn Read>;
274
275                if version == 1 && SAVE_STATE_VERSION == 2 {
276                    // no need to use compression
277                    second_stage_reader = Box::new(&mut reader);
278                } else if version != SAVE_STATE_VERSION {
279                    return Err(SaveError::UnmatchedSaveErrorVersion(version));
280                } else {
281                    second_stage_reader = Box::new(zstd::Decoder::new(&mut reader)?);
282                }
283
284                self.cpu.load(&mut second_stage_reader)?;
285                self.bus.load(&mut second_stage_reader)?;
286            }
287
288            // make sure there is no more data
289            let stream_current_pos = reader.stream_position()?;
290            reader.seek(SeekFrom::End(0))?;
291            let stream_last_pos = reader.stream_position()?;
292
293            let (remaining_data_len, overflow) =
294                stream_last_pos.overflowing_sub(stream_current_pos);
295            assert!(!overflow);
296
297            if remaining_data_len > 0 {
298                // return seek
299                reader.seek(SeekFrom::Start(stream_current_pos))?;
300
301                Err(SaveError::SaveStateError(save_state::Error::TrailingData(
302                    remaining_data_len,
303                )))
304            } else {
305                Ok(())
306            }
307        };
308
309        if let Err(err) = load_routine() {
310            let mut cursor = Cursor::new(&recovery_save_state);
311
312            self.cpu.load(&mut cursor).expect("recovery load cpu");
313            self.bus.load(&mut cursor).expect("recovery load bus");
314
315            Err(err)
316        } else {
317            Ok(())
318        }
319    }
320}