1use crate::config::{CommonConfig, GenesisConfig};
2use crate::extensions::Console;
3use crate::mainloop::create::{CreatableEmulator, ReadInputResult, WindowTitle};
4use crate::mainloop::{CreatedEmulator, NativeDebugFn, NativeEmulatorError, create};
5use crate::{NativeEmulator, NativeEmulatorResult, extensions};
6use cdrom::reader::CdRom;
7use cdrom::reader::CdRomFileFormat;
8use genesis_config::{GenesisController, GenesisInputs, GenesisRegion};
9use genesis_core::GenesisEmulator;
10use genesis_core::api::GenesisHardware;
11use jgenesis_common::frontend::{EmulatorTrait, SaveWriter};
12use jgenesis_native_config::common::WindowSize;
13use jgenesis_native_config::input::mappings::ButtonMappingVec;
14use segacd_core::api::SegaCdLoadError;
15use std::fs;
16use std::path::{Path, PathBuf};
17
18pub type NativeGenesisEmulator = NativeEmulator<GenesisEmulator>;
19
20#[derive(Clone)]
21pub struct GenesisCreateInput {
22    cartridge_rom: Option<Vec<u8>>,
23    sega_cd_bios_rom: Option<Vec<u8>>,
24    disc_path: Option<PathBuf>,
25}
26
27impl CreatableEmulator for GenesisEmulator {
28    type NativeConfig = GenesisConfig;
29    type CreateInput = GenesisCreateInput;
30
31    fn read_create_input(
32        config: &Self::NativeConfig,
33    ) -> NativeEmulatorResult<ReadInputResult<Self::CreateInput>> {
34        let read_cartridge_input =
35            || create::read_rom_file(&config.common.rom_file_path, &extensions::GENESIS_32X);
36
37        let mut rom_path = config.common.rom_file_path.clone();
38
39        let sega_cd_bios_rom = if config.hardware.has_sega_cd() {
40            let (region, bios_path) = determine_scd_bios_path(config);
41            let Some(bios_path) = bios_path else {
42                return Err(NativeEmulatorError::SegaCdNoBios(region));
43            };
44
45            if config.scd_run_without_disc {
46                rom_path.clone_from(&bios_path);
47            }
48
49            let bios_rom = fs::read(&bios_path).map_err(|source| {
50                NativeEmulatorError::SegaCdBiosRead { path: bios_path, source }
51            })?;
52            Some(bios_rom)
53        } else {
54            None
55        };
56
57        let (cartridge_input, disc_path) = if config.hardware.has_sega_cd() {
58            if config.scd_run_without_disc {
59                (None, None)
60            } else {
61                let primary_path_is_disc =
62                    CdRomFileFormat::from_file_path(&config.common.rom_file_path).is_some();
63                if primary_path_is_disc {
64                    (None, Some(config.common.rom_file_path.clone()))
65                } else {
66                    // Assume primary path is a cartridge ROM image
67                    let cartridge_input = read_cartridge_input()?;
68                    (Some(cartridge_input), config.secondary_path.clone())
69                }
70            }
71        } else {
72            let cartridge_input = read_cartridge_input()?;
73            (Some(cartridge_input), None)
74        };
75
76        let save_extension = match &cartridge_input {
77            Some(input) => input.save_extension.clone(),
78            None => Console::SegaCd.standard_extension().to_owned(),
79        };
80
81        Ok(ReadInputResult {
82            input: GenesisCreateInput {
83                cartridge_rom: cartridge_input.map(|input| input.input),
84                sega_cd_bios_rom,
85                disc_path,
86            },
87            rom_path,
88            save_extension,
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 disc = match &input.input.disc_path {
98            Some(path) => {
99                Some(read_sega_cd_disc(path, config.emulator_config.sega_cd.load_disc_into_ram)?)
100            }
101            None => None,
102        };
103
104        let mut emulator = GenesisEmulator::create(
105            config.hardware,
106            input.input.cartridge_rom,
107            input.input.sega_cd_bios_rom,
108            disc,
109            config.emulator_config.clone(),
110            save_writer,
111        )?;
112
113        let default_window_size = if config.hardware.has_32x() {
114            WindowSize::new_32x(
115                config.common.initial_window_size,
116                emulator.timing_mode(),
117                &config.emulator_config,
118            )
119        } else {
120            WindowSize::new_genesis(
121                config.common.initial_window_size,
122                emulator.timing_mode(),
123                &config.emulator_config,
124            )
125        };
126
127        let window_title = generate_window_title(&mut emulator, config.hardware);
128
129        Ok(CreatedEmulator { emulator, window_title, default_window_size })
130    }
131
132    fn common_config(config: &Self::NativeConfig) -> &CommonConfig {
133        &config.common
134    }
135
136    fn emulator_config(config: &Self::NativeConfig) -> &Self::Config {
137        &config.emulator_config
138    }
139
140    fn reload_native_config(
141        emulator: &mut NativeEmulator<Self>,
142        config: &Self::NativeConfig,
143    ) -> NativeEmulatorResult<()> {
144        update_controller_types(config, &mut emulator.inputs);
145
146        Ok(())
147    }
148
149    fn initial_inputs(config: &Self::NativeConfig) -> Self::Inputs {
150        new_initial_inputs(config)
151    }
152
153    fn input_mappings(config: &Self::NativeConfig) -> ButtonMappingVec<'_, Self::Button> {
154        config.inputs.to_mapping_vec()
155    }
156
157    fn turbo_input_mappings(config: &Self::NativeConfig) -> ButtonMappingVec<'_, Self::Button> {
158        config.inputs.to_turbo_mapping_vec()
159    }
160
161    fn change_disc(
162        &mut self,
163        disc_path: &Path,
164        config: &<Self as EmulatorTrait>::Config,
165    ) -> NativeEmulatorResult<Option<WindowTitle>> {
166        let disc = read_sega_cd_disc(disc_path, config.sega_cd.load_disc_into_ram)?;
167
168        log::info!("Changing to disc read from path '{}'", disc_path.display());
169
170        GenesisEmulator::change_disc(self, disc)?;
171
172        let window_title = generate_window_title(self, self.hardware());
173        Ok(Some(WindowTitle(window_title)))
174    }
175
176    fn remove_disc(&mut self) -> Option<WindowTitle> {
177        GenesisEmulator::remove_disc(self);
178
179        let window_title = generate_window_title(self, self.hardware());
180        Some(WindowTitle(window_title))
181    }
182
183    fn debug_fn() -> Option<NativeDebugFn<Self>> {
184        Some(jgenesis_debugger_frontend::genesis::genesis_debug_fn)
185    }
186}
187
188fn new_initial_inputs(config: &GenesisConfig) -> GenesisInputs {
189    GenesisInputs {
190        p1: GenesisController::new(config.inputs.p1_type),
191        p2: GenesisController::new(config.inputs.p2_type),
192    }
193}
194
195fn update_controller_types(config: &GenesisConfig, inputs: &mut GenesisInputs) {
196    if config.inputs.p1_type != inputs.p1.controller_type() {
197        inputs.p1 = GenesisController::new(config.inputs.p1_type);
198    }
199
200    if config.inputs.p2_type != inputs.p2.controller_type() {
201        inputs.p2 = GenesisController::new(config.inputs.p2_type);
202    }
203}
204
205fn generate_window_title(emulator: &mut GenesisEmulator, hardware: GenesisHardware) -> String {
206    let system_name = hardware.to_string().to_ascii_lowercase();
207    let mut game_title = emulator.game_title().unwrap_or("(no disc)".into());
208    // Remove non-printable characters
209    game_title.retain(|c| {
210        c.is_ascii_alphanumeric() || c.is_ascii_whitespace() || c.is_ascii_punctuation()
211    });
212    format!("{system_name} - {game_title}")
213}
214
215fn read_sega_cd_disc(path: &Path, open_in_memory: bool) -> NativeEmulatorResult<CdRom> {
216    let disc_format = CdRomFileFormat::from_file_path(path).unwrap_or_else(|| {
217        log::warn!("Unable to determine CD-ROM image format; assuming CUE/BIN");
218        CdRomFileFormat::CueBin
219    });
220
221    let disc = if open_in_memory {
222        CdRom::open_in_memory(path, disc_format)
223    } else {
224        CdRom::open(path, disc_format)
225    };
226
227    disc.map_err(|err| NativeEmulatorError::SegaCdDisc(SegaCdLoadError::CdRom(err)))
228}
229
230fn determine_scd_bios_path(config: &GenesisConfig) -> (GenesisRegion, Option<PathBuf>) {
231    if !config.scd_per_region_bios {
232        return (GenesisRegion::Americas, bios_path_for_region(config, GenesisRegion::Americas));
233    }
234
235    if let Some(region) = config.emulator_config.forced_region {
236        return (region, bios_path_for_region(config, region));
237    }
238
239    let file_path = &config.common.rom_file_path;
240    let region = CdRomFileFormat::from_file_path(file_path)
241        .and_then(|cdrom_format| CdRom::open(file_path, cdrom_format).ok())
242        .and_then(|mut disc| {
243            segacd_core::parse_disc_region(&mut disc).ok()
244        })
245        .unwrap_or_else(|| {
246            log::error!("Unable to determine region of disc at '{}' for purposes of selecting BIOS path; defaulting to US", file_path.display());
247            GenesisRegion::Americas
248        });
249
250    (region, bios_path_for_region(config, region))
251}
252
253fn bios_path_for_region(config: &GenesisConfig, region: GenesisRegion) -> Option<PathBuf> {
254    match region {
255        GenesisRegion::Americas => config.scd_us_bios_path.clone(),
256        GenesisRegion::Europe => config.scd_eu_bios_path.clone(),
257        GenesisRegion::Japan => config.scd_jp_bios_path.clone(),
258    }
259}