rust_libretro/
lib.rs

1#![allow(clippy::missing_safety_doc)]
2#![doc(
3    html_logo_url = "https://raw.githubusercontent.com/max-m/rust-libretro/master/media/logo.png",
4    html_favicon_url = "https://raw.githubusercontent.com/max-m/rust-libretro/master/media/favicon.png"
5)]
6
7#[cfg(feature = "log")]
8mod logger;
9
10mod core_wrapper;
11mod macros;
12
13pub mod contexts;
14pub mod core;
15pub mod environment;
16pub mod types;
17pub mod util;
18
19pub use const_str;
20pub use macros::*;
21pub use rust_libretro_proc as proc;
22pub use rust_libretro_sys as sys;
23
24use crate::{contexts::*, core::Core, core_wrapper::CoreWrapper, sys::*, types::*, util::*};
25use std::{
26    ffi::*,
27    os::raw::c_char,
28    path::{Path, PathBuf},
29    sync::Arc,
30};
31
32#[doc(hidden)]
33static mut RETRO_INSTANCE: Option<CoreWrapper> = None;
34
35/// This macro must be used to initialize your [`Core`].
36///
37/// # Examples
38/// ```rust
39/// # use rust_libretro::{contexts::*, core::{Core, CoreOptions}, sys::*, types::*, retro_core};
40/// # use std::ffi::CString;
41/// struct ExampleCore {
42///     option_1: bool,
43///     option_2: bool,
44///
45///     pixels: [u8; 800 * 600 * 4],
46///     timer: i64,
47///     even: bool,
48/// }
49/// retro_core!(ExampleCore {
50///     option_1: false,
51///     option_2: true,
52///
53///     pixels: [0; 800 * 600 * 4],
54///     timer: 5_000_001,
55///     even: true,
56/// });
57///
58/// /// Dummy implementation
59/// impl CoreOptions for ExampleCore {}
60/// impl Core for ExampleCore {
61///     fn get_info(&self) -> SystemInfo {
62///         SystemInfo {
63///             library_name: CString::new("ExampleCore").unwrap(),
64///             library_version: CString::new("1.0.0").unwrap(),
65///             valid_extensions: CString::new("").unwrap(),
66///             need_fullpath: false,
67///             block_extract: false,
68///         }
69///     }
70///     fn on_get_av_info(&mut self, _ctx: &mut GetAvInfoContext) -> retro_system_av_info {
71///         retro_system_av_info {
72///             geometry: retro_game_geometry {
73///                 base_width: 800,
74///                 base_height: 600,
75///                 max_width: 800,
76///                 max_height: 600,
77///                 aspect_ratio: 0.0,
78///             },
79///             timing: retro_system_timing {
80///                 fps: 60.0,
81///                 sample_rate: 0.0,
82///             },
83///         }
84///     }
85///     fn on_init(&mut self, ctx: &mut InitContext) { }
86/// }
87/// ```
88#[macro_export]
89macro_rules! retro_core {
90    ( $( $definition:tt )+ ) => {
91        #[doc(hidden)]
92        #[inline(never)]
93        #[no_mangle]
94        pub unsafe extern "Rust" fn __retro_init_core() {
95            $crate::set_core($($definition)+);
96        }
97    }
98}
99
100#[doc(hidden)]
101macro_rules! forward {
102    ($(#[doc = $doc:tt ], )* $wrapper:ident, $name:ident, $handler:ident $(-> $return_type:ty)?, $($context:tt)+) => {
103        #[no_mangle]
104        $(#[doc = $doc])*
105        pub unsafe extern "C" fn $name() $(-> $return_type)? {
106            // Check that the instance has been created
107            if let Some($wrapper) = RETRO_INSTANCE.as_mut() {
108                // Forward to the Core implementation
109                let mut ctx = $($context)+;
110                return $wrapper.core.$handler(&mut ctx);
111            }
112
113            panic!(concat!(stringify!($name), ": Core has not been initialized yet!"));
114        }
115    };
116}
117
118#[doc(hidden)]
119macro_rules! callback {
120    ($(#[doc = $doc:tt ], )* $name:ident, $arg:ident, $handler:ident) => {
121        #[no_mangle]
122        $(#[doc = $doc])*
123        pub unsafe extern "C" fn $name(arg1: $arg) {
124            // Check that the instance has been created
125            if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
126                if arg1.is_some() {
127                    // We were given a callback, make sure that it’s not a NULL pointer
128                    if (arg1.unwrap() as *const c_void).is_null() {
129                        panic!(concat!(
130                            "Expected ",
131                            stringify!($arg),
132                            " got NULL pointer instead!"
133                        ));
134                    }
135                }
136
137                // The callback is safe to set. Either it’s None or not a NULL pointer
138                return wrapper.$handler(arg1);
139            }
140
141            panic!(concat!(
142                stringify!($name),
143                ": Core has not been initialized yet!"
144            ));
145        }
146    };
147}
148
149#[doc(hidden)]
150pub fn set_core<C: 'static + Core>(core: C) {
151    unsafe {
152        if RETRO_INSTANCE.is_some() {
153            let core = &RETRO_INSTANCE.as_ref().unwrap().core;
154            let info = core.get_info();
155            let name = info.library_name.into_string().unwrap();
156            let version = info.library_version.into_string().unwrap();
157
158            panic!("Attempted to set a core after the system was already initialized.\nAlready registered core: {} {}", name, version)
159        }
160
161        RETRO_INSTANCE.replace(CoreWrapper::new(core));
162    }
163}
164
165#[cfg(feature = "log")]
166#[doc(hidden)]
167fn init_log(env_callback: retro_environment_t) {
168    let retro_logger = unsafe { environment::get_log_callback(env_callback) };
169
170    let retro_logger = if let Ok(Some(log_callback)) = retro_logger {
171        logger::RetroLogger::new(log_callback)
172    } else {
173        logger::RetroLogger::new(retro_log_callback { log: None })
174    };
175
176    log::set_max_level(log::LevelFilter::Trace);
177    log::set_boxed_logger(Box::new(retro_logger)).expect("could not set logger");
178}
179
180/*****************************************************************************\
181|                              CORE API FUNCTIONS                             |
182\*****************************************************************************/
183
184forward!(
185    #[doc = "Notifies the [`Core`] when all cheats should be unapplied."],
186    wrapper,
187    retro_cheat_reset,
188    on_cheat_reset,
189    GenericContext::new(&wrapper.environment_callback, Arc::clone(&wrapper.interfaces))
190);
191forward!(
192    #[doc = "Notifies the [`Core`] when it is being closed and its resources should be freed."],
193    wrapper,
194    retro_deinit,
195    on_deinit,
196    GenericContext::new(&wrapper.environment_callback, Arc::clone(&wrapper.interfaces))
197);
198forward!(
199    #[doc = "Called when the frontend needs region information from the [`Core`]."],
200    #[doc = ""],
201    #[doc = "## Note about RetroArch:"],
202    #[doc = "RetroArch doesn’t use this interface anymore, because [`retro_get_system_av_info`] provides similar information."],
203    wrapper,
204    retro_get_region,
205    on_get_region -> std::os::raw::c_uint,
206    GenericContext::new(&wrapper.environment_callback, Arc::clone(&wrapper.interfaces))
207);
208forward!(
209    #[doc = "Notifies the [`Core`] when the current game should be reset."],
210    wrapper,
211    retro_reset,
212    on_reset,
213    GenericContext::new(&wrapper.environment_callback, Arc::clone(&wrapper.interfaces))
214);
215forward!(
216    #[doc = "Called when the frontend needs to know how large a buffer to allocate for save states."],
217    #[doc = ""],
218    #[doc = "See also [`rust_libretro_sys::retro_serialize_size`]."],
219    wrapper,
220    retro_serialize_size,
221    get_serialize_size -> usize,
222    GenericContext::new(&wrapper.environment_callback, Arc::clone(&wrapper.interfaces))
223);
224forward!(
225    #[doc = "Notifies the [`Core`] when the currently loaded game should be unloaded. Called before [`retro_deinit`]."],
226    wrapper,
227    retro_unload_game,
228    on_unload_game,
229    GenericContext::new(&wrapper.environment_callback, Arc::clone(&wrapper.interfaces))
230);
231
232callback!(
233    #[doc = "Provides the audio sample callback to the [`Core`]."],
234    #[doc = ""],
235    #[doc = "Guaranteed to have been called before the first call to [`retro_run`] is made."],
236    retro_set_audio_sample,
237    retro_audio_sample_t,
238    on_set_audio_sample
239);
240callback!(
241    #[doc = "Provides the batched audio sample callback to the [`Core`]."],
242    #[doc = ""],
243    #[doc = "Guaranteed to have been called before the first call to [`retro_run`] is made."],
244    retro_set_audio_sample_batch,
245    retro_audio_sample_batch_t,
246    on_set_audio_sample_batch
247);
248callback!(
249    #[doc = "Provides the input polling callback to the [`Core`]."],
250    #[doc = ""],
251    #[doc = "Guaranteed to have been called before the first call to [`retro_run`] is made."],
252    retro_set_input_poll,
253    retro_input_poll_t,
254    on_set_input_poll
255);
256callback!(
257    #[doc = "Provides the input state request callback to the [`Core`]."],
258    #[doc = ""],
259    #[doc = "Guaranteed to have been called before the first call to [`retro_run`] is made."],
260    retro_set_input_state,
261    retro_input_state_t,
262    on_set_input_state
263);
264callback!(
265    #[doc = "Provides the frame drawing callback to the [`Core`]."],
266    #[doc = ""],
267    #[doc = "Guaranteed to have been called before the first call to [`retro_run`] is made."],
268    retro_set_video_refresh,
269    retro_video_refresh_t,
270    on_set_video_refresh
271);
272
273/// Tells the frontend which API version this [`Core`] implements.
274#[no_mangle]
275pub unsafe extern "C" fn retro_api_version() -> std::os::raw::c_uint {
276    #[cfg(feature = "log")]
277    log::trace!("retro_api_version()");
278
279    RETRO_API_VERSION
280}
281
282/// Initializes the [`Core`].
283///
284/// Called after the environment callbacks have been set.
285#[no_mangle]
286pub unsafe extern "C" fn retro_init() {
287    #[cfg(feature = "log")]
288    log::trace!("retro_init()");
289
290    if let Some(mut wrapper) = RETRO_INSTANCE.as_mut() {
291        wrapper.can_dupe = environment::can_dupe(wrapper.environment_callback);
292
293        let mut ctx = InitContext::new(
294            &wrapper.environment_callback,
295            Arc::clone(&wrapper.interfaces),
296        );
297
298        return wrapper.core.on_init(&mut ctx);
299    }
300
301    panic!("retro_init: Core has not been initialized yet!");
302}
303
304/// Provides _statically known_ system info to the frontend.
305///
306/// See also [`rust_libretro_sys::retro_get_system_info`].
307#[no_mangle]
308pub unsafe extern "C" fn retro_get_system_info(info: *mut retro_system_info) {
309    #[cfg(feature = "log")]
310    log::trace!("retro_get_system_info(info = {info:#?})");
311
312    // Make sure that the pointer we got is plausible
313    if info.is_null() {
314        panic!("Expected retro_system_info, got NULL pointer instead!");
315    }
316
317    // We didn’t get a NULL pointer, so this should be safe
318    let info = &mut *info;
319
320    // retro_get_system_info requires statically allocated data
321    // This is unsafe because we mutate a static value.
322    //
323    // TODO: Should this be put behind an Arc<Mutex> or Arc<RwLock>?
324    static mut SYS_INFO: Option<*const SystemInfo> = None;
325
326    let sys_info = {
327        if SYS_INFO.is_none() {
328            extern "Rust" {
329                fn __retro_init_core();
330            }
331            __retro_init_core();
332
333            if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
334                SYS_INFO = Some(Box::into_raw(Box::new(wrapper.core.get_info())));
335            } else {
336                panic!("No core instance found!");
337            }
338        }
339
340        &*SYS_INFO.unwrap()
341    };
342
343    info.library_name = sys_info.library_name.as_ptr();
344    info.library_version = sys_info.library_version.as_ptr();
345    info.valid_extensions = sys_info.valid_extensions.as_ptr();
346    info.need_fullpath = sys_info.need_fullpath;
347    info.block_extract = sys_info.block_extract;
348}
349
350/// Provides audio/video timings and geometry info to the frontend.
351///
352/// Guaranteed to be called only after successful invocation of [`retro_load_game`].
353///
354/// See also [`rust_libretro_sys::retro_get_system_av_info`].
355#[no_mangle]
356pub unsafe extern "C" fn retro_get_system_av_info(info: *mut retro_system_av_info) {
357    #[cfg(feature = "log")]
358    log::trace!("retro_get_system_av_info(info = {info:#?})");
359
360    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
361        // Make sure that the pointer we got is plausible
362        if info.is_null() {
363            panic!("Expected retro_system_av_info, got NULL pointer instead!");
364        }
365
366        // We didn’t get a NULL pointer, so this should be safe
367        let info = &mut *info;
368
369        let mut ctx = GetAvInfoContext::new(
370            &wrapper.environment_callback,
371            Arc::clone(&wrapper.interfaces),
372        );
373
374        let av_info = wrapper.core.on_get_av_info(&mut ctx);
375
376        info.geometry = av_info.geometry;
377        info.timing = av_info.timing;
378
379        return;
380    }
381
382    panic!("retro_get_system_av_info: Core has not been initialized yet!");
383}
384
385/// Provides the environment callback to the [`Core`].
386///
387/// Guaranteed to have been called before [`retro_init`].
388///
389/// **TODO:** This method seems to get called multiple times by RetroArch
390#[no_mangle]
391pub unsafe extern "C" fn retro_set_environment(environment: retro_environment_t) {
392    #[cfg(feature = "log")]
393    log::trace!("retro_set_environment(environment = {environment:#?})");
394
395    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
396        let mut initial = false;
397
398        if let Some(callback) = environment {
399            if !wrapper.environment_set {
400                initial = true;
401                wrapper.environment_set = true;
402
403                #[cfg(feature = "log")]
404                init_log(Some(callback));
405
406                #[cfg(feature = "unstable-env-commands")]
407                {
408                    wrapper.supports_bitmasks = environment::get_input_bitmasks(Some(callback));
409                }
410            }
411
412            wrapper.environment_callback.replace(callback);
413        } else {
414            wrapper.environment_callback.take();
415        }
416
417        let mut ctx = SetEnvironmentContext::new(
418            &wrapper.environment_callback,
419            Arc::clone(&wrapper.interfaces),
420        );
421
422        // Our default implementation of `set_core_options` uses `RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION`,
423        // which seems to only work on the first call to `retro_set_environment`.
424        if initial && !wrapper.core.set_core_options(&ctx) {
425            #[cfg(feature = "log")]
426            log::warn!("Failed to set core options");
427        }
428
429        return wrapper.core.on_set_environment(initial, &mut ctx);
430    }
431
432    panic!("retro_set_environment: Core has not been initialized yet!");
433}
434
435/// Sets the device type to be used for player `port`.
436///
437/// See also [`rust_libretro_sys::retro_set_controller_port_device`].
438#[no_mangle]
439pub unsafe extern "C" fn retro_set_controller_port_device(
440    port: std::os::raw::c_uint,
441    device: std::os::raw::c_uint,
442) {
443    #[cfg(feature = "log")]
444    log::trace!("retro_set_controller_port_device(port = {port}, device = {device})");
445
446    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
447        let mut ctx = GenericContext::new(
448            &wrapper.environment_callback,
449            Arc::clone(&wrapper.interfaces),
450        );
451
452        return wrapper
453            .core
454            .on_set_controller_port_device(port, device, &mut ctx);
455    }
456
457    panic!("retro_set_controller_port_device: Core has not been initialized yet!");
458}
459
460/// Runs the game for one frame.
461///
462/// See also [`rust_libretro_sys::retro_run`].
463#[no_mangle]
464pub unsafe extern "C" fn retro_run() {
465    #[cfg(feature = "log")]
466    log::trace!("retro_run()");
467
468    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
469        if environment::get_variable_update(wrapper.environment_callback) {
470            let mut ctx = OptionsChangedContext::new(
471                &wrapper.environment_callback,
472                Arc::clone(&wrapper.interfaces),
473            );
474
475            wrapper.core.on_options_changed(&mut ctx);
476        }
477
478        if let Some(callback) = wrapper.input_poll_callback {
479            (callback)();
480        }
481
482        let mut ctx = RunContext {
483            environment_callback: &wrapper.environment_callback,
484            interfaces: Arc::clone(&wrapper.interfaces),
485
486            video_refresh_callback: &wrapper.video_refresh_callback,
487            audio_sample_callback: &wrapper.audio_sample_callback,
488            audio_sample_batch_callback: &wrapper.audio_sample_batch_callback,
489            input_poll_callback: &wrapper.input_poll_callback,
490            input_state_callback: &wrapper.input_state_callback,
491
492            can_dupe: wrapper.can_dupe,
493            had_frame: &mut wrapper.had_frame,
494            last_width: &mut wrapper.last_width,
495            last_height: &mut wrapper.last_height,
496            last_pitch: &mut wrapper.last_pitch,
497
498            supports_bitmasks: wrapper.supports_bitmasks,
499        };
500
501        return wrapper.core.on_run(&mut ctx, wrapper.frame_delta.take());
502    }
503
504    panic!("retro_run: Core has not been initialized yet!");
505}
506
507/// Called by the frontend when the [`Core`]s state should be serialized (“save state”).
508/// This function should return [`false`] on error.
509///
510/// This could also be used by a frontend to implement rewind.
511#[no_mangle]
512pub unsafe extern "C" fn retro_serialize(data: *mut std::os::raw::c_void, size: usize) -> bool {
513    #[cfg(feature = "log")]
514    log::trace!("retro_serialize(data = {data:#?}, size = {size})");
515
516    if data.is_null() {
517        #[cfg(feature = "log")]
518        log::warn!("retro_serialize: data is null");
519
520        return false;
521    }
522
523    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
524        let mut ctx = GenericContext::new(
525            &wrapper.environment_callback,
526            Arc::clone(&wrapper.interfaces),
527        );
528
529        // Convert the given buffer into a proper slice
530        let slice = std::slice::from_raw_parts_mut(data as *mut u8, size);
531
532        return wrapper.core.on_serialize(slice, &mut ctx);
533    }
534
535    panic!("retro_serialize: Core has not been initialized yet!");
536}
537
538/// Called by the frontend when a “save state” should be loaded.
539/// This function should return [`false`] on error.
540///
541/// This could also be used by a frontend to implement rewind.
542#[no_mangle]
543pub unsafe extern "C" fn retro_unserialize(data: *const std::os::raw::c_void, size: usize) -> bool {
544    #[cfg(feature = "log")]
545    log::trace!("retro_unserialize(data = {data:#?}, size = {size})");
546
547    if data.is_null() {
548        #[cfg(feature = "log")]
549        log::warn!("retro_unserialize: data is null");
550
551        return false;
552    }
553
554    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
555        let mut ctx = GenericContext::new(
556            &wrapper.environment_callback,
557            Arc::clone(&wrapper.interfaces),
558        );
559
560        // Convert the given buffer into a proper slice
561        let slice = std::slice::from_raw_parts_mut(data as *mut u8, size);
562
563        return wrapper.core.on_unserialize(slice, &mut ctx);
564    }
565
566    panic!("retro_unserialize: Core has not been initialized yet!");
567}
568
569/// Called by the frontend whenever a cheat should be applied.
570///
571/// The format is core-specific but this function lacks a return value,
572/// so a [`Core`] can’t tell the frontend if it failed to parse a code.
573#[no_mangle]
574pub unsafe extern "C" fn retro_cheat_set(
575    index: std::os::raw::c_uint,
576    enabled: bool,
577    code: *const std::os::raw::c_char,
578) {
579    #[cfg(feature = "log")]
580    log::trace!("retro_cheat_set(index = {index}, enabled = {enabled}, code = {code:#?})");
581
582    if code.is_null() {
583        #[cfg(feature = "log")]
584        log::warn!("retro_cheat_set: code is null");
585
586        return;
587    }
588
589    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
590        let mut ctx = GenericContext::new(
591            &wrapper.environment_callback,
592            Arc::clone(&wrapper.interfaces),
593        );
594
595        // Wrap the pointer into a `CStr`.
596        // This assumes the pointer is valid and ends on a null byte.
597        //
598        // For now we’ll let the core handle conversion to Rust `str` or `String`,
599        // as the lack of documentation doesn’t make it clear if the returned string
600        // is encoded as valid UTF-8.
601        let code = CStr::from_ptr(code);
602
603        return wrapper.core.on_cheat_set(index, enabled, code, &mut ctx);
604    }
605
606    panic!("retro_cheat_set: Core has not been initialized yet!");
607}
608
609/// Called by the frontend when a game should be loaded.
610///
611/// A return value of [`true`] indicates success.
612#[no_mangle]
613pub unsafe extern "C" fn retro_load_game(game: *const retro_game_info) -> bool {
614    #[cfg(feature = "log")]
615    log::trace!("retro_load_game(game_type = {game:#?})");
616
617    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
618        let mut ctx = OptionsChangedContext::new(
619            &wrapper.environment_callback,
620            Arc::clone(&wrapper.interfaces),
621        );
622
623        wrapper.core.on_options_changed(&mut ctx);
624
625        let mut ctx = LoadGameContext::new(
626            &wrapper.environment_callback,
627            Arc::clone(&wrapper.interfaces),
628        );
629
630        let status = if game.is_null() {
631            wrapper.core.on_load_game(None, &mut ctx)
632        } else {
633            wrapper.core.on_load_game(Some(*game), &mut ctx)
634        };
635
636        cfg_if::cfg_if! {
637            if #[cfg(feature = "log")] {
638                match status {
639                    Ok(()) => return true,
640                    Err(err) => {
641                        log::error!("Failed to load game: {:?}", err);
642                        return false;
643                    }
644                }
645            }
646            else {
647                return status.is_ok();
648            }
649        }
650    }
651
652    panic!("retro_load_game: Core has not been initialized yet!");
653}
654
655/// See [`rust_libretro_sys::retro_load_game_special`].
656#[no_mangle]
657pub unsafe extern "C" fn retro_load_game_special(
658    game_type: std::os::raw::c_uint,
659    info: *const retro_game_info,
660    num_info: usize,
661) -> bool {
662    #[cfg(feature = "log")]
663    log::trace!(
664        "retro_load_game_special(game_type = {game_type}, info = {info:#?}, num_info = {num_info})"
665    );
666
667    if info.is_null() {
668        #[cfg(feature = "log")]
669        log::warn!("retro_load_game_special: info is null");
670
671        return false;
672    }
673
674    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
675        let mut ctx = OptionsChangedContext::new(
676            &wrapper.environment_callback,
677            Arc::clone(&wrapper.interfaces),
678        );
679
680        wrapper.core.on_options_changed(&mut ctx);
681
682        let mut ctx = LoadGameSpecialContext::new(
683            &wrapper.environment_callback,
684            Arc::clone(&wrapper.interfaces),
685        );
686
687        let status = wrapper
688            .core
689            .on_load_game_special(game_type, info, num_info, &mut ctx);
690
691        cfg_if::cfg_if! {
692            if #[cfg(feature = "log")] {
693                match status {
694                    Ok(()) => return true,
695                    Err(err) => {
696                        log::error!("Failed to load special game: {:?}", err);
697                        return false;
698                    }
699                }
700            }
701            else {
702                return status.is_ok();
703            }
704        }
705    }
706
707    panic!("retro_load_game_special: Core has not been initialized yet!");
708}
709
710/// Returns a mutable pointer to queried memory type.
711/// Return [`std::ptr::null()`] in case this doesn’t apply to your [`Core`].
712///
713/// `id` is one of the `RETRO_MEMORY_*` constants.
714#[no_mangle]
715pub unsafe extern "C" fn retro_get_memory_data(
716    id: std::os::raw::c_uint,
717) -> *mut std::os::raw::c_void {
718    #[cfg(feature = "log")]
719    log::trace!("retro_get_memory_data(id = {id})");
720
721    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
722        let mut ctx = GenericContext::new(
723            &wrapper.environment_callback,
724            Arc::clone(&wrapper.interfaces),
725        );
726
727        return wrapper.core.get_memory_data(id, &mut ctx);
728    }
729
730    panic!("retro_get_memory_data: Core has not been initialized yet!");
731}
732
733/// Returns the size (in bytes) of the queried memory type.
734/// Return `0` in case this doesn’t apply to your [`Core`].
735///
736/// `id` is one of the `RETRO_MEMORY_*` constants.
737#[no_mangle]
738pub unsafe extern "C" fn retro_get_memory_size(id: std::os::raw::c_uint) -> usize {
739    #[cfg(feature = "log")]
740    log::trace!("retro_get_memory_size(id = {id})");
741
742    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
743        let mut ctx = GenericContext::new(
744            &wrapper.environment_callback,
745            Arc::clone(&wrapper.interfaces),
746        );
747
748        return wrapper.core.get_memory_size(id, &mut ctx);
749    }
750
751    panic!("retro_get_memory_size: Core has not been initialized yet!");
752}
753
754/*****************************************************************************\
755|                            NON CORE API FUNCTIONS                           |
756\*****************************************************************************/
757
758/// If enabled by the [`Core`], notifies it when a keyboard button has been pressed or released.
759///
760/// # Parameters
761/// - `down`: `true` if the key has been pressed, `false` if it has been released
762/// - `keycode`: `retro_key` value
763/// - `character`: The text character of the pressed key, encoded as UTF-32.
764/// - `key_modifiers`: `retro_mod` value
765#[no_mangle]
766pub unsafe extern "C" fn retro_keyboard_callback_fn(
767    down: bool,
768    keycode: ::std::os::raw::c_uint,
769    character: u32,
770    key_modifiers: u16,
771) {
772    #[cfg(feature = "log")]
773    log::trace!("retro_keyboard_callback_fn(down = {down}, keycode = {keycode}, character = {character}, key_modifiers = {key_modifiers})");
774
775    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
776        // Not sure why bindgen uses `c_int32` as value type
777        // for the newtype enum on Windows but `c_uint32` on Unix.
778        cfg_if::cfg_if! {
779            if #[cfg(target_family = "windows")] {
780                let keycode = keycode as i32;
781            }
782        };
783
784        return wrapper.core.on_keyboard_event(
785            down,
786            retro_key(keycode),
787            character,
788            retro_mod(key_modifiers.into()),
789        );
790    }
791
792    panic!("retro_keyboard_callback_fn: Core has not been initialized yet!");
793}
794
795/// **TODO:** Documentation.
796#[no_mangle]
797pub unsafe extern "C" fn retro_hw_context_reset_callback() {
798    #[cfg(feature = "log")]
799    log::trace!("retro_hw_context_reset_callback()");
800
801    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
802        let mut ctx = GenericContext::new(
803            &wrapper.environment_callback,
804            Arc::clone(&wrapper.interfaces),
805        );
806
807        return wrapper.core.on_hw_context_reset(&mut ctx);
808    }
809
810    panic!("retro_hw_context_reset_callback: Core has not been initialized yet!");
811}
812
813/// **TODO:** Documentation.
814#[no_mangle]
815pub unsafe extern "C" fn retro_hw_context_destroyed_callback() {
816    #[cfg(feature = "log")]
817    log::trace!("retro_hw_context_destroyed_callback()");
818
819    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
820        let mut ctx = GenericContext::new(
821            &wrapper.environment_callback,
822            Arc::clone(&wrapper.interfaces),
823        );
824
825        return wrapper.core.on_hw_context_destroyed(&mut ctx);
826    }
827
828    panic!("retro_hw_context_destroyed_callback: Core has not been initialized yet!");
829}
830
831/// **TODO:** Documentation
832#[no_mangle]
833pub unsafe extern "C" fn retro_set_eject_state_callback(ejected: bool) -> bool {
834    #[cfg(feature = "log")]
835    log::trace!("retro_set_eject_state_callback(ejected = {ejected})");
836
837    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
838        return wrapper.core.on_set_eject_state(ejected);
839    }
840
841    panic!("retro_set_eject_state_callback: Core has not been initialized yet!");
842}
843
844/// **TODO:** Documentation
845#[no_mangle]
846pub unsafe extern "C" fn retro_get_eject_state_callback() -> bool {
847    #[cfg(feature = "log")]
848    log::trace!("retro_get_eject_state_callback()");
849
850    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
851        return wrapper.core.on_get_eject_state();
852    }
853
854    panic!("retro_get_eject_state_callback: Core has not been initialized yet!");
855}
856
857/// **TODO:** Documentation
858#[no_mangle]
859pub unsafe extern "C" fn retro_get_image_index_callback() -> ::std::os::raw::c_uint {
860    #[cfg(feature = "log")]
861    log::trace!("retro_get_image_index_callback()");
862
863    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
864        return wrapper.core.on_get_image_index();
865    }
866
867    panic!("retro_get_image_index_callback: Core has not been initialized yet!");
868}
869
870/// **TODO:** Documentation
871#[no_mangle]
872pub unsafe extern "C" fn retro_set_image_index_callback(index: ::std::os::raw::c_uint) -> bool {
873    #[cfg(feature = "log")]
874    log::trace!("retro_set_image_index_callback()");
875
876    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
877        return wrapper.core.on_set_image_index(index);
878    }
879
880    panic!("retro_set_image_index_callback: Core has not been initialized yet!");
881}
882
883/// **TODO:** Documentation
884#[no_mangle]
885pub unsafe extern "C" fn retro_get_num_images_callback() -> ::std::os::raw::c_uint {
886    #[cfg(feature = "log")]
887    log::trace!("retro_get_num_images_callback()");
888
889    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
890        return wrapper.core.on_get_num_images();
891    }
892
893    panic!("retro_get_num_images_callback: Core has not been initialized yet!");
894}
895
896/// **TODO:** Documentation
897#[no_mangle]
898pub unsafe extern "C" fn retro_replace_image_index_callback(
899    index: ::std::os::raw::c_uint,
900    info: *const retro_game_info,
901) -> bool {
902    #[cfg(feature = "log")]
903    log::trace!("retro_replace_image_index_callback(index = {index}, info = {info:#?})");
904
905    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
906        return wrapper.core.on_replace_image_index(index, info);
907    }
908
909    panic!("retro_replace_image_index_callback: Core has not been initialized yet!");
910}
911
912/// **TODO:** Documentation
913#[no_mangle]
914pub unsafe extern "C" fn retro_add_image_index_callback() -> bool {
915    #[cfg(feature = "log")]
916    log::trace!("retro_add_image_index_callback()");
917
918    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
919        return wrapper.core.on_add_image_index();
920    }
921
922    panic!("retro_add_image_index_callback: Core has not been initialized yet!");
923}
924
925/// **TODO:** Documentation
926#[no_mangle]
927pub unsafe extern "C" fn retro_set_initial_image_callback(
928    index: ::std::os::raw::c_uint,
929    path: *const ::std::os::raw::c_char,
930) -> bool {
931    #[cfg(feature = "log")]
932    log::trace!("retro_set_initial_image_callback(index = {index}, path = {path:#?})");
933
934    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
935        return wrapper
936            .core
937            .on_set_initial_image(index, CStr::from_ptr(path));
938    }
939
940    panic!("retro_set_initial_image_callback: Core has not been initialized yet!");
941}
942
943/// **TODO:** Documentation
944#[no_mangle]
945pub unsafe extern "C" fn retro_get_image_path_callback(
946    index: ::std::os::raw::c_uint,
947    path: *mut ::std::os::raw::c_char,
948    len: usize,
949) -> bool {
950    #[cfg(feature = "log")]
951    log::trace!("retro_get_image_path_callback(index = {index}, path = {path:#?}, len = {len})");
952
953    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
954        match wrapper.core.on_get_image_path(index) {
955            Some(image_path) => {
956                let image_path = image_path.as_bytes();
957                let buf = std::slice::from_raw_parts_mut(path as *mut u8, len);
958                let len = image_path.len().min(buf.len());
959
960                buf[..len].copy_from_slice(&image_path[..len]);
961                return true;
962            }
963            None => return false,
964        }
965    }
966
967    panic!("retro_get_image_path_callback: Core has not been initialized yet!");
968}
969
970/// **TODO:** Documentation
971#[no_mangle]
972pub unsafe extern "C" fn retro_get_image_label_callback(
973    index: ::std::os::raw::c_uint,
974    label: *mut ::std::os::raw::c_char,
975    len: usize,
976) -> bool {
977    #[cfg(feature = "log")]
978    log::trace!("retro_get_image_label_callback(index = {index}, label = {label:#?}, len = {len})");
979
980    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
981        match wrapper.core.on_get_image_label(index) {
982            Some(image_label) => {
983                let image_label = image_label.as_bytes();
984                let buf = std::slice::from_raw_parts_mut(label as *mut u8, len);
985                let len = image_label.len().min(buf.len());
986
987                buf[..len].copy_from_slice(&image_label[..len]);
988                return true;
989            }
990            None => return false,
991        }
992    }
993
994    panic!("retro_get_image_label_callback: Core has not been initialized yet!");
995}
996
997/// **TODO:** Documentation
998#[no_mangle]
999pub unsafe extern "C" fn retro_frame_time_callback_fn(usec: retro_usec_t) {
1000    #[cfg(feature = "log")]
1001    log::trace!("retro_frame_time_callback_fn(usec = {usec})");
1002
1003    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1004        wrapper.frame_delta = Some(usec);
1005        return;
1006    }
1007
1008    panic!("retro_frame_time_callback_fn: Core has not been initialized yet!");
1009}
1010
1011/// Notifies the [`Core`] when audio data should be written.
1012#[no_mangle]
1013pub unsafe extern "C" fn retro_audio_callback_fn() {
1014    // This is just too noisy, even for trace logging
1015    // #[cfg(feature = "log")]
1016    // log::trace!("retro_audio_callback_fn()");
1017
1018    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1019        let mut ctx = AudioContext {
1020            environment_callback: &wrapper.environment_callback,
1021            interfaces: Arc::clone(&wrapper.interfaces),
1022
1023            audio_sample_callback: &wrapper.audio_sample_callback,
1024            audio_sample_batch_callback: &wrapper.audio_sample_batch_callback,
1025        };
1026
1027        return wrapper.core.on_write_audio(&mut ctx);
1028    }
1029
1030    panic!("retro_audio_callback_fn: Core has not been initialized yet!");
1031}
1032
1033/// Notifies the [`Core`] about the state of the frontend’s audio system.
1034///
1035/// [`true`]: Audio driver in frontend is active, and callback is
1036/// expected to be called regularily.
1037///
1038/// [`false`]: Audio driver in frontend is paused or inactive.
1039///
1040/// Audio callback will not be called until set_state has been
1041/// called with [`true`].
1042///
1043/// Initial state is [`false`] (inactive).
1044#[no_mangle]
1045pub unsafe extern "C" fn retro_audio_set_state_callback_fn(enabled: bool) {
1046    #[cfg(feature = "log")]
1047    log::trace!("retro_audio_set_state_callback_fn(enabled = {enabled})");
1048
1049    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1050        return wrapper.core.on_audio_set_state(enabled);
1051    }
1052
1053    panic!("retro_audio_set_state_callback_fn: Core has not been initialized yet!");
1054}
1055
1056/// **TODO:** Documentation
1057#[no_mangle]
1058pub unsafe extern "C" fn retro_camera_frame_raw_framebuffer_callback(
1059    buffer: *const u32,
1060    width: ::std::os::raw::c_uint,
1061    height: ::std::os::raw::c_uint,
1062    pitch: usize,
1063) {
1064    let buffer_size = height as usize * pitch;
1065    let buffer = std::slice::from_raw_parts(buffer, buffer_size);
1066
1067    #[cfg(feature = "log")]
1068    log::trace!("retro_camera_frame_raw_framebuffer_callback(buffer = &[u32; {}], width = {width}, height = {height}, pitch = {pitch})", buffer.len());
1069
1070    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1071        return wrapper
1072            .core
1073            .on_camera_raw_framebuffer(buffer, width, height, pitch);
1074    }
1075
1076    panic!("retro_camera_frame_raw_framebuffer_callback: Core has not been initialized yet!");
1077}
1078
1079/// **TODO:** Documentation
1080#[no_mangle]
1081pub unsafe extern "C" fn retro_camera_frame_opengl_texture_callback(
1082    texture_id: ::std::os::raw::c_uint,
1083    texture_target: ::std::os::raw::c_uint,
1084    affine: *const f32,
1085) {
1086    #[cfg(feature = "log")]
1087    log::trace!("retro_camera_frame_opengl_texture_callback(texture_id = {texture_id}, texture_target = {texture_target}, affine = {:#?})", std::slice::from_raw_parts(affine, 3 * 3));
1088
1089    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1090        // Packed 3x3 column-major matrix
1091        let matrix = std::slice::from_raw_parts(affine, 3 * 3);
1092        // Convert to fixed size array; we know it contains 9 elements
1093        let matrix: &[f32; 3 * 3] = matrix.try_into().unwrap();
1094
1095        return wrapper
1096            .core
1097            .on_camera_gl_texture(texture_id, texture_target, matrix);
1098    }
1099
1100    panic!("retro_camera_frame_opengl_texture_callback: Core has not been initialized yet!");
1101}
1102
1103/// **TODO:** Documentation
1104#[no_mangle]
1105pub unsafe extern "C" fn retro_camera_initialized_callback() {
1106    #[cfg(feature = "log")]
1107    log::trace!("retro_camera_initialized_callback()");
1108
1109    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1110        let mut ctx = GenericContext::new(
1111            &wrapper.environment_callback,
1112            Arc::clone(&wrapper.interfaces),
1113        );
1114
1115        return wrapper.core.on_camera_initialized(&mut ctx);
1116    }
1117
1118    panic!("retro_camera_initialized_callback: Core has not been initialized yet!");
1119}
1120
1121/// **TODO:** Documentation
1122#[no_mangle]
1123pub unsafe extern "C" fn retro_camera_deinitialized_callback() {
1124    #[cfg(feature = "log")]
1125    log::trace!("retro_camera_deinitialized_callback()");
1126
1127    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1128        let mut ctx = GenericContext::new(
1129            &wrapper.environment_callback,
1130            Arc::clone(&wrapper.interfaces),
1131        );
1132
1133        return wrapper.core.on_camera_deinitialized(&mut ctx);
1134    }
1135
1136    panic!("retro_camera_deinitialized_callback: Core has not been initialized yet!");
1137}
1138
1139/// **TODO:** Documentation
1140#[no_mangle]
1141pub unsafe extern "C" fn retro_location_lifetime_status_initialized_callback() {
1142    #[cfg(feature = "log")]
1143    log::trace!("retro_location_lifetime_status_initialized_callback()");
1144
1145    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1146        let mut ctx = GenericContext::new(
1147            &wrapper.environment_callback,
1148            Arc::clone(&wrapper.interfaces),
1149        );
1150
1151        return wrapper
1152            .core
1153            .on_location_lifetime_status_initialized(&mut ctx);
1154    }
1155
1156    panic!(
1157        "retro_location_lifetime_status_initialized_callback: Core has not been initialized yet!"
1158    );
1159}
1160
1161/// **TODO:** Documentation
1162#[no_mangle]
1163pub unsafe extern "C" fn retro_location_lifetime_status_deinitialized_callback() {
1164    #[cfg(feature = "log")]
1165    log::trace!("retro_location_lifetime_status_deinitialized_callback()");
1166
1167    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1168        let mut ctx = GenericContext::new(
1169            &wrapper.environment_callback,
1170            Arc::clone(&wrapper.interfaces),
1171        );
1172
1173        return wrapper
1174            .core
1175            .on_location_lifetime_status_deinitialized(&mut ctx);
1176    }
1177
1178    panic!(
1179        "retro_location_lifetime_status_deinitialized_callback: Core has not been initialized yet!"
1180    );
1181}
1182
1183/// **TODO:** Documentation
1184#[no_mangle]
1185pub unsafe extern "C" fn retro_get_proc_address_callback(
1186    sym: *const ::std::os::raw::c_char,
1187) -> retro_proc_address_t {
1188    #[cfg(feature = "log")]
1189    log::trace!("retro_get_proc_address_callback({sym:#?})");
1190
1191    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1192        return wrapper.core.on_get_proc_address(CStr::from_ptr(sym));
1193    }
1194
1195    panic!("retro_get_proc_address_callback: Core has not been initialized yet!");
1196}
1197
1198/// **TODO:** Documentation
1199#[no_mangle]
1200pub unsafe extern "C" fn retro_audio_buffer_status_callback_fn(
1201    active: bool,
1202    occupancy: ::std::os::raw::c_uint,
1203    underrun_likely: bool,
1204) {
1205    #[cfg(feature = "log")]
1206    log::trace!("retro_audio_buffer_status_callback_fn(active = {active}, occupancy = {occupancy}, underrun_likely = {underrun_likely})");
1207
1208    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1209        return wrapper
1210            .core
1211            .on_audio_buffer_status(active, occupancy, underrun_likely);
1212    }
1213
1214    panic!("retro_audio_buffer_status_callback_fn: Core has not been initialized yet!");
1215}
1216
1217/// **TODO:** Documentation
1218#[no_mangle]
1219pub unsafe extern "C" fn retro_core_options_update_display_callback_fn() -> bool {
1220    #[cfg(feature = "log")]
1221    log::trace!("retro_core_options_update_display_callback_fn()");
1222
1223    if let Some(wrapper) = RETRO_INSTANCE.as_mut() {
1224        return wrapper.core.on_core_options_update_display();
1225    }
1226
1227    panic!("retro_core_options_update_display_callback_fn: Core has not been initialized yet!");
1228}