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}