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 CTX.with_borrow_mut(|ctx| {
261 let ctx = ctx.as_mut().unwrap();
262 (emu.core.retro_get_system_info)(&raw mut ctx.sys_info);
263 (emu.core.retro_get_system_av_info)(&raw mut ctx.av_info);
264 });
265 }
266 Emulator { core: emu }
267 }
268 pub fn get_library(&mut self) -> &Library {
269 &self.core.core_lib
270 }
271 #[must_use]
272 pub fn get_symbol<'a, T>(&'a self, symbol: &[u8]) -> Option<Symbol<'a, T>> {
273 let dll = &self.core.core_lib;
274 let sym: Result<Symbol<T>, _> = unsafe { dll.get(symbol) };
275 sym.ok()
276 }
277 #[allow(clippy::missing_panics_doc)]
278 pub fn run(&mut self, inputs: [Buttons; 2]) {
279 CTX.with_borrow_mut(|ctx| {
280 let ctx = ctx.as_mut().unwrap();
281 ctx.audio_sample.clear();
283 ctx.buttons = inputs;
285 ctx.button_callback = None;
286 });
287 unsafe {
288 (self.core.core.retro_run)();
290 }
291 }
292 #[allow(clippy::missing_panics_doc)]
293 pub fn run_with_button_callback(&mut self, input: Box<dyn Fn(u32, u32, u32, u32) -> i16>) {
294 CTX.with_borrow_mut(|ctx| {
295 let ctx = ctx.as_mut().unwrap();
296 ctx.audio_sample.clear();
298 ctx.button_callback = Some(Box::new(input));
300 });
301 unsafe {
302 (self.core.core.retro_run)();
304 }
305 }
306 #[allow(clippy::missing_panics_doc)]
307 pub fn reset(&mut self) {
308 CTX.with_borrow_mut(|ctx| {
309 let ctx = ctx.as_mut().unwrap();
310 ctx.audio_sample.clear();
312 ctx.buttons = [Buttons::new(), Buttons::new()];
314 ctx.button_callback = None;
315 ctx.frame_ptr = ptr::null();
317 });
318 unsafe { (self.core.core.retro_reset)() }
319 }
320 #[must_use]
321 fn get_ram_size(&self, rtype: libc::c_uint) -> usize {
322 unsafe { (self.core.core.retro_get_memory_size)(rtype) }
323 }
324 #[must_use]
325 pub fn get_video_ram_size(&self) -> usize {
326 self.get_ram_size(RETRO_MEMORY_VIDEO_RAM)
327 }
328 #[must_use]
329 pub fn get_system_ram_size(&self) -> usize {
330 self.get_ram_size(RETRO_MEMORY_SYSTEM_RAM)
331 }
332 #[must_use]
333 pub fn get_save_ram_size(&self) -> usize {
334 self.get_ram_size(RETRO_MEMORY_SAVE_RAM)
335 }
336 #[must_use]
337 pub fn video_ram_ref(&self) -> &[u8] {
338 self.get_ram(RETRO_MEMORY_VIDEO_RAM)
339 }
340 #[must_use]
341 pub fn system_ram_ref(&self) -> &[u8] {
342 self.get_ram(RETRO_MEMORY_SYSTEM_RAM)
343 }
344 #[must_use]
345 pub fn system_ram_mut(&mut self) -> &mut [u8] {
346 self.get_ram_mut(RETRO_MEMORY_SYSTEM_RAM)
347 }
348 #[must_use]
349 pub fn save_ram(&self) -> &[u8] {
350 self.get_ram(RETRO_MEMORY_SAVE_RAM)
351 }
352
353 #[must_use]
354 fn get_ram(&self, ramtype: libc::c_uint) -> &[u8] {
355 let len = self.get_ram_size(ramtype);
356 unsafe {
357 let ptr: *const u8 = (self.core.core.retro_get_memory_data)(ramtype).cast();
358 std::slice::from_raw_parts(ptr, len)
359 }
360 }
361 #[must_use]
362 fn get_ram_mut(&mut self, ramtype: libc::c_uint) -> &mut [u8] {
363 let len = self.get_ram_size(ramtype);
364 unsafe {
365 let ptr: *mut u8 = (self.core.core.retro_get_memory_data)(ramtype).cast();
366 std::slice::from_raw_parts_mut(ptr, len)
367 }
368 }
369 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
370 #[must_use]
371 pub fn memory_regions(&self) -> Vec<MemoryRegion> {
372 CTX.with_borrow(|ctx| {
373 let map = &ctx.as_ref().unwrap().memory_map;
374 map.iter()
375 .enumerate()
376 .map(|(i, mdesc)| MemoryRegion {
377 which: i,
378 flags: mdesc.flags,
379 len: mdesc.len,
380 start: mdesc.start,
381 offset: mdesc.offset,
382 select: mdesc.select,
383 disconnect: mdesc.disconnect,
384 name: if mdesc.addrspace.is_null() {
385 String::new()
386 } else {
387 unsafe { CStr::from_ptr(mdesc.addrspace) }
388 .to_string_lossy()
389 .into_owned()
390 },
391 })
392 .collect()
393 })
394 }
395 pub fn memory_ref(&self, start: usize) -> Result<&[u8], RetroRsError> {
398 for mr in self.memory_regions() {
399 if mr.select != 0 && (start & mr.select) == 0 {
400 continue;
401 }
402 if start >= mr.start && start < mr.start + mr.len {
403 return CTX.with_borrow(|ctx| {
404 let maps = &ctx.as_ref().unwrap().memory_map;
405 if mr.which >= maps.len() {
406 return Err(RetroRsError::RAMMapOutOfRangeError);
408 }
409 let start = (start - mr.start) & !mr.disconnect;
410 let map = &maps[mr.which];
411 let ptr: *mut u8 = map.ptr.cast();
413 let slice = unsafe {
414 let ptr = ptr.add(start).add(map.offset);
415 std::slice::from_raw_parts(ptr, map.len - start)
416 };
417 Ok(slice)
418 });
419 } else if start < mr.start {
420 return Err(RetroRsError::RAMCopySrcOutOfBoundsError);
421 }
422 }
423 Err(RetroRsError::RAMCopyNotMappedIntoMemoryRegionError)
424 }
425 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
426 pub fn memory_ref_mut(
430 &mut self,
431 mr: &MemoryRegion,
432 start: usize,
433 ) -> Result<&mut [u8], RetroRsError> {
434 CTX.with_borrow_mut(|ctx| {
435 let maps = &mut ctx.as_mut().unwrap().memory_map;
436 if mr.which >= maps.len() {
437 return Err(RetroRsError::RAMMapOutOfRangeError);
439 }
440 if start < mr.start {
441 return Err(RetroRsError::RAMCopySrcOutOfBoundsError);
442 }
443 let start = (start - mr.start) & !mr.disconnect;
444 let map = &maps[mr.which];
445 let ptr: *mut u8 = map.ptr.cast();
447 let slice = unsafe {
448 let ptr = ptr.add(start).add(map.offset);
449 std::slice::from_raw_parts_mut(ptr, map.len - start)
450 };
451 Ok(slice)
452 })
453 }
454 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
455 #[must_use]
456 pub fn pixel_format(&self) -> retro_pixel_format {
457 CTX.with_borrow(|ctx| ctx.as_ref().unwrap().pixfmt)
458 }
459 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
460 #[must_use]
461 pub fn framebuffer_size(&self) -> (usize, usize) {
462 CTX.with_borrow(|ctx| {
463 let ctx = ctx.as_ref().unwrap();
464 (ctx.frame_width as usize, ctx.frame_height as usize)
465 })
466 }
467 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
468 #[must_use]
469 pub fn framebuffer_pitch(&self) -> usize {
470 CTX.with_borrow(|ctx| ctx.as_ref().unwrap().frame_pitch)
471 }
472 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
473 pub fn peek_framebuffer<FBPeek, FBPeekRet>(&self, f: FBPeek) -> Result<FBPeekRet, RetroRsError>
476 where
477 FBPeek: FnOnce(&[u8]) -> FBPeekRet,
478 {
479 CTX.with_borrow(|ctx| {
480 let ctx = ctx.as_ref().unwrap();
481 if ctx.frame_ptr.is_null() {
482 Err(RetroRsError::NoFramebufferError)
483 } else {
484 unsafe {
485 #[allow(clippy::cast_possible_truncation)]
486 let frame_slice = std::slice::from_raw_parts(
487 ctx.frame_ptr.cast(),
488 (ctx.frame_height * (ctx.frame_pitch as u32)) as usize,
489 );
490 Ok(f(frame_slice))
491 }
492 }
493 })
494 }
495 #[allow(clippy::missing_panics_doc, clippy::unused_self)]
496 pub fn peek_audio_sample<AudioPeek, AudioPeekRet>(&self, f: AudioPeek) -> AudioPeekRet
497 where
498 AudioPeek: FnOnce(&[i16]) -> AudioPeekRet,
499 {
500 CTX.with_borrow(|ctx| f(&ctx.as_ref().unwrap().audio_sample))
501 }
502 #[must_use]
503 pub fn get_audio_sample_rate(&self) -> f64 {
504 CTX.with_borrow_mut(|ctx| ctx.as_ref().unwrap().av_info.timing.sample_rate)
505 }
506 #[must_use]
507 pub fn get_video_fps(&self) -> f64 {
508 CTX.with_borrow_mut(|ctx| ctx.as_ref().unwrap().av_info.timing.fps)
509 }
510 #[must_use]
511 pub fn get_aspect_ratio(&self) -> f32 {
512 CTX.with_borrow_mut(|ctx| ctx.as_ref().unwrap().av_info.geometry.aspect_ratio)
513 }
514
515 #[must_use]
516 pub fn save(&self, bytes: &mut [u8]) -> bool {
517 let size = self.save_size();
518 if bytes.len() < size {
519 return false;
520 }
521 unsafe { (self.core.core.retro_serialize)(bytes.as_mut_ptr().cast(), size) }
522 }
523 #[must_use]
524 pub fn load(&mut self, bytes: &[u8]) -> bool {
525 let size = self.save_size();
526 if bytes.len() < size {
527 return false;
528 }
529 unsafe { (self.core.core.retro_unserialize)(bytes.as_ptr().cast(), size) }
530 }
531 #[must_use]
532 pub fn save_size(&self) -> usize {
533 unsafe { (self.core.core.retro_serialize_size)() }
534 }
535 pub fn clear_cheats(&mut self) {
536 unsafe { (self.core.core.retro_cheat_reset)() }
537 }
538 pub fn set_cheat(&mut self, index: usize, enabled: bool, code: &str) {
541 unsafe {
542 #[allow(clippy::cast_possible_truncation)]
544 (self.core.core.retro_cheat_set)(
545 index as u32,
546 enabled,
547 CString::new(code).unwrap().into_raw(),
548 );
549 }
550 }
551 pub fn get_pixel(&self, x: usize, y: usize) -> Result<(u8, u8, u8), RetroRsError> {
556 let (w, _h) = self.framebuffer_size();
557 self.peek_framebuffer(move |fb| match self.pixel_format() {
558 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
559 let start = y * w + x;
560 let gb = fb[start * 2];
561 let arg = fb[start * 2 + 1];
562 let (red, green, blue) = argb555to888(gb, arg);
563 (red, green, blue)
564 }
565 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
566 let off = (y * w + x) * 4;
567 (fb[off + 1], fb[off + 2], fb[off + 3])
568 }
569 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
570 let start = y * w + x;
571 let gb = fb[start * 2];
572 let rg = fb[start * 2 + 1];
573 let (red, green, blue) = rgb565to888(gb, rg);
574 (red, green, blue)
575 }
576 _ => panic!("Unsupported pixel format"),
577 })
578 }
579 #[allow(clippy::many_single_char_names)]
584 pub fn for_each_pixel(
585 &self,
586 mut f: impl FnMut(usize, usize, u8, u8, u8),
587 ) -> Result<(), RetroRsError> {
588 let (w, h) = self.framebuffer_size();
589 let fmt = self.pixel_format();
590 self.peek_framebuffer(move |fb| {
591 let mut x = 0;
592 let mut y = 0;
593 match fmt {
594 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
595 for components in fb.chunks_exact(2) {
596 let gb = components[0];
597 let arg = components[1];
598 let (red, green, blue) = argb555to888(gb, arg);
599 f(x, y, red, green, blue);
600 x += 1;
601 if x >= w {
602 y += 1;
603 x = 0;
604 }
605 }
606 }
607 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
608 for components in fb.chunks_exact(4) {
609 let red = components[1];
610 let green = components[2];
611 let blue = components[3];
612 f(x, y, red, green, blue);
613 x += 1;
614 if x >= w {
615 y += 1;
616 x = 0;
617 }
618 }
619 }
620 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
621 for components in fb.chunks_exact(2) {
622 let gb = components[0];
623 let rg = components[1];
624 let (red, green, blue) = rgb565to888(gb, rg);
625 f(x, y, red, green, blue);
626 x += 1;
627 if x >= w {
628 y += 1;
629 x = 0;
630 }
631 }
632 }
633 _ => panic!("Unsupported pixel format"),
634 }
635 assert_eq!(y, h);
636 assert_eq!(x, 0);
637 })
638 }
639 pub fn copy_framebuffer_rgb888(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
644 let fmt = self.pixel_format();
645 self.peek_framebuffer(move |fb| match fmt {
646 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
647 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(3)) {
648 let gb = components[0];
649 let arg = components[1];
650 let (red, green, blue) = argb555to888(gb, arg);
651 dst[0] = red;
652 dst[1] = green;
653 dst[2] = blue;
654 }
655 }
656 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
657 for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(3)) {
658 let r = components[1];
659 let g = components[2];
660 let b = components[3];
661 dst[0] = r;
662 dst[1] = g;
663 dst[2] = b;
664 }
665 }
666 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
667 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(3)) {
668 let gb = components[0];
669 let rg = components[1];
670 let (red, green, blue) = rgb565to888(gb, rg);
671 dst[0] = red;
672 dst[1] = green;
673 dst[2] = blue;
674 }
675 }
676 _ => panic!("Unsupported pixel format"),
677 })
678 }
679 pub fn copy_framebuffer_rgba8888(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
684 let fmt = self.pixel_format();
685 self.peek_framebuffer(move |fb| match fmt {
686 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
687 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
688 let gb = components[0];
689 let arg = components[1];
690 let (red, green, blue) = argb555to888(gb, arg);
691 dst[0] = red;
692 dst[1] = green;
693 dst[2] = blue;
694 dst[3] = (arg >> 7) * 0xFF;
695 }
696 }
697 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
698 for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(4)) {
699 let a = components[0];
700 let r = components[1];
701 let g = components[2];
702 let b = components[3];
703 dst[0] = r;
704 dst[1] = g;
705 dst[2] = b;
706 dst[3] = a;
707 }
708 }
709 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
710 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
711 let gb = components[0];
712 let rg = components[1];
713 let (red, green, blue) = rgb565to888(gb, rg);
714 dst[0] = red;
715 dst[1] = green;
716 dst[2] = blue;
717 dst[3] = 0xFF;
718 }
719 }
720 _ => panic!("Unsupported pixel format"),
721 })
722 }
723 pub fn copy_framebuffer_rgb332(&self, slice: &mut [u8]) -> Result<(), RetroRsError> {
728 let fmt = self.pixel_format();
729 self.peek_framebuffer(move |fb| match fmt {
730 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
731 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
732 let gb = components[0];
733 let arg = components[1];
734 let (red, green, blue) = argb555to888(gb, arg);
735 *dst = rgb888_to_rgb332(red, green, blue);
736 }
737 }
738 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
739 for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
740 let r = components[1];
741 let g = components[2];
742 let b = components[3];
743 *dst = rgb888_to_rgb332(r, g, b);
744 }
745 }
746 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
747 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
748 let gb = components[0];
749 let rg = components[1];
750 let (red, green, blue) = rgb565to888(gb, rg);
751 *dst = rgb888_to_rgb332(red, green, blue);
752 }
753 }
754 _ => panic!("Unsupported pixel format"),
755 })
756 }
757 pub fn copy_framebuffer_argb32(&self, slice: &mut [u32]) -> Result<(), RetroRsError> {
762 let fmt = self.pixel_format();
763 self.peek_framebuffer(move |fb| match fmt {
764 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
765 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
766 let gb = components[0];
767 let arg = components[1];
768 let (red, green, blue) = argb555to888(gb, arg);
769 *dst = (0xFF00_0000 * (u32::from(arg) >> 7))
770 | (u32::from(red) << 16)
771 | (u32::from(green) << 8)
772 | u32::from(blue);
773 }
774 }
775 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
776 for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
777 *dst = (u32::from(components[0]) << 24)
778 | (u32::from(components[1]) << 16)
779 | (u32::from(components[2]) << 8)
780 | u32::from(components[3]);
781 }
782 }
783 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
784 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
785 let gb = components[0];
786 let rg = components[1];
787 let (red, green, blue) = rgb565to888(gb, rg);
788 *dst = 0xFF00_0000
789 | (u32::from(red) << 16)
790 | (u32::from(green) << 8)
791 | u32::from(blue);
792 }
793 }
794 _ => panic!("Unsupported pixel format"),
795 })
796 }
797 pub fn copy_framebuffer_rgba32(&self, slice: &mut [u32]) -> Result<(), RetroRsError> {
802 let fmt = self.pixel_format();
803 self.peek_framebuffer(move |fb| match fmt {
804 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
805 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
806 let gb = components[0];
807 let arg = components[1];
808 let (red, green, blue) = argb555to888(gb, arg);
809 *dst = (u32::from(red) << 24)
810 | (u32::from(green) << 16)
811 | (u32::from(blue) << 8)
812 | (u32::from(arg >> 7) * 0x0000_00FF);
813 }
814 }
815 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
816 for (components, dst) in fb.chunks_exact(4).zip(slice.iter_mut()) {
817 *dst = (u32::from(components[1]) << 24)
818 | (u32::from(components[2]) << 16)
819 | (u32::from(components[3]) << 8)
820 | u32::from(components[0]);
821 }
822 }
823 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
824 for (components, dst) in fb.chunks_exact(2).zip(slice.iter_mut()) {
825 let gb = components[0];
826 let rg = components[1];
827 let (red, green, blue) = rgb565to888(gb, rg);
828 *dst = (u32::from(red) << 24)
829 | (u32::from(green) << 16)
830 | (u32::from(blue) << 8)
831 | 0x0000_00FF;
832 }
833 }
834 _ => panic!("Unsupported pixel format"),
835 })
836 }
837 pub fn copy_framebuffer_rgba_f32x4(&self, slice: &mut [f32]) -> Result<(), RetroRsError> {
842 let fmt = self.pixel_format();
843 self.peek_framebuffer(move |fb| match fmt {
844 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => {
845 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
846 let gb = components[0];
847 let arg = components[1];
848 let (red, green, blue) = argb555to888(gb, arg);
849 let alpha = f32::from(arg >> 7);
850 dst[0] = f32::from(red) / 255.;
851 dst[1] = f32::from(green) / 255.;
852 dst[2] = f32::from(blue) / 255.;
853 dst[3] = alpha;
854 }
855 }
856 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => {
857 for (components, dst) in fb.chunks_exact(4).zip(slice.chunks_exact_mut(4)) {
858 dst[0] = f32::from(components[0]) / 255.;
859 dst[1] = f32::from(components[1]) / 255.;
860 dst[2] = f32::from(components[2]) / 255.;
861 dst[3] = f32::from(components[3]) / 255.;
862 }
863 }
864 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => {
865 for (components, dst) in fb.chunks_exact(2).zip(slice.chunks_exact_mut(4)) {
866 let gb = components[0];
867 let rg = components[1];
868 let (red, green, blue) = rgb565to888(gb, rg);
869 let alpha = 1.;
870 dst[0] = f32::from(red) / 255.;
871 dst[1] = f32::from(green) / 255.;
872 dst[2] = f32::from(blue) / 255.;
873 dst[3] = alpha;
874 }
875 }
876 _ => panic!("Unsupported pixel format"),
877 })
878 }
879}
880
881unsafe extern "C" fn callback_environment(cmd: u32, data: *mut c_void) -> bool {
882 let result = panic::catch_unwind(|| {
883 CTX.with_borrow_mut(|ctx| {
884 let ctx = ctx.as_mut().unwrap();
885 match cmd {
886 RETRO_ENVIRONMENT_SET_CONTROLLER_INFO => true,
887 RETRO_ENVIRONMENT_SET_PIXEL_FORMAT => {
888 let pixfmt = unsafe { *(data as *const retro_pixel_format) };
889 ctx.image_depth = match pixfmt {
890 retro_pixel_format::RETRO_PIXEL_FORMAT_0RGB1555 => 15,
891 retro_pixel_format::RETRO_PIXEL_FORMAT_XRGB8888 => 32,
892 retro_pixel_format::RETRO_PIXEL_FORMAT_RGB565 => 16,
893 _ => panic!("Unsupported pixel format"),
894 };
895 ctx.pixfmt = pixfmt;
896 true
897 }
898 RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY => unsafe {
899 *(data.cast()) = ctx.core_path.as_ptr();
900 true
901 },
902 RETRO_ENVIRONMENT_GET_CAN_DUPE => unsafe {
903 *(data.cast()) = true;
904 true
905 },
906 RETRO_ENVIRONMENT_SET_MEMORY_MAPS => unsafe {
907 let map: *const retro_memory_map = data.cast();
908 let desc_slice = std::slice::from_raw_parts(
909 (*map).descriptors,
910 (*map).num_descriptors as usize,
911 );
912 ctx.memory_map = Vec::new();
914 ctx.memory_map.extend_from_slice(desc_slice);
916 true
918 },
919 RETRO_ENVIRONMENT_GET_INPUT_BITMASKS => true,
920 _ => false,
921 }
922 })
923 });
924 result.unwrap_or(false)
925}
926
927extern "C" fn callback_video_refresh(data: *const c_void, width: u32, height: u32, pitch: usize) {
928 if !data.is_null() {
931 CTX.with_borrow_mut(|ctx| {
932 let ctx = ctx.as_mut().unwrap();
933 ctx.frame_ptr = data;
934 ctx.frame_pitch = pitch;
935 ctx.frame_width = width;
936 ctx.frame_height = height;
937 });
938 }
939}
940extern "C" fn callback_audio_sample(left: i16, right: i16) {
941 CTX.with_borrow_mut(|ctx| {
943 let ctx = ctx.as_mut().unwrap();
944 let sample_buf = &mut ctx.audio_sample;
945 sample_buf.push(left);
946 sample_buf.push(right);
947 });
948}
949extern "C" fn callback_audio_sample_batch(data: *const i16, frames: usize) -> usize {
950 CTX.with_borrow_mut(|ctx| {
952 let ctx = ctx.as_mut().unwrap();
953 let sample_buf = &mut ctx.audio_sample;
954 let slice = unsafe { std::slice::from_raw_parts(data, frames * 2) };
955 sample_buf.extend_from_slice(slice);
956 frames
957 })
958}
959
960extern "C" fn callback_input_poll() {}
961
962extern "C" fn callback_input_state(port: u32, device: u32, index: u32, id: u32) -> i16 {
963 if port > 1 || device != 1 || index != 0 {
965 println!("Unsupported port/device/index {port}/{device}/{index}");
967 return 0;
968 }
969 let bitmask_enabled = (device == RETRO_DEVICE_JOYPAD) && (id == RETRO_DEVICE_ID_JOYPAD_MASK);
970 CTX.with_borrow(|ctx| {
971 let ctx = ctx.as_ref().unwrap();
972 if let Some(cb) = &ctx.button_callback {
973 cb(port, device, index, id)
974 } else if bitmask_enabled {
975 let port = port as usize;
976 i16::from(ctx.buttons[port])
977 } else {
978 let port = port as usize;
979 i16::from(ctx.buttons[port].get(id))
980 }
981 })
982}
983
984impl Drop for Emulator {
985 fn drop(&mut self) {
986 unsafe {
987 (self.core.core.retro_unload_game)();
988 (self.core.core.retro_deinit)();
989 }
990 CTX.with_borrow_mut(Option::take);
991 }
992}
993
994#[cfg(test)]
995mod tests {
996 use super::*;
997 use std::path::Path;
998 #[cfg(feature = "use_image")]
999 extern crate image;
1000 #[cfg(feature = "use_image")]
1001 use crate::fb_to_image::*;
1002
1003 fn mario_is_dead(emu: &Emulator) -> bool {
1004 emu.system_ram_ref()[0x0770] == 0x03
1005 }
1006
1007 #[test]
1014 fn create_drop_create() {
1015 let mut emu = Emulator::create(
1017 Path::new("../../.config/retroarch/cores/fceumm_libretro0"),
1018 Path::new("roms/mario.nes"),
1019 );
1020 drop(emu);
1021 emu = Emulator::create(
1022 Path::new("../../.config/retroarch/cores/fceumm_libretro1"),
1023 Path::new("roms/mario.nes"),
1024 );
1025 drop(emu);
1026 }
1027 #[cfg(feature = "use_image")]
1028 #[test]
1029 fn it_works() {
1030 let mut emu = Emulator::create(
1032 Path::new("../../.config/retroarch/cores/fceumm_libretro2"),
1033 Path::new("roms/mario.nes"),
1034 );
1035 emu.run([Buttons::new(), Buttons::new()]);
1036 emu.reset();
1037 for i in 0..150 {
1038 emu.run([
1039 Buttons::new()
1040 .start(i > 80 && i < 100)
1041 .right(i >= 100)
1042 .a(i >= 100),
1043 Buttons::new(),
1044 ]);
1045 }
1046 let fb = emu.create_imagebuffer();
1047 fb.unwrap().save("out.png").unwrap();
1048 let mut died = false;
1049 for _ in 0..10000 {
1050 emu.run([Buttons::new().right(true), Buttons::new()]);
1051 if mario_is_dead(&emu) {
1052 died = true;
1053 let fb = emu.create_imagebuffer();
1054 fb.unwrap().save("out2.png").unwrap();
1055 break;
1056 }
1057 }
1058 assert!(died);
1059 emu.reset();
1060 for i in 0..250 {
1061 emu.run([
1062 Buttons::new()
1063 .start(i > 80 && i < 100)
1064 .right(i >= 100)
1065 .a((100..=150).contains(&i) || (i >= 180)),
1066 Buttons::new(),
1067 ]);
1068 }
1069
1070 }
1072 #[test]
1073 fn it_works_with_callback() {
1074 let mut emu = Emulator::create(
1076 Path::new("../../.config/retroarch/cores/fceumm_libretro3"),
1077 Path::new("roms/mario.nes"),
1078 );
1079 emu.run([Buttons::new(), Buttons::new()]);
1080 emu.reset();
1081 for i in 0..150 {
1082 emu.run_with_button_callback(Box::new(move |port, _dev, _idx, id| {
1083 if port == 0 {
1084 let buttons = Buttons::new()
1085 .start(i > 80 && i < 100)
1086 .right(i >= 100)
1087 .a((100..=150).contains(&i) || (i >= 180));
1088 if id == RETRO_DEVICE_ID_JOYPAD_MASK {
1089 i16::from(buttons)
1090 } else {
1091 i16::from(buttons.get(id))
1092 }
1093 } else {
1094 0
1095 }
1096 }));
1097 }
1098 let mut died = false;
1099 for _ in 0..10000 {
1100 emu.run_with_button_callback(Box::new(|_port, _dev, _idx, id| {
1101 let buttons = Buttons::new().right(true);
1102 if id == RETRO_DEVICE_ID_JOYPAD_MASK {
1103 i16::from(buttons)
1104 } else {
1105 i16::from(buttons.get(id))
1106 }
1107 }));
1108 if mario_is_dead(&emu) {
1109 died = true;
1110 break;
1111 }
1112 }
1113 assert!(died);
1114 }
1116}