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