apollo_hyper_libretro_bindings/
emulator.rs

1use crate::{buttons::InputPort, error::*};
2use apollo_hyper_api_standard::{ApolloEmulator, ApolloMultiEmulator};
3use apollo_hyper_libretro_sys::*;
4use libc::c_char;
5use libloading::{Library, Symbol};
6use std::{
7    ffi::{c_void, CStr, CString},
8    fs::File,
9    io::Read,
10    marker::PhantomData,
11    panic,
12    path::{Path, PathBuf},
13    ptr,
14};
15
16type NotSendSync = *const [u8; 0];
17
18static mut EMULATOR: *mut EmulatorCore = ptr::null_mut();
19static mut CONTEXT: *mut EmulatorContext = ptr::null_mut();
20
21struct EmulatorCore {
22    core_lib: Box<Library>,
23    #[allow(dead_code)]
24    core_path: CString,
25    system_path: CString,
26    rom_path: CString,
27    core: CoreAPI,
28    _marker: PhantomData<NotSendSync>,
29}
30
31struct EmulatorContext {
32    audio_sample: Vec<i16>,
33    input_ports: [InputPort; 2],
34    frame_ptr: *const c_void,
35    frame_pitch: usize,
36    frame_width: u32,
37    frame_height: u32,
38    pixfmt: PixelFormat,
39    image_depth: usize,
40    memory_map: Vec<MemoryDescriptor>,
41    _marker: PhantomData<NotSendSync>,
42}
43
44// A more pleasant wrapper over MemoryDescriptor
45#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct MemoryRegion {
47    which: usize,
48    pub flags: u64,
49    pub len: usize,
50    pub start: usize,
51    pub offset: usize,
52    pub name: String,
53    pub select: usize,
54    pub disconnect: usize,
55}
56
57// Emulator token must not be send nor sync
58pub struct Emulator {
59    phantom: PhantomData<NotSendSync>,
60    system_info: SystemInfo,
61    system_av_info: SystemAvInfo,
62}
63
64pub struct MultiEmulator {}
65
66pub struct RetroSystemInfo {
67    pub library_name: CString,
68    pub extensions: CString,
69}
70
71fn create_core(core_path: &Path) -> (Box<Library>, CoreAPI) {
72    let suffix = if cfg!(target_os = "windows") {
73        "dll"
74    } else if cfg!(target_os = "macos") {
75        "dylib"
76    } else if cfg!(target_os = "linux") {
77        "so"
78    } else {
79        panic!("Unsupported platform")
80    };
81    let path: PathBuf = core_path.with_extension(suffix);
82    #[cfg(target_os = "linux")]
83    let library: Library = unsafe {
84        // Load library with `RTLD_NOW | RTLD_NODELETE` to fix a SIGSEGV
85        ::libloading::os::unix::Library::open(Some(path), 0x2 | 0x1000)
86            .unwrap()
87            .into()
88    };
89
90    #[cfg(not(target_os = "linux"))]
91    let library = unsafe { Library::new(path).unwrap() };
92    let dll = Box::new(library);
93
94    unsafe {
95        let retro_set_environment = *(dll.get(b"retro_set_environment").unwrap());
96        let retro_set_video_refresh = *(dll.get(b"retro_set_video_refresh").unwrap());
97        let retro_set_audio_sample = *(dll.get(b"retro_set_audio_sample").unwrap());
98        let retro_set_audio_sample_batch = *(dll.get(b"retro_set_audio_sample_batch").unwrap());
99        let retro_set_input_poll = *(dll.get(b"retro_set_input_poll").unwrap());
100        let retro_set_input_state = *(dll.get(b"retro_set_input_state").unwrap());
101        let retro_init = *(dll.get(b"retro_init").unwrap());
102        let retro_deinit = *(dll.get(b"retro_deinit").unwrap());
103        let retro_api_version = *(dll.get(b"retro_api_version").unwrap());
104        let retro_get_system_info = *(dll.get(b"retro_get_system_info").unwrap());
105        let retro_get_system_av_info = *(dll.get(b"retro_get_system_av_info").unwrap());
106        let retro_set_controller_port_device =
107            *(dll.get(b"retro_set_controller_port_device").unwrap());
108        let retro_reset = *(dll.get(b"retro_reset").unwrap());
109        let retro_run = *(dll.get(b"retro_run").unwrap());
110        let retro_serialize_size = *(dll.get(b"retro_serialize_size").unwrap());
111        let retro_serialize = *(dll.get(b"retro_serialize").unwrap());
112        let retro_unserialize = *(dll.get(b"retro_unserialize").unwrap());
113        let retro_cheat_reset = *(dll.get(b"retro_cheat_reset").unwrap());
114        let retro_cheat_set = *(dll.get(b"retro_cheat_set").unwrap());
115        let retro_load_game = *(dll.get(b"retro_load_game").unwrap());
116        let retro_load_game_special = *(dll.get(b"retro_load_game_special").unwrap());
117        let retro_unload_game = *(dll.get(b"retro_unload_game").unwrap());
118        let retro_get_region = *(dll.get(b"retro_get_region").unwrap());
119        let retro_get_memory_data = *(dll.get(b"retro_get_memory_data").unwrap());
120        let retro_get_memory_size = *(dll.get(b"retro_get_memory_size").unwrap());
121        let core = CoreAPI {
122            retro_set_environment,
123            retro_set_video_refresh,
124            retro_set_audio_sample,
125            retro_set_audio_sample_batch,
126            retro_set_input_poll,
127            retro_set_input_state,
128
129            retro_init,
130            retro_deinit,
131
132            retro_api_version,
133
134            retro_get_system_info,
135            retro_get_system_av_info,
136            retro_set_controller_port_device,
137
138            retro_reset,
139            retro_run,
140
141            retro_serialize_size,
142            retro_serialize,
143            retro_unserialize,
144
145            retro_cheat_reset,
146            retro_cheat_set,
147
148            retro_load_game,
149            retro_load_game_special,
150            retro_unload_game,
151
152            retro_get_region,
153            retro_get_memory_data,
154            retro_get_memory_size,
155        };
156        (dll, core)
157    }
158}
159
160impl ApolloMultiEmulator for MultiEmulator {
161    // FIXME Move some of this logic to Emulator::boot
162    fn create_emulator(core_path: &Path, rom_path: &Path) -> Box<dyn ApolloEmulator> {
163        unsafe {
164            assert!(EMULATOR.is_null());
165            assert!(CONTEXT.is_null());
166        }
167
168        let (dll, core) = create_core(core_path);
169
170        let emu = EmulatorCore {
171            core_lib: dll,
172            rom_path: CString::new(rom_path.to_str().unwrap()).unwrap(),
173            core_path: CString::new(core_path.to_str().unwrap()).unwrap(),
174            system_path: { CString::new(core_path.with_extension("").to_str().unwrap()).unwrap() },
175            core: core.clone(),
176            _marker: PhantomData,
177        };
178
179        unsafe {
180            let emup = Box::new(emu);
181            // Store a pointer to the data
182            EMULATOR = Box::leak(emup);
183            // Forget the box so it doesn't drop
184            let ctx = EmulatorContext {
185                audio_sample: Vec::new(),
186                input_ports: [InputPort::new(), InputPort::new()],
187                frame_ptr: ptr::null(),
188                frame_pitch: 0,
189                frame_width: 0,
190                frame_height: 0,
191                pixfmt: PixelFormat::ARGB1555,
192                image_depth: 0,
193                memory_map: Vec::new(),
194                _marker: PhantomData,
195            };
196            // Ditto here for the context
197            let ctxp = Box::new(ctx);
198            CONTEXT = Box::leak(ctxp);
199            let emu = &(*EMULATOR);
200            // Set up callbacks
201            (emu.core.retro_set_environment)(callback_environment);
202            (emu.core.retro_set_video_refresh)(callback_video_refresh);
203            (emu.core.retro_set_audio_sample)(callback_audio_sample);
204            (emu.core.retro_set_audio_sample_batch)(callback_audio_sample_batch);
205            (emu.core.retro_set_input_poll)(callback_input_poll);
206            (emu.core.retro_set_input_state)(callback_input_state);
207            // Load the game
208            (emu.core.retro_init)();
209            let rom_cstr = &(*EMULATOR).rom_path;
210
211            let mut rom_file = File::open(rom_path).unwrap();
212            let mut buffer = Vec::new();
213            rom_file.read_to_end(&mut buffer).unwrap();
214            buffer.shrink_to_fit();
215            let game_info = GameInfo {
216                path: rom_cstr.as_ptr(),
217                data: buffer.as_ptr() as *const c_void,
218                size: buffer.len(),
219                meta: ptr::null(),
220            };
221
222            let load_game_successful = (emu.core.retro_load_game)(&game_info);
223            assert!(load_game_successful);
224
225            let mut system_info = SystemInfo {
226                library_name: ptr::null(),
227                library_version: ptr::null(),
228                valid_extensions: ptr::null(),
229                need_fullpath: false,
230                block_extract: false,
231            };
232            let mut system_av_info = SystemAvInfo {
233                geometry: GameGeometry {
234                    base_width: 0,
235                    base_height: 0,
236                    max_width: 0,
237                    max_height: 0,
238                    aspect_ratio: 0.0,
239                },
240                timing: SystemTiming {
241                    fps: 0.0,
242                    sample_rate: 0.0,
243                },
244            };
245
246            (core.retro_get_system_info)(&mut system_info);
247            (core.retro_get_system_av_info)(&mut system_av_info);
248
249            Box::new(Emulator {
250                phantom: PhantomData,
251                system_info,
252                system_av_info,
253            })
254        }
255    }
256}
257
258impl ApolloEmulator for Emulator {
259    fn boot(&self, _: &Path) {}
260}
261
262impl Emulator {
263    pub fn create_for_system_info(core_path: &Path) -> RetroSystemInfo {
264        let (_, core) = create_core(core_path);
265        let mut system_info = SystemInfo {
266            library_name: ptr::null(),
267            library_version: ptr::null(),
268            valid_extensions: ptr::null(),
269            need_fullpath: false,
270            block_extract: false,
271        };
272
273        let to_cstring = |ptr| unsafe { CStr::from_ptr(ptr).to_owned() };
274
275        unsafe {
276            (core.retro_get_system_info)(&mut system_info);
277            RetroSystemInfo {
278                library_name: to_cstring(system_info.library_name),
279                extensions: to_cstring(system_info.valid_extensions),
280            }
281        }
282    }
283
284    pub fn get_library(&mut self) -> &Library {
285        unsafe { &(*EMULATOR).core_lib }
286    }
287    pub fn get_symbol<'a, T>(&'a self, symbol: &[u8]) -> Option<Symbol<'a, T>> {
288        let dll = unsafe { &(*EMULATOR).core_lib };
289        let sym: Result<Symbol<T>, _> = unsafe { dll.get(symbol) };
290        if sym.is_err() {
291            return None;
292        }
293        Some(sym.unwrap())
294    }
295    pub fn run(&mut self, inputs: [InputPort; 2]) {
296        unsafe {
297            //clear audio buffers and whatever else
298            (*CONTEXT).audio_sample.clear();
299            //set inputs on CB
300            (*CONTEXT).input_ports = inputs;
301            //run one step
302            ((*EMULATOR).core.retro_run)()
303        }
304    }
305    pub fn reset(&mut self) {
306        unsafe {
307            //clear audio buffers and whatever else
308            (*CONTEXT).audio_sample.clear();
309            //clear inputs on CB
310            (*CONTEXT).input_ports = [InputPort::new(), InputPort::new()];
311            //clear fb
312            (*CONTEXT).frame_ptr = ptr::null();
313            ((*EMULATOR).core.retro_reset)()
314        }
315    }
316    fn get_ram_size(&self, rtype: libc::c_uint) -> usize {
317        unsafe { ((*EMULATOR).core.retro_get_memory_size)(rtype) }
318    }
319    pub fn get_video_ram_size(&self) -> usize {
320        self.get_ram_size(MEMORY_VIDEO_RAM)
321    }
322    pub fn get_system_ram_size(&self) -> usize {
323        self.get_ram_size(MEMORY_SYSTEM_RAM)
324    }
325    pub fn get_save_ram_size(&self) -> usize {
326        self.get_ram_size(MEMORY_SAVE_RAM)
327    }
328    pub fn video_ram_ref(&self) -> &[u8] {
329        self.get_ram(MEMORY_VIDEO_RAM)
330    }
331    pub fn system_ram_ref(&self) -> &[u8] {
332        self.get_ram(MEMORY_SYSTEM_RAM)
333    }
334    pub fn system_ram_mut(&mut self) -> &mut [u8] {
335        self.get_ram_mut(MEMORY_SYSTEM_RAM)
336    }
337    pub fn save_ram(&self) -> &[u8] {
338        self.get_ram(MEMORY_SAVE_RAM)
339    }
340
341    fn get_ram(&self, ramtype: libc::c_uint) -> &[u8] {
342        let len = self.get_ram_size(ramtype);
343        unsafe {
344            let ptr: *const u8 = ((*EMULATOR).core.retro_get_memory_data)(ramtype).cast();
345            std::slice::from_raw_parts(ptr, len)
346        }
347    }
348
349    fn get_ram_mut(&mut self, ramtype: libc::c_uint) -> &mut [u8] {
350        let len = self.get_ram_size(ramtype);
351        unsafe {
352            let ptr: *mut u8 = ((*EMULATOR).core.retro_get_memory_data)(ramtype).cast();
353            std::slice::from_raw_parts_mut(ptr, len)
354        }
355    }
356
357    pub fn memory_regions(&self) -> Vec<MemoryRegion> {
358        let map = unsafe { &((*CONTEXT).memory_map) };
359        map.iter()
360            .enumerate()
361            .map(|(i, mdesc)| MemoryRegion {
362                which: i,
363                flags: mdesc.flags,
364                len: mdesc.len,
365                start: mdesc.start,
366                offset: mdesc.offset,
367                select: mdesc.select,
368                disconnect: mdesc.disconnect,
369                name: if mdesc.addrspace.is_null() {
370                    "".to_owned()
371                } else {
372                    unsafe { CStr::from_ptr(mdesc.addrspace) }
373                        .to_string_lossy()
374                        .into_owned()
375                },
376            })
377            .collect()
378    }
379    pub fn memory_ref(&self, start: usize) -> Result<&[u8], RetroRsError> {
380        for mr in self.memory_regions() {
381            if mr.select != 0 && (start & mr.select) == 0 {
382                continue;
383            }
384            if start >= mr.start && start < mr.start + mr.len {
385                return self.memory_ref_mut(mr, start).map(|slice| &*slice);
386            }
387        }
388        Err(RetroRsError::RAMCopyNotMappedIntoMemoryRegionError)
389    }
390    pub fn memory_ref_mut(
391        &self,
392        mr: MemoryRegion,
393        start: usize,
394    ) -> Result<&mut [u8], RetroRsError> {
395        let maps = unsafe { &(*CONTEXT).memory_map };
396        if mr.which >= maps.len() {
397            // TODO more aggressive checking of mr vs map
398            return Err(RetroRsError::RAMMapOutOfRangeError);
399        }
400        if start < mr.start {
401            return Err(RetroRsError::RAMCopySrcOutOfBoundsError);
402        }
403        let start = (start - mr.start) & !mr.disconnect;
404        let map = &maps[mr.which];
405        //0-based at this point, modulo offset
406        let ptr: *mut u8 = map.ptr.cast();
407        let slice = unsafe {
408            let ptr = ptr.add(start).add(map.offset);
409            std::slice::from_raw_parts_mut(ptr, map.len - start)
410        };
411        Ok(slice)
412    }
413
414    pub fn pixel_format(&self) -> PixelFormat {
415        unsafe { (*CONTEXT).pixfmt }
416    }
417    pub fn framebuffer_size(&self) -> (usize, usize) {
418        unsafe {
419            (
420                (*CONTEXT).frame_width as usize,
421                (*CONTEXT).frame_height as usize,
422            )
423        }
424    }
425    pub fn framebuffer_pitch(&self) -> usize {
426        unsafe { (*CONTEXT).frame_pitch }
427    }
428    pub fn peek_framebuffer<FBPeek, FBPeekRet>(&self, f: FBPeek) -> Result<FBPeekRet, RetroRsError>
429    where
430        FBPeek: FnOnce(&[u8]) -> FBPeekRet,
431    {
432        unsafe {
433            if (*CONTEXT).frame_ptr.is_null() {
434                Err(RetroRsError::NoFramebufferError)
435            } else {
436                let frame_slice = std::slice::from_raw_parts(
437                    (*CONTEXT).frame_ptr as *const u8,
438                    ((*CONTEXT).frame_height * ((*CONTEXT).frame_pitch as u32)) as usize,
439                );
440                Ok(f(frame_slice))
441            }
442        }
443    }
444
445    pub fn peek_audio_buffer<F, R>(&self, f: F) -> Result<R, RetroRsError>
446    where
447        F: FnOnce(&[i16]) -> R,
448    {
449        unsafe {
450            let slice = &(*CONTEXT).audio_sample[..];
451            Ok(f(slice))
452        }
453    }
454
455    pub fn save(&self, bytes: &mut [u8]) {
456        let size = self.save_size();
457        assert!(bytes.len() >= size);
458        unsafe { ((*EMULATOR).core.retro_serialize)(bytes.as_mut_ptr() as *mut c_void, size) }
459    }
460    pub fn load(&mut self, bytes: &[u8]) -> bool {
461        let size = self.save_size();
462        assert!(bytes.len() >= size);
463        unsafe { ((*EMULATOR).core.retro_unserialize)(bytes.as_ptr() as *const c_void, size) }
464    }
465    pub fn save_size(&self) -> usize {
466        unsafe { ((*EMULATOR).core.retro_serialize_size)() }
467    }
468    pub fn clear_cheats(&mut self) {
469        unsafe { ((*EMULATOR).core.retro_cheat_reset)() }
470    }
471    pub fn set_cheat(&mut self, index: usize, enabled: bool, code: &str) {
472        unsafe {
473            // FIXME: Creates a memory leak since the libretro api won't let me from_raw() it back and drop it.  I don't know if libretro guarantees anything about ownership of this str to cores.
474            ((*EMULATOR).core.retro_cheat_set)(
475                index as u32,
476                enabled,
477                CString::new(code).unwrap().into_raw(),
478            )
479        }
480    }
481
482    pub fn system_info(&self) -> &SystemInfo {
483        &self.system_info
484    }
485
486    pub fn system_av_info(&self) -> &SystemAvInfo {
487        &self.system_av_info
488    }
489}
490
491unsafe extern "C" fn callback_environment(cmd: u32, data: *mut c_void) -> bool {
492    let result = panic::catch_unwind(|| {
493        match cmd {
494            ENVIRONMENT_SET_CONTROLLER_INFO => true,
495            ENVIRONMENT_SET_PIXEL_FORMAT => {
496                let pixfmti = *(data as *const u32);
497                let pixfmt = PixelFormat::from_uint(pixfmti);
498                if pixfmt.is_none() {
499                    return false;
500                }
501                let pixfmt = pixfmt.unwrap();
502                (*CONTEXT).image_depth = match pixfmt {
503                    PixelFormat::ARGB1555 => 15,
504                    PixelFormat::ARGB8888 => 32,
505                    PixelFormat::RGB565 => 16,
506                };
507                (*CONTEXT).pixfmt = pixfmt;
508                true
509            }
510            ENVIRONMENT_GET_SYSTEM_DIRECTORY => {
511                *(data as *mut *const c_char) = (*EMULATOR).system_path.as_ptr();
512                println!("{}", (*EMULATOR).system_path.to_str().unwrap());
513                true
514            }
515            ENVIRONMENT_GET_CAN_DUPE => {
516                *(data as *mut bool) = true;
517                true
518            }
519            ENVIRONMENT_SET_MEMORY_MAPS => {
520                let map = data as *const MemoryMap;
521                let desc_slice =
522                    std::slice::from_raw_parts((*map).descriptors, (*map).num_descriptors as usize);
523                // Don't know who owns map or how long it will last
524                (*CONTEXT).memory_map = Vec::new();
525                // So we had better copy it
526                (*CONTEXT).memory_map.extend_from_slice(desc_slice);
527                // (Implicitly we also want to drop the old one, which we did by reassigning)
528                true
529            }
530
531            // Added by Aldo Acevedo (2022)
532            ENVIRONMENT_GET_VARIABLE => {
533                let variable = data as *const Variable;
534                let key = CStr::from_ptr((*(variable)).key).to_str().unwrap();
535
536                let value = match key {
537                    //"pcsx2_bios" => Some("SCPH-39001 NA 160-mar.bin"),
538                    "pcsx2_fastboot" => Some("disabled"),
539                    "pcsx2_memcard_slot_1" => Some("shared8"),
540                    "pcsx2_memcard_slot_2" => Some("empty"),
541                    "pcsx2_renderer" => Some("Auto"),
542
543                    "pcsx2_upscale_multiplier" => Some("1"),
544                    "pcsx2_palette_conversion" => Some("disabled"),
545                    "pcsx2_userhack_fb_conversion" => Some("disabled"),
546                    "pcsx2_userhack_auto_flush" => Some("disabled"),
547                    "pcsx2_fast_invalidation" => Some("disabled"),
548                    "pcsx2_speedhacks_presets" => Some("1"),
549                    "pcsx2_fastcdvd" => Some("disabled"),
550                    "pcsx2_deinterlace_mode" => Some("7"),
551                    "pcsx2_enable_60fps_patches" => Some("disabled"),
552                    "pcsx2_enable_widescreen_patches" => Some("disabled"),
553                    "pcsx2_frameskip" => Some("disabled"),
554                    "pcsx2_frames_to_draw" => Some("1"),
555                    "pcsx2_frames_to_skip" => Some("1"),
556                    "pcsx2_vsync_mtgs_queue" => Some("2"),
557                    "pcsx2_enable_cheats" => Some("disabled"),
558                    "pcsx2_clamping_mode" => Some("1"),
559                    "pcsx2_round_mode" => Some("3"),
560                    "pcsx2_vu_clamping_mode" => Some("1"),
561                    "pcsx2_vu_round_mode" => Some("3"),
562                    "pcsx2_gamepad_l_deadzone" => Some("0"),
563                    "pcsx2_gamepad_r_deadzone" => Some("0"),
564                    "pcsx2_rumble_intensity" => Some("100"),
565                    "pcsx2_rumble_enable" => Some("enabled"),
566
567                    "beetle_psx_renderer" => Some("software"),
568                    "beetle_psx_hw_renderer" => Some("software"),
569
570                    "desmume_pointer_mouse" => Some("enabled"),
571                    "desmume_pointer_device_l" => Some("absolute"),
572                    "desmume_pointer_device_r" => Some("absolute"),
573                    //"desmume_pointer_type" => Some("touch"),
574                    key => {
575                        eprintln!("INTERNAL: Unrecognized variable: {key}");
576                        None
577                    }
578                };
579
580                // Successful if value isn't null
581                value.is_some()
582            }
583            ENVIRONMENT_GET_LOG_INTERFACE => {
584                // Variadic C print function
585                unsafe extern "C" fn log_function(
586                    level: LogLevel,
587                    fmt: *const libc::c_char,
588                    mut args: ...
589                ) {
590                    let level = match level {
591                        LogLevel::Debug => "DEBUG",
592                        LogLevel::Info => "INFO",
593                        LogLevel::Warn => "WARN",
594                        LogLevel::Error => "ERROR",
595                    };
596                    let format_args = printf_compat::output::display(fmt, args.as_va_list());
597                    eprint!("{level}: {format_args}");
598                }
599
600                let log_callback = data as *mut LogCallback;
601                (*log_callback).log = log_function;
602                true
603            }
604            ENVIRONMENT_SET_MESSAGE => {
605                let msg = data as *const Message;
606                eprint!(
607                    "--- MESSAGE: {}",
608                    CStr::from_ptr((*msg).msg).to_str().unwrap()
609                );
610                true
611            }
612            _ => false,
613        }
614    });
615    result.unwrap_or(false)
616}
617
618extern "C" fn callback_video_refresh(data: *const c_void, width: u32, height: u32, pitch: usize) {
619    // Can't panic
620    unsafe {
621        // context's framebuffer just points to the given data.  Seems to work OK for gym-retro.
622        if !data.is_null() {
623            (*CONTEXT).frame_ptr = data;
624            (*CONTEXT).frame_pitch = pitch;
625            (*CONTEXT).frame_width = width;
626            (*CONTEXT).frame_height = height;
627        }
628    }
629}
630extern "C" fn callback_audio_sample(left: i16, right: i16) {
631    // Can't panic
632    unsafe {
633        let sample_buf = &mut (*CONTEXT).audio_sample;
634        sample_buf.push(left);
635        sample_buf.push(right);
636    }
637}
638extern "C" fn callback_audio_sample_batch(data: *const i16, frames: usize) -> usize {
639    // Can't panic
640    unsafe {
641        let sample_buf = &mut (*CONTEXT).audio_sample;
642        let slice = std::slice::from_raw_parts(data, frames * 2);
643        sample_buf.clear();
644        sample_buf.extend_from_slice(slice);
645        frames
646    }
647}
648
649extern "C" fn callback_input_poll() {}
650
651extern "C" fn callback_input_state(port: u32, device: u32, index: u32, id: u32) -> i16 {
652    if port > 1 {
653        println!("Unsupported port {port} (only two controllers are implemented)");
654        return 0;
655    }
656
657    let port = port as usize;
658
659    match (device, index, id) {
660        // RETRO_DEVICE_NONE         0
661        (DEVICE_NONE, ..) => 0,
662        // RETRO_DEVICE_JOYPAD       1
663        (DEVICE_JOYPAD, 0, id) => {
664            if id > 16 {
665                println!("Unexpected button id {id}");
666                return 0;
667            }
668            unsafe {
669                if (*CONTEXT).input_ports[port].buttons.get(id) {
670                    1
671                } else {
672                    0
673                }
674            }
675        }
676        // RETRO_DEVICE_MOUSE        2
677        (DEVICE_MOUSE, 0, id) => unsafe {
678            println!("MOUSE REQUESTED!!!!");
679            let left = (*CONTEXT).input_ports[port].mouse_left_down;
680            let right = (*CONTEXT).input_ports[port].mouse_right_down;
681            let middle = (*CONTEXT).input_ports[port].mouse_middle_down;
682            let bool_to_i16 = |b| if b { 1 } else { 0 };
683
684            match id {
685                DEVICE_ID_MOUSE_X => (*CONTEXT).input_ports[port].mouse_x,
686                DEVICE_ID_MOUSE_Y => (*CONTEXT).input_ports[port].mouse_y,
687                DEVICE_ID_MOUSE_LEFT => bool_to_i16(left),
688                DEVICE_ID_MOUSE_RIGHT => bool_to_i16(right),
689                DEVICE_ID_MOUSE_MIDDLE => bool_to_i16(middle),
690                _ => 0,
691            }
692        },
693        // RETRO_DEVICE_KEYBOARD     3
694        (DEVICE_KEYBOARD, ..) => {
695            println!("Keyboard input method unsupported");
696            0
697        }
698        // RETRO_DEVICE_LIGHTGUN     4
699        (DEVICE_LIGHTGUN, ..) => {
700            println!("Lightgun input method unsupported");
701            0
702        }
703        // RETRO_DEVICE_ANALOG       5
704        //(DEVICE_ANALOG, DEVICE_INDEX_ANALOG_LEFT, DEVICE_ID_JOYPAD_X) => unsafe { (*CONTEXT).input_ports[port].joystick_x },
705        //(DEVICE_ANALOG, DEVICE_INDEX_ANALOG_LEFT, DEVICE_ID_JOYPAD_Y) => unsafe { (*CONTEXT).input_ports[port].joystick_y },
706        // Right not yet implemented
707        (DEVICE_ANALOG, DEVICE_INDEX_ANALOG_LEFT, DEVICE_ID_ANALOG_X) => unsafe {
708            (*CONTEXT).input_ports[port].joystick_x
709        },
710        (DEVICE_ANALOG, DEVICE_INDEX_ANALOG_LEFT, DEVICE_ID_ANALOG_Y) => unsafe {
711            (*CONTEXT).input_ports[port].joystick_y
712        },
713        // RETRO_DEVICE_POINTER      6
714        (6, ..) => {
715            println!("Pointer input method unsupported");
716            0
717        }
718        _ => {
719            println!("Unsupported device/index/id ({device}/{index}/{id})");
720            0
721        }
722    }
723}
724
725impl Drop for Emulator {
726    fn drop(&mut self) {
727        unsafe {
728            ((*EMULATOR).core.retro_unload_game)();
729            ((*EMULATOR).core.retro_deinit)();
730        }
731        //TODO drop memory maps etc
732        unsafe {
733            // "remember" context and emulator we forgot before
734            let _ctx = Box::from_raw(CONTEXT);
735            let _emu = Box::from_raw(EMULATOR);
736            CONTEXT = ptr::null_mut();
737            EMULATOR = ptr::null_mut();
738        }
739        // let them drop naturally
740    }
741}