libretro_rs/
lib.rs

1pub use libc;
2
3pub mod core_macro;
4pub mod sys;
5
6use libc::{c_char, c_void};
7use std::ffi::CStr;
8use sys::*;
9
10#[allow(unused_variables)]
11pub trait RetroCore {
12  const SUPPORT_NO_GAME: bool = false;
13
14  /// Called when a new instance of the core is needed. The `env` parameter can be used to set-up and/or query values
15  /// required for the core to function properly.
16  fn init(env: &RetroEnvironment) -> Self;
17
18  /// Called to get information about the core. This information can then be displayed in a frontend, or used to
19  /// construct core-specific paths.
20  fn get_system_info() -> RetroSystemInfo;
21
22  /// Called to associate a particular device with a particular port. A core is allowed to ignore this request.
23  fn set_controller_port_device(&mut self, env: &RetroEnvironment, port: u32, device: RetroDevice) {}
24
25  /// Called when a player resets their game.
26  fn reset(&mut self, env: &RetroEnvironment);
27
28  /// Called continuously once the core is initialized and a game is loaded. The core is expected to advance emulation
29  /// by a single frame before returning.
30  fn run(&mut self, env: &RetroEnvironment, runtime: &RetroRuntime);
31
32  /// Called to determine the size of the save state buffer. This is only ever called once per run, and the core must
33  /// not exceed the size returned here for subsequent saves.
34  fn serialize_size(&self, env: &RetroEnvironment) -> usize {
35    0
36  }
37
38  /// Allows a core to save its internal state into the specified buffer. The buffer is guaranteed to be at least `size`
39  /// bytes, where `size` is the value returned from `serialize_size`.
40  fn serialize(&self, env: &RetroEnvironment, data: *mut (), size: usize) -> bool {
41    false
42  }
43
44  /// Allows a core to load its internal state from the specified buffer. The buffer is guaranteed to be at least `size`
45  /// bytes, where `size` is the value returned from `serialize_size`.
46  fn unserialize(&mut self, env: &RetroEnvironment, data: *const (), size: usize) -> bool {
47    false
48  }
49
50  fn cheat_reset(&mut self, env: &RetroEnvironment) {}
51
52  fn cheat_set(&mut self, env: &RetroEnvironment, index: u32, enabled: bool, code: *const libc::c_char) {}
53
54  fn load_game(&mut self, env: &RetroEnvironment, game: RetroGame) -> RetroLoadGameResult;
55
56  fn load_game_special(&mut self, env: &RetroEnvironment, game_type: u32, info: RetroGame, num_info: usize) -> bool {
57    false
58  }
59
60  fn unload_game(&mut self, env: &RetroEnvironment) {}
61
62  fn get_region(&self, env: &RetroEnvironment) -> RetroRegion {
63    RetroRegion::NTSC
64  }
65
66  fn get_memory_data(&mut self, env: &RetroEnvironment, id: u32) -> *mut () {
67    std::ptr::null_mut()
68  }
69
70  fn get_memory_size(&self, env: &RetroEnvironment, id: u32) -> usize {
71    0
72  }
73}
74
75pub struct RetroAudioInfo {
76  sample_rate: f64,
77}
78
79impl RetroAudioInfo {
80  pub fn new(sample_rate: f64) -> RetroAudioInfo {
81    RetroAudioInfo { sample_rate }
82  }
83}
84
85#[derive(Debug)]
86pub enum RetroDevice {
87  None = 0,
88  Joypad = 1,
89  Mouse = 2,
90  Keyboard = 3,
91  LightGun = 4,
92  Analog = 5,
93  Pointer = 6,
94}
95
96impl From<u32> for RetroDevice {
97  fn from(val: u32) -> Self {
98    match val {
99      0 => Self::None,
100      1 => Self::Joypad,
101      2 => Self::Mouse,
102      3 => Self::Keyboard,
103      4 => Self::LightGun,
104      5 => Self::Analog,
105      6 => Self::Pointer,
106      _ => panic!("unrecognized device type. type={}", val),
107    }
108  }
109}
110
111trait Assoc {
112  type Type;
113}
114
115impl<T> Assoc for Option<T> {
116  type Type = T;
117}
118
119/// Exposes the `retro_environment_t` callback in an idiomatic fashion. Each of the `RETRO_ENVIRONMENT_*` keys will
120/// eventually have a corresponding method here.
121///
122/// Until that is accomplished, the keys are available in `libretro_rs::sys` and can be used manually with the `get_raw`,
123/// `get`, `get_str` and `set_raw` functions.
124#[derive(Clone, Copy)]
125pub struct RetroEnvironment(<retro_environment_t as Assoc>::Type);
126
127impl RetroEnvironment {
128  fn new(cb: <retro_environment_t as Assoc>::Type) -> RetroEnvironment {
129    RetroEnvironment(cb)
130  }
131
132  /* Commands */
133
134  /// Requests that the frontend shut down. The frontend can refuse to do this, and return false.
135  pub fn shutdown(&self) -> bool {
136    unsafe { self.cmd_raw(RETRO_ENVIRONMENT_SHUTDOWN) }
137  }
138
139  pub fn set_pixel_format(&self, val: RetroPixelFormat) -> bool {
140    self.set_u32(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, val.into())
141  }
142
143  pub fn set_support_no_game(&self, val: bool) -> bool {
144    self.set_bool(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, val)
145  }
146
147  /* Queries */
148
149  /// Queries the path where the current libretro core resides.
150  pub fn get_libretro_path(&self) -> Option<&str> {
151    self.get_str(RETRO_ENVIRONMENT_GET_LIBRETRO_PATH)
152  }
153
154  /// Queries the path of the "core assets" directory.
155  pub fn get_core_assets_directory(&self) -> Option<&str> {
156    self.get_str(RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY)
157  }
158
159  /// Queries the path of the save directory.
160  pub fn get_save_directory(&self) -> Option<&str> {
161    self.get_str(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY)
162  }
163
164  /// Queries the path of the system directory.
165  pub fn get_system_directory(&self) -> Option<&str> {
166    self.get_str(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY)
167  }
168
169  /// Queries the username associated with the frontend.
170  pub fn get_username(&self) -> Option<&str> {
171    self.get_str(RETRO_ENVIRONMENT_GET_USERNAME)
172  }
173
174  /// Queries a string slice from the environment. A null pointer (`*const c_char`) is interpreted as `None`.
175  pub fn get_str<'a>(&'a self, key: u32) -> Option<&'a str> {
176    unsafe {
177      let s = self.get(key)?;
178      CStr::from_ptr(s).to_str().ok()
179    }
180  }
181
182  /// Queries a struct from the environment. A null pointer (`*const T`) is interpreted as `None`.
183  pub unsafe fn get<T>(&self, key: u32) -> Option<*const T> {
184    let mut val: *const T = std::ptr::null();
185    if self.get_raw(key, &mut val) && !val.is_null() {
186      Some(val)
187    } else {
188      None
189    }
190  }
191
192  /// Directly invokes the underlying `retro_environment_t` in a "get" fashion.
193  #[inline]
194  pub unsafe fn get_raw<T>(&self, key: u32, output: *mut *const T) -> bool {
195    self.0(key, output as *mut c_void)
196  }
197
198  #[inline]
199  pub fn set_bool(&self, key: u32, val: bool) -> bool {
200    unsafe { self.set_raw(key, &val) }
201  }
202
203  #[inline]
204  pub fn set_u32(&self, key: u32, val: u32) -> bool {
205    unsafe { self.set_raw(key, &val) }
206  }
207
208  /// Directly invokes the underlying `retro_environment_t` in a "set" fashion.
209  #[inline]
210  pub unsafe fn set_raw<T>(&self, key: u32, val: *const T) -> bool {
211    self.0(key, val as *mut c_void)
212  }
213
214  /// Directly invokes the underlying `retro_environment_t` in a "command" fashion.
215  #[inline]
216  pub unsafe fn cmd_raw(&self, key: u32) -> bool {
217    self.0(key, std::ptr::null_mut())
218  }
219}
220
221/// Represents the possible ways that a frontend can pass game information to a core.
222pub enum RetroGame<'a> {
223  /// Used if a core supports "no game" and no game was selected.
224  ///
225  /// * `meta` contains implementation-specific metadata, if present.
226  ///
227  /// **Note**: "No game" support is hinted with the `RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME` key.
228  None { meta: Option<&'a str> },
229  /// Used if a core doesn't need paths, and a game was selected.
230  ///
231  /// * `meta` contains implementation-specific metadata, if present.
232  /// * `data` contains the entire contents of the game.
233  Data { meta: Option<&'a str>, data: &'a [u8] },
234  /// Used if a core needs paths, and a game was selected.
235  ///
236  /// * `meta` contains implementation-specific metadata, if present.
237  /// * `path` contains the entire absolute path of the game.
238  Path { meta: Option<&'a str>, path: &'a str },
239}
240
241impl<'a> From<&retro_game_info> for RetroGame<'a> {
242  fn from(game: &retro_game_info) -> RetroGame<'a> {
243    let meta = if game.meta.is_null() {
244      None
245    } else {
246      unsafe { CStr::from_ptr(game.meta).to_str().ok() }
247    };
248
249    if game.path.is_null() && game.data.is_null() {
250      return RetroGame::None { meta };
251    }
252
253    if !game.data.is_null() {
254      unsafe {
255        let data = std::slice::from_raw_parts(game.data as *const u8, game.size);
256        return RetroGame::Data { meta, data };
257      }
258    }
259
260    if !game.path.is_null() {
261      unsafe {
262        let path = CStr::from_ptr(game.path).to_str().unwrap();
263        return RetroGame::Path { meta, path };
264      }
265    }
266
267    unreachable!("`game_info` has a `path` and a `data` field.")
268  }
269}
270
271pub enum RetroJoypadButton {
272  B = 0,
273  Y = 1,
274  Select = 2,
275  Start = 3,
276  Up = 4,
277  Down = 5,
278  Left = 6,
279  Right = 7,
280  A = 8,
281  X = 9,
282  L1 = 10,
283  R1 = 11,
284  L2 = 12,
285  R2 = 13,
286  L3 = 14,
287  R3 = 15,
288}
289
290impl Into<u32> for RetroJoypadButton {
291  fn into(self) -> u32 {
292    match self {
293      Self::B => 0,
294      Self::Y => 1,
295      Self::Select => 2,
296      Self::Start => 3,
297      Self::Up => 4,
298      Self::Down => 5,
299      Self::Left => 6,
300      Self::Right => 7,
301      Self::A => 8,
302      Self::X => 9,
303      Self::L1 => 10,
304      Self::R1 => 11,
305      Self::L2 => 12,
306      Self::R2 => 13,
307      Self::L3 => 14,
308      Self::R3 => 15,
309    }
310  }
311}
312
313#[must_use]
314pub enum RetroLoadGameResult {
315  Failure,
316  Success { audio: RetroAudioInfo, video: RetroVideoInfo },
317}
318
319/// Represents the set of regions supported by `libretro`.
320pub enum RetroRegion {
321  /// A 30 frames/second (60 fields/second) video system.
322  NTSC = 0,
323  /// A 25 frames/second (50 fields/second) video system.
324  PAL = 1,
325}
326
327impl Into<u32> for RetroRegion {
328  fn into(self) -> u32 {
329    match self {
330      Self::NTSC => 0,
331      Self::PAL => 1,
332    }
333  }
334}
335
336#[derive(Clone, Copy)]
337pub enum RetroPixelFormat {
338  RGB1555,
339  XRGB8888,
340  RGB565,
341}
342
343impl Into<u32> for RetroPixelFormat {
344  fn into(self) -> u32 {
345    match self {
346      RetroPixelFormat::RGB1555 => 0,
347      RetroPixelFormat::XRGB8888 => 1,
348      RetroPixelFormat::RGB565 => 2,
349    }
350  }
351}
352
353pub struct RetroRuntime {
354  audio_sample: <retro_audio_sample_t as Assoc>::Type,
355  audio_sample_batch: <retro_audio_sample_batch_t as Assoc>::Type,
356  input_state: <retro_input_state_t as Assoc>::Type,
357  video_refresh: <retro_video_refresh_t as Assoc>::Type,
358}
359
360impl RetroRuntime {
361  pub fn new(
362    audio_sample: retro_audio_sample_t,
363    audio_sample_batch: retro_audio_sample_batch_t,
364    input_state: retro_input_state_t,
365    video_refresh: retro_video_refresh_t,
366  ) -> Option<RetroRuntime> {
367    Some(RetroRuntime {
368      audio_sample: audio_sample?,
369      audio_sample_batch: audio_sample_batch?,
370      input_state: input_state?,
371      video_refresh: video_refresh?,
372    })
373  }
374
375  /// Sends audio data to the `libretro` frontend.
376  pub fn upload_audio_frame(&self, frame: &[i16]) -> usize {
377    unsafe {
378      return (self.audio_sample_batch)(frame.as_ptr(), frame.len() / 2);
379    }
380  }
381
382  /// Sends audio data to the `libretro` frontend.
383  pub fn upload_audio_sample(&self, left: i16, right: i16) {
384    unsafe {
385      return (self.audio_sample)(left, right);
386    }
387  }
388
389  /// Sends video data to the `libretro` frontend.
390  pub fn upload_video_frame(&self, frame: &[u8], width: u32, height: u32, pitch: usize) {
391    unsafe {
392      return (self.video_refresh)(frame.as_ptr() as *const c_void, width, height, pitch);
393    }
394  }
395
396  /// Returns true if the specified button is pressed, false otherwise.
397  pub fn is_joypad_button_pressed(&self, port: u32, btn: RetroJoypadButton) -> bool {
398    unsafe {
399      // port, device, index, id
400      return (self.input_state)(port, RETRO_DEVICE_JOYPAD, 0, btn.into()) != 0;
401    }
402  }
403}
404
405pub struct RetroSystemInfo {
406  name: String,
407  version: String,
408  valid_extensions: Option<String>,
409  block_extract: bool,
410  need_full_path: bool,
411}
412
413impl RetroSystemInfo {
414  pub fn new<N: Into<String>, V: Into<String>>(name: N, version: V) -> RetroSystemInfo {
415    RetroSystemInfo {
416      name: name.into(),
417      version: version.into(),
418      valid_extensions: None,
419      block_extract: false,
420      need_full_path: false,
421    }
422  }
423
424  pub fn with_valid_extensions(mut self, extensions: &[&str]) -> Self {
425    self.valid_extensions = if extensions.len() == 0 {
426      None
427    } else {
428      Some(extensions.join("|"))
429    };
430
431    self
432  }
433
434  pub fn with_block_extract(mut self) -> Self {
435    self.block_extract = true;
436    self
437  }
438
439  pub fn with_need_full_path(mut self) -> Self {
440    self.need_full_path = true;
441    self
442  }
443}
444
445pub struct RetroSystemAvInfo {
446  audio: RetroAudioInfo,
447  video: RetroVideoInfo,
448}
449
450pub struct RetroVideoInfo {
451  frame_rate: f64,
452  width: u32,
453  height: u32,
454  aspect_ratio: f32,
455  max_width: u32,
456  max_height: u32,
457  pixel_format: RetroPixelFormat,
458}
459
460impl RetroVideoInfo {
461  pub fn new(frame_rate: f64, width: u32, height: u32) -> RetroVideoInfo {
462    assert_ne!(height, 0);
463
464    RetroVideoInfo {
465      frame_rate,
466      width,
467      height,
468      aspect_ratio: (width as f32) / (height as f32),
469      max_width: width,
470      max_height: height,
471      pixel_format: RetroPixelFormat::RGB1555,
472    }
473  }
474
475  pub fn with_aspect_ratio(mut self, aspect_ratio: f32) -> Self {
476    self.aspect_ratio = aspect_ratio;
477    self
478  }
479
480  pub fn with_max(mut self, width: u32, height: u32) -> Self {
481    self.max_width = width;
482    self.max_height = height;
483    self
484  }
485
486  pub fn with_pixel_format(mut self, pixel_format: RetroPixelFormat) -> Self {
487    self.pixel_format = pixel_format;
488    self
489  }
490}
491
492/// This is the glue layer between a `RetroCore` implementation, and the `libretro` API.
493pub struct RetroInstance<T: RetroCore> {
494  pub system: Option<T>,
495  pub system_info: Option<RetroSystemInfo>,
496  pub system_av_info: Option<RetroSystemAvInfo>,
497  pub audio_sample: retro_audio_sample_t,
498  pub audio_sample_batch: retro_audio_sample_batch_t,
499  pub environment: Option<RetroEnvironment>,
500  pub input_poll: retro_input_poll_t,
501  pub input_state: retro_input_state_t,
502  pub video_refresh: retro_video_refresh_t,
503}
504
505impl<T: RetroCore> RetroInstance<T> {
506  /// Invoked by a `libretro` frontend, with the `retro_get_system_info` API call.
507  pub fn on_get_system_info(&mut self, info: &mut retro_system_info) {
508    let system_info = T::get_system_info();
509
510    info.library_name = system_info.name.as_ptr() as *const c_char;
511    info.library_version = system_info.version.as_ptr() as *const c_char;
512    info.block_extract = system_info.block_extract;
513    info.need_fullpath = system_info.need_full_path;
514    info.valid_extensions = match system_info.valid_extensions.as_ref() {
515      None => std::ptr::null(),
516      Some(ext) => ext.as_ptr() as *const c_char,
517    };
518
519    // Hold on to the struct so the pointers don't dangle.
520    self.system_info = Some(system_info)
521  }
522
523  /// Invoked by a `libretro` frontend, with the `retro_get_system_av_info` API call.
524  pub fn on_get_system_av_info(&self, info: &mut retro_system_av_info) {
525    let av_info = self
526      .system_av_info
527      .as_ref()
528      .expect("`retro_get_system_av_info` called without a successful `retro_load_game` call. The frontend is not compliant.");
529
530    let audio = &av_info.audio;
531    let video = &av_info.video;
532
533    self.environment().set_pixel_format(video.pixel_format);
534
535    info.geometry.aspect_ratio = video.aspect_ratio;
536    info.geometry.base_width = video.width;
537    info.geometry.base_height = video.height;
538    info.geometry.max_width = video.max_width;
539    info.geometry.max_height = video.max_height;
540    info.timing.fps = video.frame_rate;
541    info.timing.sample_rate = audio.sample_rate;
542  }
543
544  /// Invoked by a `libretro` frontend, with the `retro_init` API call.
545  pub fn on_init(&mut self) {
546    let env = self.environment();
547    self.system = Some(T::init(&env))
548  }
549
550  /// Invoked by a `libretro` frontend, with the `retro_deinit` API call.
551  pub fn on_deinit(&mut self) {
552    self.system = None;
553    self.audio_sample = None;
554    self.audio_sample_batch = None;
555    self.environment = None;
556    self.input_poll = None;
557    self.input_state = None;
558    self.video_refresh = None;
559  }
560
561  /// Invoked by a `libretro` frontend, with the `retro_set_environment` API call.
562  pub fn on_set_environment(&mut self, cb: retro_environment_t) {
563    self.environment = cb.map(RetroEnvironment::new);
564    self.environment().set_support_no_game(T::SUPPORT_NO_GAME);
565  }
566
567  /// Invoked by a `libretro` frontend, with the `retro_set_audio_sample` API call.
568  pub fn on_set_audio_sample(&mut self, cb: retro_audio_sample_t) {
569    self.audio_sample = cb;
570  }
571
572  /// Invoked by a `libretro` frontend, with the `retro_set_audio_sample_batch` API call.
573  pub fn on_set_audio_sample_batch(&mut self, cb: retro_audio_sample_batch_t) {
574    self.audio_sample_batch = cb;
575  }
576
577  /// Invoked by a `libretro` frontend, with the `retro_set_input_poll` API call.
578  pub fn on_set_input_poll(&mut self, cb: retro_input_poll_t) {
579    self.input_poll = cb;
580  }
581
582  /// Invoked by a `libretro` frontend, with the `retro_set_input_state` API call.
583  pub fn on_set_input_state(&mut self, cb: retro_input_state_t) {
584    self.input_state = cb;
585  }
586
587  /// Invoked by a `libretro` frontend, with the `retro_set_video_refresh` API call.
588  pub fn on_set_video_refresh(&mut self, cb: retro_video_refresh_t) {
589    self.video_refresh = cb;
590  }
591
592  /// Invoked by a `libretro` frontend, with the `retro_set_controller_port_device` API call.
593  pub fn on_set_controller_port_device(&mut self, port: libc::c_uint, device: libc::c_uint) {
594    let env = self.environment();
595    self.core_mut(|core| core.set_controller_port_device(&env, port, device.into()))
596  }
597
598  /// Invoked by a `libretro` frontend, with the `retro_reset` API call.
599  pub fn on_reset(&mut self) {
600    let env = self.environment();
601    self.core_mut(|core| core.reset(&env))
602  }
603
604  /// Invoked by a `libretro` frontend, with the `retro_run` API call.
605  pub fn on_run(&mut self) {
606    unsafe {
607      // `input_poll` is required to be called once per `retro_run`.
608      (self.input_poll.unwrap())();
609    }
610
611    let env = self.environment();
612
613    let runtime = RetroRuntime::new(
614      self.audio_sample,
615      self.audio_sample_batch,
616      self.input_state,
617      self.video_refresh,
618    )
619    .unwrap();
620
621    self.core_mut(|core| core.run(&env, &runtime));
622  }
623
624  /// Invoked by a `libretro` frontend, with the `retro_serialize_size` API call.
625  pub fn on_serialize_size(&self) -> libc::size_t {
626    let env = self.environment();
627    self.core_ref(|core| core.serialize_size(&env))
628  }
629
630  /// Invoked by a `libretro` frontend, with the `retro_serialize` API call.
631  pub fn on_serialize(&self, data: *mut (), size: libc::size_t) -> bool {
632    let env = self.environment();
633    self.core_ref(|core| core.serialize(&env, data, size))
634  }
635
636  /// Invoked by a `libretro` frontend, with the `retro_unserialize` API call.
637  pub fn on_unserialize(&mut self, data: *const (), size: libc::size_t) -> bool {
638    let env = self.environment();
639    self.core_mut(|core| core.unserialize(&env, data, size))
640  }
641
642  /// Invoked by a `libretro` frontend, with the `retro_cheat_reset` API call.
643  pub fn on_cheat_reset(&mut self) {
644    let env = self.environment();
645    self.core_mut(|core| core.cheat_reset(&env))
646  }
647
648  /// Invoked by a `libretro` frontend, with the `retro_cheat_set` API call.
649  pub fn on_cheat_set(&mut self, index: libc::c_uint, enabled: bool, code: *const libc::c_char) {
650    let env = self.environment();
651    self.core_mut(|core| core.cheat_set(&env, index, enabled, code))
652  }
653
654  /// Invoked by a `libretro` frontend, with the `retro_load_game` API call.
655  pub fn on_load_game(&mut self, game: &retro_game_info) -> bool {
656    let env = self.environment();
657
658    match self.core_mut(|core| core.load_game(&env, game.into())) {
659      RetroLoadGameResult::Failure => {
660        self.system_av_info = None;
661        false
662      }
663      RetroLoadGameResult::Success { audio, video } => {
664        self.system_av_info = Some(RetroSystemAvInfo { audio, video });
665        true
666      }
667    }
668  }
669
670  /// Invoked by a `libretro` frontend, with the `retro_load_game_special` API call.
671  pub fn on_load_game_special(&mut self, game_type: libc::c_uint, info: &retro_game_info, num_info: libc::size_t) -> bool {
672    let env = self.environment();
673    self.core_mut(|core| core.load_game_special(&env, game_type, info.into(), num_info))
674  }
675
676  /// Invoked by a `libretro` frontend, with the `retro_unload_game` API call.
677  pub fn on_unload_game(&mut self) {
678    let env = self.environment();
679    self.core_mut(|core| core.unload_game(&env))
680  }
681
682  /// Invoked by a `libretro` frontend, with the `retro_get_region` API call.
683  pub fn on_get_region(&self) -> libc::c_uint {
684    let env = self.environment();
685    self.core_ref(|core| core.get_region(&env).into())
686  }
687
688  /// Invoked by a `libretro` frontend, with the `retro_get_memory_data` API call.
689  pub fn on_get_memory_data(&mut self, id: libc::c_uint) -> *mut () {
690    let env = self.environment();
691    self.core_mut(|core| core.get_memory_data(&env, id))
692  }
693
694  /// Invoked by a `libretro` frontend, with the `retro_get_memory_size` API call.
695  pub fn on_get_memory_size(&mut self, id: libc::c_uint) -> libc::size_t {
696    let env = self.environment();
697    self.core_mut(|core| core.get_memory_size(&env, id))
698  }
699
700  #[inline]
701  #[doc(hidden)]
702  fn environment(&self) -> RetroEnvironment {
703    self.environment.unwrap()
704  }
705
706  #[inline]
707  #[doc(hidden)]
708  fn core_mut<F, Output>(&mut self, f: F) -> Output
709  where
710    F: FnOnce(&mut T) -> Output,
711  {
712    f(self.system.as_mut().unwrap())
713  }
714
715  #[inline]
716  #[doc(hidden)]
717  fn core_ref<F, Output>(&self, f: F) -> Output
718  where
719    F: FnOnce(&T) -> Output,
720  {
721    f(self.system.as_ref().unwrap())
722  }
723}