smsgg.rsannotatedsmsgg.rssource193 lines · 6.4 KB · raw
1use crate::config::{CommonConfig, SmsGgConfig};
2use std::fs;
3
4use crate::mainloop::{CreatedEmulator, NativeDebugFn, create, file_name_no_ext};
5use crate::{NativeEmulator, NativeEmulatorError, NativeEmulatorResult, extensions};
6
7use crate::mainloop::create::{CreatableEmulator, ReadInputResult};
8use jgenesis_common::frontend::SaveWriter;
9use jgenesis_native_config::common::WindowSize;
10use jgenesis_native_config::input::mappings::ButtonMappingVec;
11use smsgg_core::{SmsGgEmulator, SmsGgHardware};
12use std::path::PathBuf;
13
14pub type NativeSmsGgEmulator = NativeEmulator<SmsGgEmulator>;
15
16trait SmsGgHardwareExt: Sized + Copy {
17    fn bios_path(self, config: &SmsGgConfig) -> Option<&PathBuf>;
18
19    fn no_bios_error(self) -> NativeEmulatorError;
20
21    fn standard_extension(self) -> &'static str;
22
23    fn boot_from_bios(self, config: &SmsGgConfig) -> bool;
24}
25
26impl SmsGgHardwareExt for SmsGgHardware {
27    fn bios_path(self, config: &SmsGgConfig) -> Option<&PathBuf> {
28        match self {
29            Self::MasterSystem => config.sms_bios_path.as_ref(),
30            Self::GameGear => config.gg_bios_path.as_ref(),
31            Self::Sg1000 => None,
32        }
33    }
34
35    fn no_bios_error(self) -> NativeEmulatorError {
36        match self {
37            Self::MasterSystem | Self::Sg1000 => NativeEmulatorError::SmsNoBios,
38            Self::GameGear => NativeEmulatorError::GgNoBios,
39        }
40    }
41
42    fn standard_extension(self) -> &'static str {
43        match self {
44            Self::MasterSystem => "sms",
45            Self::GameGear => "gg",
46            Self::Sg1000 => "sg",
47        }
48    }
49
50    fn boot_from_bios(self, config: &SmsGgConfig) -> bool {
51        match self {
52            Self::MasterSystem => config.sms_boot_from_bios,
53            Self::GameGear => config.gg_boot_from_bios,
54            Self::Sg1000 => false,
55        }
56    }
57}
58
59impl CreatableEmulator for SmsGgEmulator {
60    type NativeConfig = SmsGgConfig;
61    type CreateInput = Vec<u8>;
62
63    fn read_create_input(
64        config: &Self::NativeConfig,
65    ) -> NativeEmulatorResult<ReadInputResult<Self::CreateInput>> {
66        if config.run_without_cartridge {
67            let hardware = config.hardware.unwrap_or_else(|| {
68                log::error!(
69                    "run_without_cartridge set without specifying hardware; this is probably a bug"
70                );
71                SmsGgHardware::MasterSystem
72            });
73
74            let Some(bios_path) = hardware.bios_path(config) else {
75                return Err(hardware.no_bios_error());
76            };
77
78            let bios_rom = fs::read(bios_path).map_err(|source| {
79                NativeEmulatorError::SmsGgBiosRead { path: bios_path.clone(), source }
80            })?;
81
82            Ok(ReadInputResult {
83                input: bios_rom,
84                rom_path: bios_path.clone(),
85                save_extension: hardware.standard_extension().into(),
86            })
87        } else {
88            create::read_rom_file(&config.common.rom_file_path, &extensions::SMSGG)
89        }
90    }
91
92    fn create(
93        input: ReadInputResult<Self::CreateInput>,
94        config: &Self::NativeConfig,
95        save_writer: &mut impl SaveWriter,
96    ) -> NativeEmulatorResult<CreatedEmulator<Self>> {
97        let rom: Option<Vec<u8>>;
98        let bios_rom: Option<Vec<u8>>;
99        let hardware: SmsGgHardware;
100        let rom_title: String;
101
102        if config.run_without_cartridge {
103            rom = None;
104            bios_rom = Some(input.input);
105            hardware = config.hardware.unwrap_or(SmsGgHardware::MasterSystem);
106            rom_title = "(BIOS)".into();
107        } else {
108            rom = Some(input.input);
109
110            hardware = config.hardware.unwrap_or_else(|| hardware_for_ext(&input.save_extension));
111
112            bios_rom = if hardware.boot_from_bios(config) {
113                let Some(bios_path) = hardware.bios_path(config) else {
114                    return Err(hardware.no_bios_error());
115                };
116                Some(fs::read(bios_path).map_err(|source| NativeEmulatorError::SmsGgBiosRead {
117                    path: bios_path.clone(),
118                    source,
119                })?)
120            } else {
121                None
122            };
123
124            rom_title = file_name_no_ext(&input.rom_path)?;
125        }
126
127        let emulator = SmsGgEmulator::create(
128            rom,
129            bios_rom,
130            hardware,
131            config.emulator_config.clone(),
132            save_writer,
133        );
134
135        let window_title = match hardware {
136            SmsGgHardware::MasterSystem => format!("sms - {rom_title}"),
137            SmsGgHardware::GameGear => format!("gg - {rom_title}"),
138            SmsGgHardware::Sg1000 => format!("sg1000 - {rom_title}"),
139        };
140
141        let default_window_size = match hardware {
142            SmsGgHardware::MasterSystem | SmsGgHardware::Sg1000 => WindowSize::new_sms(
143                config.common.initial_window_size,
144                config.emulator_config.sms_aspect_ratio,
145            ),
146            SmsGgHardware::GameGear => WindowSize::new_game_gear(
147                config.common.initial_window_size,
148                config.emulator_config.gg_aspect_ratio,
149            ),
150        };
151
152        Ok(CreatedEmulator { emulator, window_title, default_window_size })
153    }
154
155    fn common_config(config: &Self::NativeConfig) -> &CommonConfig {
156        &config.common
157    }
158
159    fn emulator_config(config: &Self::NativeConfig) -> &Self::Config {
160        &config.emulator_config
161    }
162
163    fn input_mappings(config: &Self::NativeConfig) -> ButtonMappingVec<'_, Self::Button> {
164        config.inputs.to_mapping_vec()
165    }
166
167    fn turbo_input_mappings(config: &Self::NativeConfig) -> ButtonMappingVec<'_, Self::Button> {
168        config.inputs.to_turbo_mapping_vec()
169    }
170
171    fn debug_fn() -> Option<NativeDebugFn<Self>> {
172        Some(|| {
173            jgenesis_debugger_frontend::partial_clone_debug_fn(
174                jgenesis_debugger_frontend::smsgg::render_fn(),
175            )
176        })
177    }
178}
179
180fn hardware_for_ext(extension: &str) -> SmsGgHardware {
181    let extension = extension.to_ascii_lowercase();
182
183    if extensions::MASTER_SYSTEM.contains(&extension.as_str()) {
184        SmsGgHardware::MasterSystem
185    } else if extensions::GAME_GEAR.contains(&extension.as_str()) {
186        SmsGgHardware::GameGear
187    } else if extensions::SG_1000.contains(&extension.as_str()) {
188        SmsGgHardware::Sg1000
189    } else {
190        log::error!("Unrecognized file extension '{extension}', defaulting to SMS mode");
191        SmsGgHardware::MasterSystem
192    }
193}