1use crate::buttons::Buttons;
2use crate::error::RetroRsError;
3use crate::pixels::{argb555to888, rgb565to888, rgb888_to_rgb332};
4use libloading::Library;
5use libloading::Symbol;
6#[allow(clippy::wildcard_imports)]
7use rust_libretro_sys::*;
8use std::ffi::{CStr, CString, c_char, c_uint, c_void};
9use std::fs::File;
10use std::io::Read;
11use std::marker::PhantomData;
12use std::panic;
13use std::path::{Path, PathBuf};
14use std::ptr;
15
16thread_local! {
17 static CTX:std::cell::RefCell<Option<EmulatorContext>> = const{std::cell::RefCell::new(None)};
18}
19
20type NotSendSync = *const [u8; 0];
21struct EmulatorCore {
22 core_lib: Library,
23 rom_path: CString,
24 core: CoreFns,
25 _marker: PhantomData<NotSendSync>,
26}
27
28#[allow(dead_code, clippy::struct_field_names)]
29struct CoreFns {
30 retro_api_version: unsafe extern "C" fn() -> c_uint,
31 retro_cheat_reset: unsafe extern "C" fn(),
32 retro_cheat_set: unsafe extern "C" fn(c_uint, bool, *const c_char),
33 retro_deinit: unsafe extern "C" fn(),
34 retro_get_memory_data: unsafe extern "C" fn(c_uint) -> *mut c_void,
35 retro_get_memory_size: unsafe extern "C" fn(c_uint) -> usize,
36 retro_get_region: unsafe extern "C" fn() -> c_uint,
37 retro_get_system_av_info: unsafe extern "C" fn(*mut retro_system_av_info),
38 retro_get_system_info: unsafe extern "C" fn(*mut retro_system_info),
39 retro_init: unsafe extern "C" fn(),
40 retro_load_game: unsafe extern "C" fn(*const retro_game_info) -> bool,
41 retro_load_game_special: unsafe extern "C" fn(c_uint, *const retro_game_info, usize) -> bool,
42 retro_reset: unsafe extern "C" fn(),
43 retro_run: unsafe extern "C" fn(),
44 retro_serialize: unsafe extern "C" fn(*mut c_void, usize) -> bool,
45 retro_serialize_size: unsafe extern "C" fn() -> usize,
46 retro_set_audio_sample: unsafe extern "C" fn(retro_audio_sample_t),
47 retro_set_audio_sample_batch: unsafe extern "C" fn(retro_audio_sample_batch_t),
48 retro_set_controller_port_device: unsafe extern "C" fn(c_uint, c_uint),
49 retro_set_environment: unsafe extern "C" fn(retro_environment_t),
50 retro_set_input_poll: unsafe extern "C" fn(retro_input_poll_t),
51 retro_set_input_state: unsafe extern "C" fn(retro_input_state_t),
52 retro_set_video_refresh: unsafe extern "C" fn(retro_video_refresh_t),
53 retro_unload_game: unsafe extern "C" fn(),
54 retro_unserialize: unsafe extern "C" fn(*const c_void, usize) -> bool,
55}
56
57pub type ButtonCallback = Box<dyn Fn(u32, u32, u32, u32) -> i16>;
58
59#[allow(dead_code)]
60struct EmulatorContext {
61 audio_sample: Vec<i16>,
62 buttons: [Buttons; 2],
63 button_callback: Option<ButtonCallback>,
64 core_path: CString,
65 frame_ptr: *const c_void,
66 frame_pitch: usize,
67 frame_width: u32,
68 frame_height: u32,
69 pixfmt: retro_pixel_format,
70 image_depth: usize,
71 memory_map: Vec<retro_memory_descriptor>,
72 av_info: retro_system_av_info,
73 sys_info: retro_system_info,
74 _marker: PhantomData<NotSendSync>,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79pub struct MemoryRegion {
80 which: usize,
81 pub flags: u64,
82 pub len: usize,
83 pub start: usize,
84 pub offset: usize,
85 pub name: String,
86 pub select: usize,
87 pub disconnect: usize,
88}
89
90pub struct Emulator {
91 core: EmulatorCore,
92}
93
94impl Emulator {
95 #[must_use]
98 #[allow(clippy::too_many_lines)]
99 pub fn create(core_path: &Path, rom_path: &Path) -> Emulator {
100 let emu = CTX.with_borrow_mut(move |ctx_opt| {
101 assert!(
102 ctx_opt.is_none(),
103 "Can't use multiple emulators in one thread currently"
104 );
105 let suffix = if cfg!(target_os = "windows") {
106 "dll"
107 } else if cfg!(target_os = "macos") {
108 "dylib"
109 } else if cfg!(target_os = "linux") {
110 "so"
111 } else {
112 panic!("Unsupported platform")
113 };
114 let path: PathBuf = core_path.with_extension(suffix);
115 #[cfg(target_os = "linux")]
116 let dll: Library = unsafe {
117 use libc::RTLD_NODELETE;
118 use libloading::os::unix::{self, RTLD_LOCAL, RTLD_NOW};
119 unix::Library::open(Some(path), RTLD_NOW | RTLD_LOCAL | RTLD_NODELETE)
121 .unwrap()
122 .into()
123 };
124 #[cfg(not(target_os = "linux"))]
125 let dll = unsafe { Library::new(path).unwrap() };
126 unsafe {
127 let retro_set_environment = *(dll.get(b"retro_set_environment").unwrap());
128 let retro_set_video_refresh = *(dll.get(b"retro_set_video_refresh").unwrap());
129 let retro_set_audio_sample = *(dll.get(b"retro_set_audio_sample").unwrap());
130 let retro_set_audio_sample_batch =
131 *(dll.get(b"retro_set_audio_sample_batch").unwrap());
132 let retro_set_input_poll = *(dll.get(b"retro_set_input_poll").unwrap());
133 let retro_set_input_state = *(dll.get(b"retro_set_input_state").unwrap());
134 let retro_init = *(dll.get(b"retro_init").unwrap());
135 let retro_deinit = *(dll.get(b"retro_deinit").unwrap());
136 let retro_api_version = *(dll.get(b"retro_api_version").unwrap());
137 let retro_get_system_info = *(dll.get(b"retro_get_system_info").unwrap());
138 let retro_get_system_av_info = *(dll.get(b"retro_get_system_av_info").unwrap());
139 let retro_set_controller_port_device =
140 *(dll.get(b"retro_set_controller_port_device").unwrap());
141 let retro_reset = *(dll.get(b"retro_reset").unwrap());
142 let retro_run = *(dll.get(b"retro_run").unwrap());
143 let retro_serialize_size = *(dll.get(b"retro_serialize_size").unwrap());
144 let retro_serialize = *(dll.get(b"retro_serialize").unwrap());
145 let retro_unserialize = *(dll.get(b"retro_unserialize").unwrap());
146 let retro_cheat_reset = *(dll.get(b"retro_cheat_reset").unwrap());
147 let retro_cheat_set = *(dll.get(b"retro_cheat_set").unwrap());
148 let retro_load_game = *(dll.get(b"retro_load_game").unwrap());
149 let retro_load_game_special = *(dll.get(b"retro_load_game_special").unwrap());
150 let retro_unload_game = *(dll.get(b"retro_unload_game").unwrap());
151 let retro_get_region = *(dll.get(b"retro_get_region").unwrap());
152 let retro_get_memory_data = *(dll.get(b"retro_get_memory_data").unwrap());
153 let retro_get_memory_size = *(dll.get(b"retro_get_memory_size").unwrap());
154 let emu = EmulatorCore {
155 core_lib: dll,
156 rom_path: CString::new(rom_path.to_str().unwrap()).unwrap(),
157 core: CoreFns {
158 retro_api_version,
159 retro_cheat_reset,
160 retro_cheat_set,
161 retro_deinit,
162 retro_get_memory_data,
163 retro_get_memory_size,
164
165 retro_get_region,
166 retro_get_system_av_info,
167
168 retro_get_system_info,
169
170 retro_init,
171 retro_load_game,
172 retro_load_game_special,
173
174 retro_reset,
175 retro_run,
176
177 retro_serialize,
178 retro_serialize_size,
179 retro_set_audio_sample,
180
181 retro_set_audio_sample_batch,
182 retro_set_controller_port_device,
183
184 retro_set_environment,
185 retro_set_input_poll,
186 retro_set_input_state,
187
188 retro_set_video_refresh,
189 retro_unload_game,
190 retro_unserialize,
191 },
192 _marker: PhantomData,
193 };
194 let sys_info = retro_system_info {
195 library_name: ptr::null(),
196 library_version: ptr::null(),
197 valid_extensions: ptr::null(),
198 need_fullpath: false,
199 block_extract: false,
200 };
201 let av_info = retro_system_av_info {
202 geometry: retro_game_geometry {
203 base_width: 0,
204 base_height: 0,
205 max_width: 0,
206 max_height: 0,
207 aspect_ratio: 0.0,
208 },
209 timing: retro_system_timing {
210 fps: 0.0,
211 sample_rate: 0.0,
212 },
213 };
214
215 let mut ctx = EmulatorContext {
216 av_info,
217 sys_info,
218 core_path: CString::new(core_path.to_str().unwrap()).unwrap(),
219 audio_sample: Vec::new(),
220 buttons: [Buttons::new(), Buttons::new()],
221 button_callback: None,
222 frame_ptr: ptr::null(),
223 frame_pitch: 0,
224 frame_width: 0,
225 frame_height: 0,
226 pixfmt: retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555,
227 image_depth: 0,
228 memory_map: Vec::new(),
229 _marker: PhantomData,
230 };
231 (emu.core.retro_get_system_info)(&raw mut ctx.sys_info);
232 (emu.core.retro_get_system_av_info)(&raw mut ctx.av_info);
233
234 *ctx_opt = Some(ctx);
235 emu
236 }
237 });
238 unsafe {
239 (emu.core.retro_set_environment)(Some(callback_environment));
241 (emu.core.retro_set_video_refresh)(Some(callback_video_refresh));
242 (emu.core.retro_set_audio_sample)(Some(callback_audio_sample));
243 (emu.core.retro_set_audio_sample_batch)(Some(callback_audio_sample_batch));
244 (emu.core.retro_set_input_poll)(Some(callback_input_poll));
245 (emu.core.retro_set_input_state)(Some(callback_input_state));
246 (emu.core.retro_init)();
248 let rom_cstr = emu.rom_path.clone();
249 let mut rom_file = File::open(rom_path).unwrap();
250 let mut buffer = Vec::new();
251 rom_file.read_to_end(&mut buffer).unwrap();
252 buffer.shrink_to_fit();
253 let game_info = retro_game_info {
254 path: rom_cstr.as_ptr(),
255 data: buffer.as_ptr().cast(),
256 size: buffer.len(),
257 meta: ptr::null(),
258 };
259 (emu.core.retro_load_game)(&raw const game_info);
260 }
261 Emulator { core: emu }
262 }
263 pub fn get_library(&mut self) -> &Library {
264 &self.core.core_lib
265 }
266 #[must_use]
267 pub fn get_symbol<'a, T>(&'a self, symbol: &[u8]) -> Option<Symbol<'a, T>> {
268 let dll = &self.core.core_lib;
269 let sym: Result<Symbol<T>, _> = unsafe { dll.get(symbol) };
270 sym.ok()
271 }
272 #[allow(clippy::missing_panics_doc)]
273 pub fn run(&mut self, inputs: [Buttons; 2]) {
274 CTX.with_borrow_mut(|ctx| {
275 let ctx = ctx.as_mut().unwrap();
276 ctx.audio_sample.clear();
278 ctx.buttons = inputs;
280 ctx.button_callback = None;
281 });
282 unsafe {
283 (self.core.core.retro_run)();
285 }
286 }
287 #[allow(clippy::missing_panics_doc)]
288 pub fn run_with_button_callback(&mut self, input: Box<dyn Fn(u32, u32, u32, u32) -> i16>) {
289 CTX.with_borrow_mut(|ctx| {
290 let ctx = ctx.as_mut().unwrap();
291 ctx.audio_sample.clear();
293 ctx.button_callback = Some(Box::new(input));
295 });
296 unsafe {
297 (self.core.core.retro_run)();
299 }
300 }
301 #[allow(clippy::missing_panics_doc)]
302 pub fn reset(&mut self) {
303 CTX.with_borrow_mut(|ctx| {
304 let ctx = ctx.as_mut().unwrap();
305 ctx.audio_sample.clear();
307 ctx.buttons = [Buttons::new(), Buttons::new()];
309 ctx.button_callback = None;
310 ctx.frame_ptr = ptr::null();
312 });
313 unsafe { (self.core.core.retro_reset)() }
314 }
315 #[must_use]
316 fn get_ram_size(&self, rtype: libc::c_uint) -> usize {
317 unsafe { (self.core.core.retro_get_memory_size)(rtype) }
318 }
319 #[must_use]
320 pub fn get_video_ram_size(&self) -> usize {
321 self.get_ram_size(RETRO_MEMORY_VIDEO_RAM)
322 }
323 #[must_use]
324 pub fn get_system_ram_size(&self) -> usize {
325 self.get_ram_size(RETRO_MEMORY_SYSTEM_RAM)
326 }
327 #[must_use]
328 pub fn get_save_ram_size(&self) -> usize {
329 self.get_ram_size(RETRO_MEMORY_SAVE_RAM)
330 }
331 #[must_use]
332 pub fn video_ram_ref(&self) -> &[u8] {
333 self.get_ram(RETRO_MEMORY_VIDEO_RAM)
334 }
335 #[must_use]
336 pub fn system_ram_ref(&self) -> &[u8] {
337 self.get_ram(RETRO_MEMORY_SYSTEM_RAM)
338 }
339 #[must_use]
340 pub fn system_ram_mut(&mut self) -> &mut [u8] {
341 self.get_ram_mut(RETRO_MEMORY_SYSTEM_RAM)
342 }
343 #[must_use]
344 pub fn save_ram(&self) -> &[u8] {
345 self.get_ram(RETRO_MEMORY_SAVE_RAM)
346 }
347
348 #[must_use]
349 fn get_ram(&self, ramtype: libc::c_uint) -> &[u8] {
350 let len = self.get_ram_size(ramtype);
351 unsafe {
352 let ptr: *const u8 = (self.core.core.retro_get_memory_data)(ramtype).cast();
353 std::slice::from_raw_parts(ptr, len)
354 }
355 }
356 #[must_use]
357 fn get_ram_mut(&mut self, ramtype: libc::c_uint) -> &mut [u8] {
358 let len = self.get_ram_size(ramtype);
359 unsafe {
360 let ptr: *mut u8 = (self.core.core.retro_get_memory_data)(ramtype).cast();
361 std::slice::from_raw_parts_mut(ptr, len)
362 }
363 }
364 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
365 #[must_use]
366 pub fn memory_regions(&self) -> Vec<MemoryRegion> {
367 CTX.with_borrow(|ctx| {
368 let map = &ctx.as_ref().unwrap().memory_map;
369 map.iter()
370 .enumerate()
371 .map(|(i, mdesc)| MemoryRegion {
372 which: i,
373 flags: mdesc.flags,
374 len: mdesc.len,
375 start: mdesc.start,
376 offset: mdesc.offset,
377 select: mdesc.select,
378 disconnect: mdesc.disconnect,
379 name: if mdesc.addrspace.is_null() {
380 String::new()
381 } else {
382 unsafe { CStr::from_ptr(mdesc.addrspace) }
383 .to_string_lossy()
384 .into_owned()
385 },
386 })
387 .collect()
388 })
389 }
390 pub fn memory_ref(&self, start: usize) -> Result<&[u8], RetroRsError> {
393 for mr in self.memory_regions() {
394 if mr.select != 0 && (start & mr.select) == 0 {
395 continue;
396 }
397 if start >= mr.start && start < mr.start + mr.len {
398 return CTX.with_borrow(|ctx| {
399 let maps = &ctx.as_ref().unwrap().memory_map;
400 if mr.which >= maps.len() {
401 return Err(RetroRsError::RAMMapOutOfRangeError);
403 }
404 let start = (start - mr.start) & !mr.disconnect;
405 let map = &maps[mr.which];
406 let ptr: *mut u8 = map.ptr.cast();
408 let slice = unsafe {
409 let ptr = ptr.add(start).add(map.offset);
410 std::slice::from_raw_parts(ptr, map.len - start)
411 };
412 Ok(slice)
413 });
414 } else if start < mr.start {
415 return Err(RetroRsError::RAMCopySrcOutOfBoundsError);
416 }
417 }
418 Err(RetroRsError::RAMCopyNotMappedIntoMemoryRegionError)
419 }
420 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
421 pub fn memory_ref_mut(
425 &mut self,
426 mr: &MemoryRegion,
427 start: usize,
428 ) -> Result<&mut [u8], RetroRsError> {
429 CTX.with_borrow_mut(|ctx| {
430 let maps = &mut ctx.as_mut().unwrap().memory_map;
431 if mr.which >= maps.len() {
432 return Err(RetroRsError::RAMMapOutOfRangeError);
434 }
435 if start < mr.start {
436 return Err(RetroRsError::RAMCopySrcOutOfBoundsError);
437 }
438 let start = (start - mr.start) & !mr.disconnect;
439 let map = &maps[mr.which];
440 let ptr: *mut u8 = map.ptr.cast();
442 let slice = unsafe {
443 let ptr = ptr.add(start).add(map.offset);
444 std::slice::from_raw_parts_mut(ptr, map.len - start)
445 };
446 Ok(slice)
447 })
448 }
449 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
450 #[must_use]
451 pub fn pixel_format(&self) -> retro_pixel_format {
452 CTX.with_borrow(|ctx| ctx.as_ref().unwrap().pixfmt)
453 }
454 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
455 #[must_use]
456 pub fn framebuffer_size(&self) -> (usize, usize) {
457 CTX.with_borrow(|ctx| {
458 let ctx = ctx.as_ref().unwrap();
459 (ctx.frame_width as usize, ctx.frame_height as usize)
460 })
461 }
462 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
463 #[must_use]
464 pub fn framebuffer_pitch(&self) -> usize {
465 CTX.with_borrow(|ctx| ctx.as_ref().unwrap().frame_pitch)
466 }
467 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
468 fn peek_framebuffer<FBPeek, FBPeekRet>(&self, f: FBPeek) -> Result<FBPeekRet, RetroRsError>
471 where
472 FBPeek: FnOnce(&[u8]) -> FBPeekRet,
473 {
474 CTX.with_borrow(|ctx| {
475 let ctx = ctx.as_ref().unwrap();
476 if ctx.frame_ptr.is_null() {
477 Err(RetroRsError::NoFramebufferError)
478 } else {
479 unsafe {
480 #[allow(clippy::cast_possible_truncation)]
481 let frame_slice = std::slice::from_raw_parts(
482 ctx.frame_ptr.cast(),
483 (ctx.frame_height * (ctx.frame_pitch as u32)) as usize,
484 );
485 Ok(f(frame_slice))
486 }
487 }
488 })
489 }
490 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
491 #[must_use]
492 pub fn peek_audio_sample<AudioPeek, AudioPeekRet>(&self, f: AudioPeek) -> AudioPeekRet
493 where
494 AudioPeek: FnOnce(&[i16]) -> AudioPeekRet,
495 {
496 CTX.with_borrow(|ctx| f(&ctx.as_ref().unwrap().audio_sample))
497 }
498 #[must_use]
499 pub fn get_audio_sample_rate(&self) -> f64 {
500 CTX.with_borrow_mut(|ctx| ctx.as_ref().unwrap().av_info.timing.sample_rate)
501 }
502 #[must_use]
503 pub fn get_video_fps(&self) -> f64 {
504 CTX.with_borrow_mut(|ctx| ctx.as_ref().unwrap().av_info.timing.fps)
505 }
506
507 #[must_use]
508 pub fn save(&self, bytes: &mut [u8]) -> bool {
509 let size = self.save_size();
510 if bytes.len() < size {
511 return false;
512 }
513 unsafe { (self.core.core.retro_serialize)(bytes.as_mut_ptr().cast(), size) }
514 }
515 #[must_use]
516 pub fn load(&mut self, bytes: &[u8]) -> bool {
517 let size = self.save_size();
518 if bytes.len() < size {
519 return false;
520 }
521 unsafe { (self.core.core.retro_unserialize)(bytes.as_ptr().cast(), size) }
522 }
523 #[must_use]
524 pub fn save_size(&self) -> usize {
525 unsafe { (self.core.core.retro_serialize_size)() }
526 }
527 pub fn clear_cheats(&mut self) {
528 unsafe { (self.core.core.retro_cheat_reset)() }
529 }
530 pub fn set_cheat(&mut self, index: usize, enabled: bool, code: &str) {
533 unsafe {
534 #[allow(clippy::cast_possible_truncation)]
536 (self.core.core.retro_cheat_set)(
537 index as u32,
538 enabled,
539 CString::new(code).unwrap().into_raw(),
540 );
541 }
542 }
543 pub fn get_pixel(&self, x: usize, y: usize) -> Result<(u8, u8, u8), RetroRsError> {
548 let (w, _h) = self.framebuffer_size();
549 self.peek_framebuffer(move |fb| match self.pixel_format() {
550 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
551 let start = y * w + x;
552 let gb = fb[start * 2];
553 let arg = fb[start * 2 + 1];
554 let (red, green, blue) = argb555to888(gb, arg);
555 (red, green, blue)
556 }
557 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
558 let off = (y * w + x) * 4;
559 (fb[off + 1], fb[off + 2], fb[off + 3])
560 }
561 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
562 let start = y * w + x;
563 let gb = fb[start * 2];
564 let rg = fb[start * 2 + 1];
565 let (red, green, blue) = rgb565to888(gb, rg);
566 (red, green, blue)
567 }
568 _ => panic!("Unsupported pixel format"),
569 })
570 }
571 #[allow(clippy::many_single_char_names)]
576 pub fn for_each_pixel(
577 &self,
578 mut f: impl FnMut(usize, usize, u8, u8, u8),
579 ) -> Result<(), RetroRsError> {
580 let (w, h) = self.framebuffer_size();
581 let fmt = self.pixel_format();
582 self.peek_framebuffer(move |fb| {
583 let mut x = 0;
584 let mut y = 0;
585 match fmt {
586 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
587 for components in fb.chunks_exact(2) {
588 let gb = components[0];
589 let arg = components[1];
590 let (red, green, blue) = argb555to888(gb, arg);
591 f(x, y, red, green, blue);
592 x += 1;
593 if x >= w {
594 y += 1;
595 x = 0;
596 }
597 }
598 }
599 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
600 for components in fb.chunks_exact(4) {
601 let red = components[1];
602 let green = components[2];
603 let blue = components[3];
604 f(x, y, red, green, blue);
605 x += 1;
606 if x >= w {
607 y += 1;
608 x = 0;
609 }
610 }
611 }
612 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
613 for components in fb.chunks_exact(2) {
614 let gb = components[0];
615 let rg = components[1];
616 let (red, green, blue) = rgb565to888(gb, rg);
617 f(x, y, red, green, blue);
618 x += 1;
619 if x >= w {
620 y += 1;
621 x = 0;
622 }
623 }
624 }
625 _ => panic!("Unsupported pixel format"),
626 }
627 assert_eq!(y, h);
628 assert_eq!(x, 0);
629 })
630 }
631 pub fn copy_framebuffer_rgb888(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
636 let fmt = self.pixel_format();
637 self.peek_framebuffer(move |fb| match fmt {
638 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
639 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(3)) {
640 let gb = components[0];
641 let arg = components[1];
642 let (red, green, blue) = argb555to888(gb, arg);
643 dst[0] = red;
644 dst[1] = green;
645 dst[2] = blue;
646 }
647 }
648 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
649 for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(3)) {
650 let r = components[1];
651 let g = components[2];
652 let b = components[3];
653 dst[0] = r;
654 dst[1] = g;
655 dst[2] = b;
656 }
657 }
658 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
659 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(3)) {
660 let gb = components[0];
661 let rg = components[1];
662 let (red, green, blue) = rgb565to888(gb, rg);
663 dst[0] = red;
664 dst[1] = green;
665 dst[2] = blue;
666 }
667 }
668 _ => panic!("Unsupported pixel format"),
669 })
670 }
671 pub fn copy_framebuffer_rgba8888(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
676 let fmt = self.pixel_format();
677 self.peek_framebuffer(move |fb| match fmt {
678 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
679 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
680 let gb = components[0];
681 let arg = components[1];
682 let (red, green, blue) = argb555to888(gb, arg);
683 dst[0] = red;
684 dst[1] = green;
685 dst[2] = blue;
686 dst[3] = (arg >> 7) * 0xFF;
687 }
688 }
689 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
690 for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(4)) {
691 let a = components[0];
692 let r = components[1];
693 let g = components[2];
694 let b = components[3];
695 dst[0] = r;
696 dst[1] = g;
697 dst[2] = b;
698 dst[3] = a;
699 }
700 }
701 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
702 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
703 let gb = components[0];
704 let rg = components[1];
705 let (red, green, blue) = rgb565to888(gb, rg);
706 dst[0] = red;
707 dst[1] = green;
708 dst[2] = blue;
709 dst[3] = 0xFF;
710 }
711 }
712 _ => panic!("Unsupported pixel format"),
713 })
714 }
715 pub fn copy_framebuffer_rgb332(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
720 let fmt = self.pixel_format();
721 self.peek_framebuffer(move |fb| match fmt {
722 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
723 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
724 let gb = components[0];
725 let arg = components[1];
726 let (red, green, blue) = argb555to888(gb, arg);
727 *dst = rgb888_to_rgb332(red, green, blue);
728 }
729 }
730 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
731 for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
732 let r = components[1];
733 let g = components[2];
734 let b = components[3];
735 *dst = rgb888_to_rgb332(r, g, b);
736 }
737 }
738 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
739 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
740 let gb = components[0];
741 let rg = components[1];
742 let (red, green, blue) = rgb565to888(gb, rg);
743 *dst = rgb888_to_rgb332(red, green, blue);
744 }
745 }
746 _ => panic!("Unsupported pixel format"),
747 })
748 }
749 pub fn copy_framebuffer_argb32(&self, slice: &mut [u32]) -> Result<(), RetroRsError> {
754 let fmt = self.pixel_format();
755 self.peek_framebuffer(move |fb| match fmt {
756 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
757 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
758 let gb = components[0];
759 let arg = components[1];
760 let (red, green, blue) = argb555to888(gb, arg);
761 *dst = (0xFF00_0000 * (u32::from(arg) >> 7))
762 | (u32::from(red) << 16)
763 | (u32::from(green) << 8)
764 | u32::from(blue);
765 }
766 }
767 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
768 for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
769 *dst = (u32::from(components[0]) << 24)
770 | (u32::from(components[1]) << 16)
771 | (u32::from(components[2]) << 8)
772 | u32::from(components[3]);
773 }
774 }
775 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
776 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
777 let gb = components[0];
778 let rg = components[1];
779 let (red, green, blue) = rgb565to888(gb, rg);
780 *dst = 0xFF00_0000
781 | (u32::from(red) << 16)
782 | (u32::from(green) << 8)
783 | u32::from(blue);
784 }
785 }
786 _ => panic!("Unsupported pixel format"),
787 })
788 }
789 pub fn copy_framebuffer_rgba32(&self, slice: &mut [u32]) -> Result<(), RetroRsError> {
794 let fmt = self.pixel_format();
795 self.peek_framebuffer(move |fb| match fmt {
796 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
797 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
798 let gb = components[0];
799 let arg = components[1];
800 let (red, green, blue) = argb555to888(gb, arg);
801 *dst = (u32::from(red) << 24)
802 | (u32::from(green) << 16)
803 | (u32::from(blue) << 8)
804 | (u32::from(arg >> 7) * 0x0000_00FF);
805 }
806 }
807 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
808 for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
809 *dst = (u32::from(components[1]) << 24)
810 | (u32::from(components[2]) << 16)
811 | (u32::from(components[3]) << 8)
812 | u32::from(components[0]);
813 }
814 }
815 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
816 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
817 let gb = components[0];
818 let rg = components[1];
819 let (red, green, blue) = rgb565to888(gb, rg);
820 *dst = (u32::from(red) << 24)
821 | (u32::from(green) << 16)
822 | (u32::from(blue) << 8)
823 | 0x0000_00FF;
824 }
825 }
826 _ => panic!("Unsupported pixel format"),
827 })
828 }
829 pub fn copy_framebuffer_rgba_f32x4(&self, slice: &mut [f32]) -> Result<(), RetroRsError> {
834 let fmt = self.pixel_format();
835 self.peek_framebuffer(move |fb| match fmt {
836 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
837 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
838 let gb = components[0];
839 let arg = components[1];
840 let (red, green, blue) = argb555to888(gb, arg);
841 let alpha = f32::from(arg >> 7);
842 dst[0] = f32::from(red) / 255.;
843 dst[1] = f32::from(green) / 255.;
844 dst[2] = f32::from(blue) / 255.;
845 dst[3] = alpha;
846 }
847 }
848 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
849 for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(4)) {
850 dst[0] = f32::from(components[0]) / 255.;
851 dst[1] = f32::from(components[1]) / 255.;
852 dst[2] = f32::from(components[2]) / 255.;
853 dst[3] = f32::from(components[3]) / 255.;
854 }
855 }
856 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
857 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
858 let gb = components[0];
859 let rg = components[1];
860 let (red, green, blue) = rgb565to888(gb, rg);
861 let alpha = 1.;
862 dst[0] = f32::from(red) / 255.;
863 dst[1] = f32::from(green) / 255.;
864 dst[2] = f32::from(blue) / 255.;
865 dst[3] = alpha;
866 }
867 }
868 _ => panic!("Unsupported pixel format"),
869 })
870 }
871}
872
873unsafe extern "C" fn callback_environment(cmd: u32, data: *mut c_void) -> bool {
874 let result = panic::catch_unwind(|| {
875 CTX.with_borrow_mut(|ctx| {
876 let ctx = ctx.as_mut().unwrap();
877 match cmd {
878 RETRO_ENVIRONMENT_SET_CONTROLLER_INFO => true,
879 RETRO_ENVIRONMENT_SET_PIXEL_FORMAT => {
880 let pixfmt = unsafe { *(data as *const retro_pixel_format) };
881 ctx.image_depth = match pixfmt {
882 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => 15,
883 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => 32,
884 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => 16,
885 _ => panic!("Unsupported pixel format"),
886 };
887 ctx.pixfmt = pixfmt;
888 true
889 }
890 RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY => unsafe {
891 *(data.cast()) = ctx.core_path.as_ptr();
892 true
893 },
894 RETRO_ENVIRONMENT_GET_CAN_DUPE => unsafe {
895 *(data.cast()) = true;
896 true
897 },
898 RETRO_ENVIRONMENT_SET_MEMORY_MAPS => unsafe {
899 let map: *const retro_memory_map = data.cast();
900 let desc_slice = std::slice::from_raw_parts(
901 (*map).descriptors,
902 (*map).num_descriptors as usize,
903 );
904 ctx.memory_map = Vec::new();
906 ctx.memory_map.extend_from_slice(desc_slice);
908 true
910 },
911 RETRO_ENVIRONMENT_GET_INPUT_BITMASKS => true,
912 _ => false,
913 }
914 })
915 });
916 result.unwrap_or(false)
917}
918
919extern "C" fn callback_video_refresh(data: *const c_void, width: u32, height: u32, pitch: usize) {
920 if !data.is_null() {
923 CTX.with_borrow_mut(|ctx| {
924 let ctx = ctx.as_mut().unwrap();
925 ctx.frame_ptr = data;
926 ctx.frame_pitch = pitch;
927 ctx.frame_width = width;
928 ctx.frame_height = height;
929 });
930 }
931}
932extern "C" fn callback_audio_sample(left: i16, right: i16) {
933 CTX.with_borrow_mut(|ctx| {
935 let ctx = ctx.as_mut().unwrap();
936 let sample_buf = &mut ctx.audio_sample;
937 sample_buf.push(left);
938 sample_buf.push(right);
939 });
940}
941extern "C" fn callback_audio_sample_batch(data: *const i16, frames: usize) -> usize {
942 CTX.with_borrow_mut(|ctx| {
944 let ctx = ctx.as_mut().unwrap();
945 let sample_buf = &mut ctx.audio_sample;
946 let slice = unsafe { std::slice::from_raw_parts(data, frames * 2) };
947 sample_buf.extend_from_slice(slice);
948 frames
949 })
950}
951
952extern "C" fn callback_input_poll() {}
953
954extern "C" fn callback_input_state(port: u32, device: u32, index: u32, id: u32) -> i16 {
955 if port > 1 || device != 1 || index != 0 {
957 println!("Unsupported port/device/index {port}/{device}/{index}");
959 return 0;
960 }
961 let bitmask_enabled = (device == RETRO_DEVICE_JOYPAD) && (id == RETRO_DEVICE_ID_JOYPAD_MASK);
962 CTX.with_borrow(|ctx| {
963 let ctx = ctx.as_ref().unwrap();
964 if let Some(cb) = &ctx.button_callback {
965 cb(port, device, index, id)
966 } else if bitmask_enabled {
967 let port = port as usize;
968 i16::from(ctx.buttons[port])
969 } else {
970 let port = port as usize;
971 i16::from(ctx.buttons[port].get(id))
972 }
973 })
974}
975
976impl Drop for Emulator {
977 fn drop(&mut self) {
978 unsafe {
979 (self.core.core.retro_unload_game)();
980 (self.core.core.retro_deinit)();
981 }
982 CTX.with_borrow_mut(Option::take);
983 }
984}
985
986#[cfg(test)]
987mod tests {
988 use super::*;
989 use std::path::Path;
990 #[cfg(feature = "use_image")]
991 extern crate image;
992 #[cfg(feature = "use_image")]
993 use crate::fb_to_image::*;
994
995 fn mario_is_dead(emu: &Emulator) -> bool {
996 emu.system_ram_ref()[0x0770] == 0x03
997 }
998
999 #[test]
1006 fn create_drop_create() {
1007 let mut emu = Emulator::create(
1009 Path::new("../../.config/retroarch/cores/fceumm_libretro0"),
1010 Path::new("roms/mario.nes"),
1011 );
1012 drop(emu);
1013 emu = Emulator::create(
1014 Path::new("../../.config/retroarch/cores/fceumm_libretro1"),
1015 Path::new("roms/mario.nes"),
1016 );
1017 drop(emu);
1018 }
1019 #[cfg(feature = "use_image")]
1020 #[test]
1021 fn it_works() {
1022 let mut emu = Emulator::create(
1024 Path::new("../../.config/retroarch/cores/fceumm_libretro2"),
1025 Path::new("roms/mario.nes"),
1026 );
1027 emu.run([Buttons::new(), Buttons::new()]);
1028 emu.reset();
1029 for i in 0..150 {
1030 emu.run([
1031 Buttons::new()
1032 .start(i > 80 && i < 100)
1033 .right(i >= 100)
1034 .a(i >= 100),
1035 Buttons::new(),
1036 ]);
1037 }
1038 let fb = emu.create_imagebuffer();
1039 fb.unwrap().save("out.png").unwrap();
1040 let mut died = false;
1041 for _ in 0..10000 {
1042 emu.run([Buttons::new().right(true), Buttons::new()]);
1043 if mario_is_dead(&emu) {
1044 died = true;
1045 let fb = emu.create_imagebuffer();
1046 fb.unwrap().save("out2.png").unwrap();
1047 break;
1048 }
1049 }
1050 assert!(died);
1051 emu.reset();
1052 for i in 0..250 {
1053 emu.run([
1054 Buttons::new()
1055 .start(i > 80 && i < 100)
1056 .right(i >= 100)
1057 .a((100..=150).contains(&i) || (i >= 180)),
1058 Buttons::new(),
1059 ]);
1060 }
1061
1062 }
1064 #[test]
1065 fn it_works_with_callback() {
1066 let mut emu = Emulator::create(
1068 Path::new("../../.config/retroarch/cores/fceumm_libretro3"),
1069 Path::new("roms/mario.nes"),
1070 );
1071 emu.run([Buttons::new(), Buttons::new()]);
1072 emu.reset();
1073 for i in 0..150 {
1074 emu.run_with_button_callback(Box::new(move |port, _dev, _idx, id| {
1075 if port == 0 {
1076 let buttons = Buttons::new()
1077 .start(i > 80 && i < 100)
1078 .right(i >= 100)
1079 .a((100..=150).contains(&i) || (i >= 180));
1080 if id == RETRO_DEVICE_ID_JOYPAD_MASK {
1081 i16::from(buttons)
1082 } else {
1083 i16::from(buttons.get(id))
1084 }
1085 } else {
1086 0
1087 }
1088 }));
1089 }
1090 let mut died = false;
1091 for _ in 0..10000 {
1092 emu.run_with_button_callback(Box::new(|_port, _dev, _idx, id| {
1093 let buttons = Buttons::new().right(true);
1094 if id == RETRO_DEVICE_ID_JOYPAD_MASK {
1095 i16::from(buttons)
1096 } else {
1097 i16::from(buttons.get(id))
1098 }
1099 }));
1100 if mario_is_dead(&emu) {
1101 died = true;
1102 break;
1103 }
1104 }
1105 assert!(died);
1106 }
1108}