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 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 let mut gb: GameBoy = ciborium::de::from_reader(data)?;
179
180 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}