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 fn init(env: &RetroEnvironment) -> Self;
17
18 fn get_system_info() -> RetroSystemInfo;
21
22 fn set_controller_port_device(&mut self, env: &RetroEnvironment, port: u32, device: RetroDevice) {}
24
25 fn reset(&mut self, env: &RetroEnvironment);
27
28 fn run(&mut self, env: &RetroEnvironment, runtime: &RetroRuntime);
31
32 fn serialize_size(&self, env: &RetroEnvironment) -> usize {
35 0
36 }
37
38 fn serialize(&self, env: &RetroEnvironment, data: *mut (), size: usize) -> bool {
41 false
42 }
43
44 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#[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 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 pub fn get_libretro_path(&self) -> Option<&str> {
151 self.get_str(RETRO_ENVIRONMENT_GET_LIBRETRO_PATH)
152 }
153
154 pub fn get_core_assets_directory(&self) -> Option<&str> {
156 self.get_str(RETRO_ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY)
157 }
158
159 pub fn get_save_directory(&self) -> Option<&str> {
161 self.get_str(RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY)
162 }
163
164 pub fn get_system_directory(&self) -> Option<&str> {
166 self.get_str(RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY)
167 }
168
169 pub fn get_username(&self) -> Option<&str> {
171 self.get_str(RETRO_ENVIRONMENT_GET_USERNAME)
172 }
173
174 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 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 #[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 #[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 #[inline]
216 pub unsafe fn cmd_raw(&self, key: u32) -> bool {
217 self.0(key, std::ptr::null_mut())
218 }
219}
220
221pub enum RetroGame<'a> {
223 None { meta: Option<&'a str> },
229 Data { meta: Option<&'a str>, data: &'a [u8] },
234 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
319pub enum RetroRegion {
321 NTSC = 0,
323 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 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 pub fn upload_audio_sample(&self, left: i16, right: i16) {
384 unsafe {
385 return (self.audio_sample)(left, right);
386 }
387 }
388
389 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 pub fn is_joypad_button_pressed(&self, port: u32, btn: RetroJoypadButton) -> bool {
398 unsafe {
399 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
492pub 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 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 self.system_info = Some(system_info)
521 }
522
523 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 pub fn on_init(&mut self) {
546 let env = self.environment();
547 self.system = Some(T::init(&env))
548 }
549
550 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 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 pub fn on_set_audio_sample(&mut self, cb: retro_audio_sample_t) {
569 self.audio_sample = cb;
570 }
571
572 pub fn on_set_audio_sample_batch(&mut self, cb: retro_audio_sample_batch_t) {
574 self.audio_sample_batch = cb;
575 }
576
577 pub fn on_set_input_poll(&mut self, cb: retro_input_poll_t) {
579 self.input_poll = cb;
580 }
581
582 pub fn on_set_input_state(&mut self, cb: retro_input_state_t) {
584 self.input_state = cb;
585 }
586
587 pub fn on_set_video_refresh(&mut self, cb: retro_video_refresh_t) {
589 self.video_refresh = cb;
590 }
591
592 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 pub fn on_reset(&mut self) {
600 let env = self.environment();
601 self.core_mut(|core| core.reset(&env))
602 }
603
604 pub fn on_run(&mut self) {
606 unsafe {
607 (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 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 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 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 pub fn on_cheat_reset(&mut self) {
644 let env = self.environment();
645 self.core_mut(|core| core.cheat_reset(&env))
646 }
647
648 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 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 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 pub fn on_unload_game(&mut self) {
678 let env = self.environment();
679 self.core_mut(|core| core.unload_game(&env))
680 }
681
682 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 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 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}