gameboyr/
lib.rs

1//! A Gameboy emulator written in Rust
2//!
3//! Gameboy R provides a simple to use implmentation of a Gameboy / Gameboy Color.
4
5mod apu;
6mod cartridges;
7mod clock;
8mod cpu;
9mod joypad;
10mod memory;
11mod mmu;
12mod ppu;
13mod serial;
14mod timer;
15
16use std::cell::RefCell;
17use std::path::Path;
18use std::rc::Rc;
19
20use crate::memory::Memory;
21
22/// Dimensions represent length and width of a screen.
23pub struct Dimensions {
24    pub width: usize,
25    pub height: usize,
26}
27
28/// GameboyButton represents each possibly button available.
29///
30/// This enum is used to provide users with a way to easily map any
31/// key to a specific Gameboy button.
32#[derive(Clone, Copy)]
33pub enum GameboyButton {
34    Right,
35    Left,
36    Up,
37    Down,
38    A,
39    B,
40    Select,
41    Start,
42}
43
44impl From<GameboyButton> for joypad::JoypadKey {
45    fn from(value: GameboyButton) -> joypad::JoypadKey {
46        match value {
47            GameboyButton::A => joypad::JoypadKey::A,
48            GameboyButton::B => joypad::JoypadKey::B,
49            GameboyButton::Right => joypad::JoypadKey::Right,
50            GameboyButton::Left => joypad::JoypadKey::Left,
51            GameboyButton::Up => joypad::JoypadKey::Up,
52            GameboyButton::Down => joypad::JoypadKey::Down,
53            GameboyButton::Select => joypad::JoypadKey::Select,
54            GameboyButton::Start => joypad::JoypadKey::Start,
55        }
56    }
57}
58
59/// Gameboy represents the physical device itself.
60///
61/// The Gameboy functionality is provided to the user through a set of
62/// consise and useful helper functions. The user can not directly interact
63/// with the Gameboy itself.
64pub struct Gameboy {
65    mmu: Rc<RefCell<mmu::Mmu>>,
66    cpu: cpu::RealTimeCpu,
67}
68
69impl Gameboy {
70    /// Create a new Gameboy by providing ROM data, a save path, and whether to skip checks.
71    /// When the save path contains an existing save, that data will be loaded.
72    pub fn new(rom: Vec<u8>, save_path: impl AsRef<Path>, skip_checks: bool) -> Gameboy {
73        let cartridge = cartridges::new(rom, save_path, skip_checks);
74        let cartridge_mode = cartridge.get_mode();
75        let mmu = Rc::new(RefCell::new(mmu::Mmu::new(cartridge)));
76        let cpu = cpu::RealTimeCpu::new(cartridge_mode, mmu.clone());
77        Gameboy { mmu, cpu }
78    }
79
80    // Shutdown the Gameboy.
81    pub fn shutdown(&mut self) {
82        self.save();
83    }
84
85    // Attempt to enable audio on the Gameboy. Returning true if successful.
86    pub fn try_enable_audio(&mut self) -> bool {
87        let apu = apu::Apu::new();
88        self.mmu.borrow_mut().apu = apu;
89        self.mmu.borrow().apu.is_some()
90    }
91
92    /// Perform one step (including CPU, MMU, PPU), returning the number of CPU cycles run.
93    pub fn step(&mut self) -> u32 {
94        if self.mmu.borrow().get_byte(self.cpu.cpu.registers.pc) == 0x10 {
95            self.mmu.borrow_mut().perform_speed_switch();
96        }
97        let cycles = self.cpu.run();
98        self.mmu.borrow_mut().run_cycles(cycles);
99        cycles
100    }
101
102    /// Save the current state of the Gameboy.
103    pub fn save(&mut self) {
104        self.mmu.borrow_mut().cartridge.save();
105    }
106
107    /// Get the title of the currently loaded ROM.
108    pub fn get_rom_title(&self) -> String {
109        self.mmu.borrow().cartridge.get_title()
110    }
111
112    /// Get the dimensions of the Gameboys screen.
113    ///
114    /// ```
115    /// struct Dimensions {
116    ///     width: usize,
117    ///     height: usize,
118    /// }
119    /// ```
120    pub fn get_screen_dimensions(&self) -> Dimensions {
121        Dimensions {
122            width: ppu::SCREEN_WIDTH,
123            height: ppu::SCREEN_HEIGHT,
124        }
125    }
126
127    /// Check whether the Gameboy screen has updated and should rerender. This
128    /// will also reset the value to false once checked.
129    pub fn has_screen_updated(&mut self) -> bool {
130        let result = self.mmu.borrow().ppu.vblank;
131        self.mmu.borrow_mut().ppu.vblank = false;
132        result
133    }
134
135    /// Get the current data on the screen. This is returned as a 1D array of Pixel's.
136    /// Where each Pixel represents the colors of a single pixel.
137    ///
138    /// ```
139    /// struct Pixel {
140    ///     r: u8,
141    ///     g: u8,
142    ///     b: u8,
143    /// }
144    /// ```
145    ///
146    /// NOTE: when using a Gameboy without color support, all fields of the Pixel will be
147    ///       the same.
148    pub fn get_screen_data(&self) -> [ppu::Pixel; ppu::SCREEN_WIDTH * ppu::SCREEN_HEIGHT] {
149        self.mmu.borrow().ppu.data
150    }
151
152    /// Check whether the Gameboy is able to take input.
153    pub fn can_take_input(&mut self) -> bool {
154        self.cpu.flip()
155    }
156
157    /// Handle keydown on a GameboyButton.
158    pub fn handle_keydown(&mut self, button: GameboyButton) {
159        self.mmu.borrow_mut().joypad.keydown(button.into());
160    }
161
162    /// Handle keyup on a GameboyButton.
163    pub fn handle_keyup(&mut self, button: GameboyButton) {
164        self.mmu.borrow_mut().joypad.keyup(button.into());
165    }
166}