tgbr_core/
gameboy.rs

1use anyhow::{bail, Result};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    config::{Config, Model},
6    context::{self, Context},
7    interface::{AudioBuffer, Color, FrameBuffer, Input, LinkCable},
8    rom::{CgbFlag, Rom},
9};
10
11#[derive(Serialize, Deserialize)]
12pub struct GameBoy {
13    rom_hash: [u8; 32],
14    #[serde(flatten)]
15    ctx: context::Context,
16}
17
18impl GameBoy {
19    pub fn new(rom: Rom, backup_ram: Option<Vec<u8>>, config: &Config) -> Result<Self> {
20        let rom_hash = {
21            use sha2::Digest;
22            sha2::Sha256::digest(&rom.data).into()
23        };
24
25        let model = match rom.cgb_flag {
26            CgbFlag::NonCgb => {
27                if config.model == Model::Auto {
28                    Model::Dmg
29                } else {
30                    config.model
31                }
32            }
33            CgbFlag::SupportCgb => {
34                if config.model == Model::Auto {
35                    Model::Cgb
36                } else {
37                    config.model
38                }
39            }
40            CgbFlag::OnlyCgb => {
41                if config.model == Model::Dmg {
42                    bail!("This ROM support only CGB");
43                } else {
44                    Model::Cgb
45                }
46            }
47        };
48
49        log::info!("Model: {model:?}");
50
51        let boot_rom = config.boot_roms.get(model).map(|r| r.to_owned());
52
53        let mut ret = Self {
54            rom_hash,
55            ctx: Context::new(model, rom, &boot_rom, backup_ram, &config.dmg_palette),
56        };
57
58        if boot_rom.is_none() {
59            // Do not use boot ROM
60            // Set the values of the state after the boot ROM
61            ret.setup_initial_state();
62        }
63
64        Ok(ret)
65    }
66
67    fn setup_initial_state(&mut self) {
68        match context::Model::model(&self.ctx) {
69            Model::Dmg => {
70                let reg = self.ctx.cpu.register();
71                reg.a = 0x01;
72                reg.f.unpack(0xB0);
73                reg.b = 0x00;
74                reg.c = 0x13;
75                reg.d = 0x00;
76                reg.e = 0xD8;
77                reg.h = 0x01;
78                reg.l = 0x4D;
79                reg.sp = 0xFFFE;
80                reg.pc = 0x0100;
81            }
82            Model::Cgb => {
83                let reg = self.ctx.cpu.register();
84                reg.a = 0x11;
85                reg.f.unpack(0x80);
86                reg.b = 0x00;
87                reg.c = 0x00;
88                reg.d = 0xFF;
89                reg.e = 0x56;
90                reg.h = 0x00;
91                reg.l = 0x0D;
92                reg.sp = 0xFFFE;
93                reg.pc = 0x0100;
94            }
95            _ => unreachable!(),
96        }
97    }
98
99    pub fn reset(&mut self) {
100        use context::*;
101
102        let model = self.ctx.model();
103        let backup_ram = self.backup_ram();
104        let mut rom = crate::rom::Rom::default();
105        std::mem::swap(&mut rom, self.ctx.rom_mut());
106
107        let boot_rom = self.ctx.inner.bus.boot_rom().clone();
108        let dmg_palette = self.ctx.inner.inner.ppu.dmg_palette();
109
110        self.ctx = Context::new(model, rom, &boot_rom, backup_ram, dmg_palette);
111
112        if boot_rom.is_none() {
113            self.setup_initial_state();
114        }
115    }
116
117    pub fn exec_frame(&mut self) {
118        use context::*;
119
120        self.ctx.apu_mut().audio_buffer_mut().buf.clear();
121
122        let start_frame = self.ctx.inner.inner.ppu.frame();
123        while start_frame == self.ctx.inner.inner.ppu.frame() {
124            self.ctx.cpu.step(&mut self.ctx.inner);
125        }
126    }
127
128    pub fn model(&self) -> Model {
129        use context::Model;
130        self.ctx.model()
131    }
132
133    pub fn set_dmg_palette(&mut self, palette: &[Color; 4]) {
134        self.ctx.inner.inner.ppu.set_dmg_palette(palette);
135    }
136
137    pub fn set_input(&mut self, input: &Input) {
138        let io = self.ctx.inner.bus.io();
139        io.set_input(&mut self.ctx.inner.inner, input);
140    }
141
142    pub fn frame_buffer(&self) -> &FrameBuffer {
143        self.ctx.inner.inner.ppu.frame_buffer()
144    }
145
146    pub fn audio_buffer(&self) -> &AudioBuffer {
147        self.ctx.inner.inner.apu.audio_buffer()
148    }
149
150    pub fn backup_ram(&mut self) -> Option<Vec<u8>> {
151        use crate::mbc::MbcTrait;
152        let external_ram = self.ctx.backup_ram();
153        let internal_ram = self.ctx.inner.bus.mbc().internal_ram();
154        assert!(!(external_ram.is_some() && internal_ram.is_some()));
155        if external_ram.is_some() {
156            external_ram
157        } else {
158            internal_ram.map(|r| r.to_vec())
159        }
160    }
161
162    pub fn set_link_cable(&mut self, link_cable: Option<impl LinkCable + Send + Sync + 'static>) {
163        let link_cable = link_cable.map(|r| Box::new(r) as Box<dyn LinkCable + Send + Sync>);
164        self.ctx.inner.bus.io().set_link_cable(link_cable);
165    }
166
167    pub fn save_state(&self) -> Vec<u8> {
168        let mut ret = vec![];
169        ciborium::ser::into_writer(self, &mut ret).unwrap();
170        ret
171    }
172
173    pub fn load_state(&mut self, data: &[u8]) -> Result<()> {
174        use context::*;
175        // TODO: limitation: cannot restore connector
176
177        // Deserialize object
178        let mut gb: GameBoy = ciborium::de::from_reader(data)?;
179
180        // Restore unserialized fields
181        if self.rom_hash != gb.rom_hash {
182            bail!("ROM hash mismatch");
183        }
184
185        std::mem::swap(self.ctx.rom_mut(), gb.ctx.rom_mut());
186        *self = gb;
187
188        Ok(())
189    }
190}