diff --git a/Cargo.toml b/Cargo.toml index aba3243..25e84ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,5 @@ clap = "2.33.3" rand = "0.8.4" rodio = "0.17.1" gilrs = "0.10.1" +log = "0.4" +env_logger = "0.9" diff --git a/clippy_warnings.txt b/clippy_warnings.txt new file mode 100644 index 0000000..09bc6cb --- /dev/null +++ b/clippy_warnings.txt @@ -0,0 +1,430 @@ + Checking rustro_arch v0.1.0 (/app) +warning: unused variable: `port` + --> src/main.rs:220:5 + | +220 | port: libc::c_uint, + | ^^^^ help: if this is intentional, prefix it with an underscore: `_port` + | + = note: `#[warn(unused_variables)]` on by default + +warning: unused variable: `device` + --> src/main.rs:221:5 + | +221 | device: libc::c_uint, + | ^^^^^^ help: if this is intentional, prefix it with an underscore: `_device` + +warning: unused variable: `index` + --> src/main.rs:222:5 + | +222 | index: libc::c_uint, + | ^^^^^ help: if this is intentional, prefix it with an underscore: `_index` + +warning: unreachable pattern + --> src/main.rs:288:17 + | +288 | _ => { + | ^ no value can reach this + | +note: multiple earlier patterns match some of the same values + --> src/main.rs:288:17 + | +270 | PixelFormat::ARGB1555 => { + | --------------------- matches some of the same values +... +276 | PixelFormat::RGB565 => { + | ------------------- matches some of the same values +... +282 | PixelFormat::ARGB8888 => { + | --------------------- matches some of the same values +... +288 | _ => { + | ^ collectively making this unreachable + = note: `#[warn(unreachable_patterns)]` on by default + +warning: unused variable: `game_info` + --> src/main.rs:467:17 + | +467 | let game_info = CURRENT_EMULATOR_STATE.game_info.clone().unwrap_unchecked(); + | ^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_game_info` + +warning: unused variable: `data` + --> src/main.rs:469:17 + | +469 | let data = return_data as *mut GameInfoExt; + | ^^^^ help: if this is intentional, prefix it with an underscore: `_data` + +warning: unused variable: `audio_thread` + --> src/main.rs:876:13 + | +876 | let audio_thread = thread::spawn(move || { + | ^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_audio_thread` + +warning: unused variable: `event` + --> src/main.rs:951:36 + | +951 | while let Some(Event { id, event, time }) = gilrs.next_event() { + | ^^^^^ help: try ignoring the field: `event: _` + +warning: unused variable: `time` + --> src/main.rs:951:43 + | +951 | while let Some(Event { id, event, time }) = gilrs.next_event() { + | ^^^^ help: try ignoring the field: `time: _` + +warning: field `game_info_ext` is never read + --> src/main.rs:43:5 + | +29 | struct EmulatorState { + | ------------- field in this struct +... +43 | game_info_ext: Option, + | ^^^^^^^^^^^^^ + | + = note: `#[warn(dead_code)]` on by default + +warning: function `convert_to_cstring` is never used + --> src/main.rs:86:4 + | +86 | fn convert_to_cstring(input: String) -> CString { + | ^^^^^^^^^^^^^^^^^^ + +warning: casting raw pointers to the same type and constness is unnecessary (`*const i8` -> `*const i8`) + --> src/main.rs:359:58 + | +359 | *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE + | __________________________________________________________^ +360 | | .system_directory +361 | | .as_ref() +362 | | .unwrap() +363 | | .as_ptr() as *const i8; // TODO use CString otherwise this will segfault + | |______________________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + = note: `#[warn(clippy::unnecessary_cast)]` on by default +help: try + | +359 ~ *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE +360 + .system_directory +361 + .as_ref() +362 + .unwrap() +363 ~ .as_ptr(); // TODO use CString otherwise this will segfault + | + +warning: casting raw pointers to the same type and constness is unnecessary (`*const i8` -> `*const i8`) + --> src/main.rs:375:58 + | +375 | *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE + | __________________________________________________________^ +376 | | .system_directory +377 | | .as_ref() +378 | | .unwrap() +379 | | .as_ptr() as *const i8; + | |______________________________________^ + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast +help: try + | +375 ~ *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE +376 + .system_directory +377 + .as_ref() +378 + .unwrap() +379 ~ .as_ptr(); + | + +warning: writing `&Vec` instead of `&[_]` involves a new object where a slice will do + --> src/main.rs:660:50 + | +660 | unsafe fn play_audio(sink: &Sink, audio_samples: &Vec, sample_rate: u32) { + | ^^^^^^^^^ help: change this to: `&[i16]` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#ptr_arg + = note: `#[warn(clippy::ptr_arg)]` on by default + +warning: casting raw pointers to the same type and constness is unnecessary (`*const i16` -> `*const i16`) + --> src/main.rs:666:40 + | +666 | std::slice::from_raw_parts(audio_samples.as_ptr() as *const i16, audio_samples.len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `audio_samples.as_ptr()` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + +warning: casting raw pointers to the same type and constness is unnecessary (`*const u32` -> `*const u32`) + --> src/main.rs:1032:52 + | +1032 | std::slice::from_raw_parts(buffer.as_ptr() as *const u32, buffer.len()); // convert to &[u32] slice reference + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `buffer.as_ptr()` + | + = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast + +warning: constant `audio_enable` should have an upper case name + --> src/main.rs:27:7 + | +27 | const audio_enable: bool = false; + | ^^^^^^^^^^^^ help: convert the identifier to upper case: `AUDIO_ENABLE` + | + = note: `#[warn(non_upper_case_globals)]` on by default + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:202:13 + | +202 | CURRENT_EMULATOR_STATE.pixel_format + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + = note: `#[warn(static_mut_refs)]` on by default + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:228:11 + | +228 | match &CURRENT_EMULATOR_STATE.buttons_pressed { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +228 | match &raw const CURRENT_EMULATOR_STATE.buttons_pressed { + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:359:58 + | +359 | *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE + | __________________________________________________________^ +360 | | .system_directory +361 | | .as_ref() + | |_________________________^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:372:40 + | +372 | println!("Rom name: {:?}", CURRENT_EMULATOR_STATE.rom_name); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:373:39 + | +373 | println!("Pointer: {:?}", CURRENT_EMULATOR_STATE.rom_name.as_ptr()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:375:58 + | +375 | *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE + | __________________________________________________________^ +376 | | .system_directory +377 | | .as_ref() + | |_________________________^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:467:29 + | +467 | let game_info = CURRENT_EMULATOR_STATE.game_info.clone().unwrap_unchecked(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:471:17 + | +471 | CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:475:17 + | +475 | CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:478:17 + | +478 | CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; // TODO: Convert to Cstring + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:480:17 + | +480 | CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; // TODO: Convert to Cstring + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:484:17 + | +484 | CURRENT_EMULATOR_STATE.game_info.as_ref().unwrap().size; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:486:17 + | +486 | CURRENT_EMULATOR_STATE.game_info.as_ref().unwrap().data; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: a dangling pointer will be produced because the temporary `std::ffi::CString` will be dropped + --> src/main.rs:633:10 + | +631 | let rom_name_cptr = CString::new(rom_name.clone()) + | _________________________- +632 | | .expect("Failed to create CString") + | |___________________________________________- this `std::ffi::CString` is deallocated at the end of the statement, bind it to a variable to extend its lifetime +633 | .as_ptr(); + | ^^^^^^ this pointer will immediately be invalid + | + = note: pointers do not have a lifetime; when calling `as_ptr` the `std::ffi::CString` will be deallocated at the end of the statement because nothing is referencing it as far as the type system is concerned + = help: you must make sure that the variable you bind the `std::ffi::CString` to lives at least as long as the pointer returned by the call to `as_ptr` + = help: in particular, if this pointer is returned from the current function, binding the `std::ffi::CString` inside the function will not suffice + = help: for more information, see + = note: `#[warn(dangling_pointers_from_temporaries)]` on by default + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:655:25 + | +655 | if let Some(data) = &CURRENT_EMULATOR_STATE.audio_data { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +655 | if let Some(data) = &raw const CURRENT_EMULATOR_STATE.audio_data { + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:715:9 + | +715 | &CURRENT_EMULATOR_STATE.rom_name, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +715 | &raw const CURRENT_EMULATOR_STATE.rom_name, + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:730:9 + | +730 | &CURRENT_EMULATOR_STATE.rom_name, + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +730 | &raw const CURRENT_EMULATOR_STATE.rom_name, + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:879:23 + | +879 | match &CURRENT_EMULATOR_STATE.av_info { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +879 | match &raw const CURRENT_EMULATOR_STATE.av_info { + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:915:30 + | +915 | core_api = load_core(&CURRENT_EMULATOR_STATE.core_name); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +915 | core_api = load_core(&raw const CURRENT_EMULATOR_STATE.core_name); + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:923:45 + | +923 | println!("About to load ROM: {:?}", CURRENT_EMULATOR_STATE.rom_name); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:924:34 + | +924 | load_rom_file(&core_api, &CURRENT_EMULATOR_STATE.rom_name); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +924 | load_rom_file(&core_api, &raw const CURRENT_EMULATOR_STATE.rom_name); + | +++++++++ + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:1004:29 + | +1004 | ... CURRENT_EMULATOR_STATE.current_save_slot + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:1014:29 + | +1014 | ... CURRENT_EMULATOR_STATE.current_save_slot + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives + +warning: creating a shared reference to mutable static is discouraged + --> src/main.rs:1025:19 + | +1025 | match &CURRENT_EMULATOR_STATE.frame_buffer { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ shared reference to mutable static + | + = note: for more information, see + = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives +help: use `&raw const` instead to create a raw pointer + | +1025 | match &raw const CURRENT_EMULATOR_STATE.frame_buffer { + | +++++++++ + +warning: `rustro_arch` (bin "rustro_arch") generated 41 warnings + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.32s diff --git a/src/main.rs b/src/main.rs index 7e5bdaf..5fa24b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,29 +1,31 @@ extern crate libc; extern crate libloading; use clap::{App, Arg}; +use log::{debug, error, info, trace, warn}; use libloading::Library; -use libretro_sys::{CoreAPI, GameInfo, PixelFormat, SystemAvInfo, GameGeometry, SystemTiming, LogCallback, LogLevel}; +use libretro_sys::{ + CoreAPI, GameGeometry, GameInfo, LogCallback, LogLevel, PixelFormat, SystemAvInfo, SystemTiming, +}; use minifb::{Key, KeyRepeat, Window, WindowOptions}; +use rodio::buffer::SamplesBuffer; +use rodio::{OutputStream, Sink}; use std::collections::HashMap; -use std::ffi::{c_void, CString, CStr}; +use std::ffi::{c_void, CStr, CString}; use std::fs::File; use std::io::Read; use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; -use std::time::{Duration, Instant}; -use std::{env, fs, ptr, mem}; // Add this line to import the Read trait -use rodio::{Sink, OutputStream, OutputStreamHandle}; -use rodio::buffer::SamplesBuffer; use std::sync::mpsc::{channel, Sender}; use std::thread; +use std::time::{Duration, Instant}; +use std::{env, fs, ptr}; // Add this line to import the Read trait -use gilrs::{Gilrs, Button, Event}; - +use gilrs::{Button, Event, Gilrs}; const EXPECTED_LIB_RETRO_VERSION: u32 = 1; -const audio_enable: bool = false; +const AUDIO_ENABLE: bool = false; struct EmulatorState { rom_name: String, @@ -39,8 +41,14 @@ struct EmulatorState { current_save_slot: u8, av_info: Option, game_info: Option, + #[allow(dead_code)] // TODO: Implement full GameInfoExt handling game_info_ext: Option, - system_directory: Option + system_directory: Option, + // New CString fields for ROM path components + rom_full_path_c: Option, + rom_dir_c: Option, + rom_name_c: Option, + rom_ext_c: Option, } static mut CURRENT_EMULATOR_STATE: EmulatorState = EmulatorState { @@ -58,7 +66,11 @@ static mut CURRENT_EMULATOR_STATE: EmulatorState = EmulatorState { av_info: None, game_info: None, game_info_ext: None, - system_directory: None + system_directory: None, + rom_full_path_c: None, + rom_dir_c: None, + rom_name_c: None, + rom_ext_c: None, }; // retro_game_info_ext wasn't in libretro-sys package so declaring it here @@ -82,6 +94,7 @@ pub struct GameInfoExt { //////////////////////// // Convert the input String to a CString, but be-careful with memory management when sending this to a core.. +#[allow(dead_code)] fn convert_to_cstring(input: String) -> CString { CString::new(input).expect("Failed to convert to CString") } @@ -92,7 +105,7 @@ fn print_c_string(c_string_ptr: *const libc::c_char) { if !c_string_ptr.is_null() { let c_str = CStr::from_ptr(c_string_ptr); if let Ok(rust_string) = c_str.to_str() { - println!("{}", rust_string); + info!("{}", rust_string); // Changed from println! } } } @@ -101,13 +114,37 @@ fn print_c_string(c_string_ptr: *const libc::c_char) { /////////////////////// // Config Functions /////////////////////// -fn get_retroarch_config_path() -> PathBuf { - return match std::env::consts::OS { - "windows" => PathBuf::from(env::var("APPDATA").ok().unwrap()).join("retroarch"), - "macos" => PathBuf::from(env::var("HOME").ok().unwrap()) - .join("Library/Application Support/RetroArch"), - _ => PathBuf::from(env::var("XDG_CONFIG_HOME").ok().unwrap()).join("retroarch"), - }; +fn get_retroarch_config_path_for_os( + os_name: &str, + home_dir: Option<&str>, + appdata_dir: Option<&str>, + xdg_config_home_dir: Option<&str>, +) -> PathBuf { + match os_name { + "windows" => { + let appdata = appdata_dir + .expect("APPDATA environment variable not found or is invalid for Windows"); + PathBuf::from(appdata).join("retroarch") + } + "macos" => { + let home = home_dir.expect("HOME environment variable not found or is invalid for macOS"); + PathBuf::from(home).join("Library/Application Support/RetroArch") + } + _ => { // Default to Linux/XDG behavior + let xdg_config_home = xdg_config_home_dir + .expect("XDG_CONFIG_HOME environment variable not found or is invalid for Linux/other"); + PathBuf::from(xdg_config_home).join("retroarch") + } + } +} + +pub fn get_retroarch_config_path() -> PathBuf { + get_retroarch_config_path_for_os( + std::env::consts::OS, + std::env::var("HOME").ok().as_deref(), + std::env::var("APPDATA").ok().as_deref(), + std::env::var("XDG_CONFIG_HOME").ok().as_deref(), + ) } fn parse_retroarch_config(config_file: &Path) -> Result, String> { @@ -127,7 +164,7 @@ fn parse_retroarch_config(config_file: &Path) -> Result, } fn convert_pixel_array_from_rgb565_to_xrgb8888(color_array: &[u8]) -> Box<[u32]> { - println!("convert_pixel_array_from_rgb565_to_xrgb8888"); + debug!("convert_pixel_array_from_rgb565_to_xrgb8888"); // Changed from println! let bytes_per_pixel = 2; assert_eq!( color_array.len() % bytes_per_pixel, @@ -168,59 +205,75 @@ unsafe extern "C" fn libretro_set_video_refresh_callback( height: libc::c_uint, pitch: libc::size_t, ) { - println!("libretro_set_video_refresh_callback width: {} height: {} pitch: {}", width, height, pitch); - if (frame_buffer_data == ptr::null()) { - println!("frame_buffer_data was null"); + trace!( // Changed from println! + "libretro_set_video_refresh_callback width: {} height: {} pitch: {}", + width, height, pitch + ); + if frame_buffer_data.is_null() { + warn!("frame_buffer_data was null"); // Changed from println! return; } - let length_of_frame_buffer = - ((pitch as u32) * height) * CURRENT_EMULATOR_STATE.bytes_per_pixel as u32; - println!("length_of_frame_buffer: {}", length_of_frame_buffer); + // The pitch is the number of bytes in a scanline. + // The total buffer size is pitch * height. + let length_of_frame_buffer = (pitch as u32) * height; + trace!("length_of_frame_buffer: {}", length_of_frame_buffer); let buffer_slice = std::slice::from_raw_parts( frame_buffer_data as *const u8, - length_of_frame_buffer as usize, + length_of_frame_buffer as usize, // length_of_frame_buffer is already in bytes ); - println!("got buffer_slice"); - let result = match CURRENT_EMULATOR_STATE.pixel_format { + trace!("got buffer_slice"); + let result = match unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).pixel_format } { PixelFormat::RGB565 => Vec::from(convert_pixel_array_from_rgb565_to_xrgb8888(buffer_slice)), PixelFormat::ARGB8888 => { - println!("ARGB8888 len:{} w*h*p: {}", buffer_slice.len(), width * height); - // std::slice::from_raw_parts(buffer_slice.as_ptr() as *const u32, buffer_slice.len()).to_vec() // original code doesn't work in nestopia - std::slice::from_raw_parts(buffer_slice.as_ptr() as *const u32, buffer_slice.len()/4).to_vec() // dividing by 4 here seems to fix nestopia for some reason - }, - _ => panic!("Unknown Pixel Format {:?}", CURRENT_EMULATOR_STATE.pixel_format) + trace!( + "ARGB8888 buffer_slice.len():{}, width*height: {}", // Log actual byte length and expected pixels + buffer_slice.len(), + width * height + ); + // For ARGB8888, each pixel is 4 bytes (u32). Convert byte slice to u32 slice. + // The length of the u32 slice is the total byte length divided by 4 (size of u32). + std::slice::from_raw_parts(buffer_slice.as_ptr() as *const u32, buffer_slice.len() / 4) + .to_vec() + } + _ => panic!( + "Unknown Pixel Format {:?}", + unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).pixel_format } + ), }; - println!("Middle of libretro_set_video_refresh_callback"); + trace!("Middle of libretro_set_video_refresh_callback"); // Changed from println! // Wrap the Vec in an Option and assign it to the frame_buffer field CURRENT_EMULATOR_STATE.frame_buffer = Some(result); CURRENT_EMULATOR_STATE.screen_height = height; CURRENT_EMULATOR_STATE.screen_width = width; CURRENT_EMULATOR_STATE.screen_pitch = pitch as u32; - println!("End of libretro_set_video_refresh_callback") + trace!("End of libretro_set_video_refresh_callback") // Changed from println! } unsafe extern "C" fn libretro_set_input_poll_callback() { - println!("libretro_set_input_poll_callback") + trace!("libretro_set_input_poll_callback") // Changed from println! } unsafe extern "C" fn libretro_set_input_state_callback( - port: libc::c_uint, - device: libc::c_uint, - index: libc::c_uint, + _port: libc::c_uint, + _device: libc::c_uint, + _index: libc::c_uint, id: libc::c_uint, ) -> i16 { - // println!("libretro_set_input_state_callback port: {} device: {} index: {} id: {}", port, device, index, id); - let is_pressed = match &CURRENT_EMULATOR_STATE.buttons_pressed { + // trace!("libretro_set_input_state_callback port: {} device: {} index: {} id: {}", port, device, index, id); + + + match unsafe { &(*(&raw const CURRENT_EMULATOR_STATE)).buttons_pressed } { Some(buttons_pressed) => buttons_pressed[id as usize], None => 0, - }; - - return is_pressed; + } } unsafe extern "C" fn libretro_set_audio_sample_callback(left: i16, right: i16) { - println!("libretro_set_audio_sample_callback left channel: {} right: {}", left, right); + trace!( // Changed from println! + "libretro_set_audio_sample_callback left channel: {} right: {}", + left, right + ); } const AUDIO_CHANNELS: usize = 2; // left and right @@ -230,21 +283,32 @@ unsafe extern "C" fn libretro_set_audio_sample_batch_callback( ) -> libc::size_t { let audio_slice = std::slice::from_raw_parts(audio_data, frames * AUDIO_CHANNELS); CURRENT_EMULATOR_STATE.audio_data = Some(audio_slice.to_vec()); - return frames; + frames } unsafe extern "C" fn libretro_log_print_callback(level: LogLevel, fmt: *const libc::c_char) { - print!("{:?}: ", level); - print_c_string(fmt); + let message = if !fmt.is_null() { + CStr::from_ptr(fmt).to_string_lossy().into_owned() + } else { + String::from("") + }; + + match level { + LogLevel::Debug => debug!("LibretroCore: {}", message), // Changed from print! + LogLevel::Info => info!("LibretroCore: {}", message), // Changed from print! + LogLevel::Warn => warn!("LibretroCore: {}", message), // Changed from print! + LogLevel::Error => error!("LibretroCore: {}", message), // Changed from print! + _ => info!("LibretroCore (unknown level {}): {}", level.0, message), // Changed from print! + } } // NOTE: In the implementation of this function make sure you only send CString's to return_data, otherwise the core will not know when the String ends! unsafe extern "C" fn libretro_environment_callback(command: u32, return_data: *mut c_void) -> bool { - println!("libretro_environment_callback command:{}", command); - return match command { + debug!("libretro_environment_callback command:{}", command); // Changed from println! + match command { libretro_sys::ENVIRONMENT_GET_CAN_DUPE => { *(return_data as *mut bool) = true; // Set the return_data to the value true - println!("Set ENVIRONMENT_GET_CAN_DUPE to true"); + info!("Set ENVIRONMENT_GET_CAN_DUPE to true"); // Changed from println! false } libretro_sys::ENVIRONMENT_SET_PIXEL_FORMAT => { @@ -253,224 +317,257 @@ unsafe extern "C" fn libretro_environment_callback(command: u32, return_data: *m CURRENT_EMULATOR_STATE.pixel_format = pixel_format_as_enum; match pixel_format_as_enum { PixelFormat::ARGB1555 => { - println!( + info!( // Changed from println! "Core will send us pixel data in the RETRO_PIXEL_FORMAT_0RGB1555 format" ); CURRENT_EMULATOR_STATE.bytes_per_pixel = 2; } PixelFormat::RGB565 => { - println!( + info!( // Changed from println! "Core will send us pixel data in the RETRO_PIXEL_FORMAT_RGB565 format" ); CURRENT_EMULATOR_STATE.bytes_per_pixel = 2; } PixelFormat::ARGB8888 => { - println!( + info!( // Changed from println! "Core will send us pixel data in the RETRO_PIXEL_FORMAT_XRGB8888 format" ); CURRENT_EMULATOR_STATE.bytes_per_pixel = 4; } _ => { - panic!("Core is trying to use an Unknown Pixel Format") + unreachable!("PixelFormat should have been validated by PixelFormat::from_uint earlier or it would have panicked") } } true } libretro_sys::ENVIRONMENT_SET_MEMORY_MAPS => { - println!("TODO: Handle ENVIRONMENT_SET_MEMORY_MAPS"); + warn!("TODO: Handle ENVIRONMENT_SET_MEMORY_MAPS"); // Changed from println! true } libretro_sys::ENVIRONMENT_SET_CONTROLLER_INFO => { - println!("TODO: Handle ENVIRONMENT_SET_CONTROLLER_INFO"); + warn!("TODO: Handle ENVIRONMENT_SET_CONTROLLER_INFO"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_VARIABLE_UPDATE => { - println!("INFO: Ignoring ENVIRONMENT_GET_VARIABLE_UPDATE"); + debug!("INFO: Ignoring ENVIRONMENT_GET_VARIABLE_UPDATE"); // Changed from println! // Return true when we have changed variables that the core needs to know about, but we don't change anything yet false } // All the GETs not currently supported libretro_sys::ENVIRONMENT_GET_CAMERA_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_CAMERA_INTERFACE"); + warn!("TODO: Handle ENVIRONMENT_GET_CAMERA_INTERFACE"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY => { - println!("TODO: Handle ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY"); + warn!("TODO: Handle ENVIRONMENT_GET_CORE_ASSETS_DIRECTORY"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER => { - println!("TODO: Handle ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER"); + warn!("TODO: Handle ENVIRONMENT_GET_CURRENT_SOFTWARE_FRAMEBUFFER"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_HW_RENDER_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_HW_RENDER_INTERFACE"); + warn!("TODO: Handle ENVIRONMENT_GET_HW_RENDER_INTERFACE"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES => { - println!("TODO: Handle ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES"); + warn!("TODO: Handle ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_LANGUAGE => { - println!("TODO: Handle ENVIRONMENT_GET_LANGUAGE"); + warn!("TODO: Handle ENVIRONMENT_GET_LANGUAGE"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_LIBRETRO_PATH => { - println!("TODO: Handle ENVIRONMENT_GET_LIBRETRO_PATH"); + warn!("TODO: Handle ENVIRONMENT_GET_LIBRETRO_PATH"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_LOCATION_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_LOCATION_INTERFACE"); + warn!("TODO: Handle ENVIRONMENT_GET_LOCATION_INTERFACE"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_LOG_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_LOG_INTERFACE"); + info!("TODO: Handle ENVIRONMENT_GET_LOG_INTERFACE"); // Changed from println! (*(return_data as *mut LogCallback)).log = libretro_log_print_callback; true } libretro_sys::ENVIRONMENT_GET_OVERSCAN => { - println!("TODO: Handle ENVIRONMENT_GET_OVERSCAN"); + warn!("TODO: Handle ENVIRONMENT_GET_OVERSCAN"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_PERF_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_PERF_INTERFACE"); + warn!("TODO: Handle ENVIRONMENT_GET_PERF_INTERFACE"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_RUMBLE_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_RUMBLE_INTERFACE"); + warn!("TODO: Handle ENVIRONMENT_GET_RUMBLE_INTERFACE"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_SAVE_DIRECTORY => { - println!("TODO: Handle ENVIRONMENT_GET_SAVE_DIRECTORY"); - *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE.system_directory.as_ref().unwrap().as_ptr() as *const i8; // TODO use CString otherwise this will segfault - true + warn!("TODO: Handle ENVIRONMENT_GET_SAVE_DIRECTORY"); + match unsafe { &(*(&raw const CURRENT_EMULATOR_STATE)).system_directory } { + Some(s_dir_c) => { + *(return_data as *mut *const libc::c_char) = s_dir_c.as_ptr(); + true + } + None => { + *(return_data as *mut *const libc::c_char) = ptr::null(); + // Consider returning false if a null ptr isn't acceptable by core for this. + // For now, assume core handles null or we should provide a default. + warn!("System directory CString is None for ENVIRONMENT_GET_SAVE_DIRECTORY"); + true + } + } } libretro_sys::ENVIRONMENT_GET_SENSOR_INTERFACE => { - println!("TODO: Handle ENVIRONMENT_GET_SENSOR_INTERFACE"); + warn!("TODO: Handle ENVIRONMENT_GET_SENSOR_INTERFACE"); true } libretro_sys::ENVIRONMENT_GET_SYSTEM_DIRECTORY => { - println!("TODO: Handle ENVIRONMENT_GET_SYSTEM_DIRECTORY"); - println!("Rom name: {:?}", CURRENT_EMULATOR_STATE.rom_name); - println!("Pointer: {:?}", CURRENT_EMULATOR_STATE.rom_name.as_ptr()); - - *(return_data as *mut *const libc::c_char) = CURRENT_EMULATOR_STATE.system_directory.as_ref().unwrap().as_ptr() as *const i8; - println!("return_data: {:?}", return_data); - true + info!("ENVIRONMENT_GET_SYSTEM_DIRECTORY called"); + match unsafe { &(*(&raw const CURRENT_EMULATOR_STATE)).system_directory } { + Some(s_dir_c) => { + *(return_data as *mut *const libc::c_char) = s_dir_c.as_ptr(); + debug!("System directory provided: {:?}", s_dir_c); + true + } + None => { + *(return_data as *mut *const libc::c_char) = ptr::null(); + warn!("System directory CString is None for ENVIRONMENT_GET_SYSTEM_DIRECTORY"); + // Returning false might be more appropriate if the core requires a valid path. + // However, providing null and returning true is also a common pattern. + true + } + } } libretro_sys::ENVIRONMENT_GET_USERNAME => { - println!("TODO: Handle ENVIRONMENT_GET_USERNAME"); + warn!("TODO: Handle ENVIRONMENT_GET_USERNAME"); // Changed from println! true } libretro_sys::ENVIRONMENT_GET_VARIABLE => { - println!("TODO: Handle ENVIRONMENT_GET_VARIABLE command: {}", command); // 15 + warn!("TODO: Handle ENVIRONMENT_GET_VARIABLE command: {}", command); // Changed from println! // 15 false } // Rest of the SET_ - libretro_sys::ENVIRONMENT_SET_DISK_CONTROL_INTERFACE=> { - println!("TODO: Handle ENVIRONMENT_SET_DISK_CONTROL_INTERFACE"); + libretro_sys::ENVIRONMENT_SET_DISK_CONTROL_INTERFACE => { + warn!("TODO: Handle ENVIRONMENT_SET_DISK_CONTROL_INTERFACE"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_FRAME_TIME_CALLBACK=> { - println!("TODO: Handle ENVIRONMENT_SET_FRAME_TIME_CALLBACK"); + libretro_sys::ENVIRONMENT_SET_FRAME_TIME_CALLBACK => { + warn!("TODO: Handle ENVIRONMENT_SET_FRAME_TIME_CALLBACK"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_GEOMETRY=> { - println!("TODO: Handle ENVIRONMENT_SET_GEOMETRY"); + libretro_sys::ENVIRONMENT_SET_GEOMETRY => { + warn!("TODO: Handle ENVIRONMENT_SET_GEOMETRY"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_HW_RENDER=> { - println!("TODO: Handle ENVIRONMENT_SET_HW_RENDER"); + libretro_sys::ENVIRONMENT_SET_HW_RENDER => { + warn!("TODO: Handle ENVIRONMENT_SET_HW_RENDER"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_INPUT_DESCRIPTORS=> { - println!("TODO: Handle ENVIRONMENT_SET_INPUT_DESCRIPTORS"); + libretro_sys::ENVIRONMENT_SET_INPUT_DESCRIPTORS => { + warn!("TODO: Handle ENVIRONMENT_SET_INPUT_DESCRIPTORS"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_KEYBOARD_CALLBACK=> { - println!("TODO: Handle ENVIRONMENT_SET_KEYBOARD_CALLBACK"); + libretro_sys::ENVIRONMENT_SET_KEYBOARD_CALLBACK => { + warn!("TODO: Handle ENVIRONMENT_SET_KEYBOARD_CALLBACK"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_MESSAGE=> { - println!("TODO: Handle ENVIRONMENT_SET_MESSAGE"); + libretro_sys::ENVIRONMENT_SET_MESSAGE => { + warn!("TODO: Handle ENVIRONMENT_SET_MESSAGE"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_PERFORMANCE_LEVEL=> { - println!("TODO: Handle ENVIRONMENT_SET_PERFORMANCE_LEVEL"); + libretro_sys::ENVIRONMENT_SET_PERFORMANCE_LEVEL => { + warn!("TODO: Handle ENVIRONMENT_SET_PERFORMANCE_LEVEL"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK=> { - println!("TODO: Handle ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK"); + libretro_sys::ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK => { + warn!("TODO: Handle ENVIRONMENT_SET_PROC_ADDRESS_CALLBACK"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_ROTATION=> { - println!("TODO: Handle ENVIRONMENT_SET_ROTATION"); + libretro_sys::ENVIRONMENT_SET_ROTATION => { + warn!("TODO: Handle ENVIRONMENT_SET_ROTATION"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_SUBSYSTEM_INFO=> { - println!("TODO: Handle ENVIRONMENT_SET_SUBSYSTEM_INFO"); + libretro_sys::ENVIRONMENT_SET_SUBSYSTEM_INFO => { + warn!("TODO: Handle ENVIRONMENT_SET_SUBSYSTEM_INFO"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_SUPPORT_NO_GAME=> { - println!("TODO: Handle ENVIRONMENT_SET_SUPPORT_NO_GAME"); + libretro_sys::ENVIRONMENT_SET_SUPPORT_NO_GAME => { + warn!("TODO: Handle ENVIRONMENT_SET_SUPPORT_NO_GAME"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_SYSTEM_AV_INFO=> { - println!("TODO: Handle ENVIRONMENT_SET_SYSTEM_AV_INFO"); + libretro_sys::ENVIRONMENT_SET_SYSTEM_AV_INFO => { + warn!("TODO: Handle ENVIRONMENT_SET_SYSTEM_AV_INFO"); // Changed from println! true } - libretro_sys::ENVIRONMENT_SET_VARIABLES=> { - println!("TODO: Handle ENVIRONMENT_SET_VARIABLES"); + libretro_sys::ENVIRONMENT_SET_VARIABLES => { + warn!("TODO: Handle ENVIRONMENT_SET_VARIABLES"); // Changed from println! true } libretro_sys::ENVIRONMENT_EXPERIMENTAL => { - println!("TODO: Handle ENVIRONMENT_EXPERIMENTAL"); + warn!("TODO: Handle ENVIRONMENT_EXPERIMENTAL"); // Changed from println! true } libretro_sys::ENVIRONMENT_PRIVATE => { - println!("TODO: Handle ENVIRONMENT_PRIVATE"); + warn!("TODO: Handle ENVIRONMENT_PRIVATE"); // Changed from println! true } libretro_sys::ENVIRONMENT_SHUTDOWN => { - println!("TODO: Handle ENVIRONMENT_SHUTDOWN"); + warn!("TODO: Handle ENVIRONMENT_SHUTDOWN"); // Changed from println! true } 55 => { - println!("TODO: Handle RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY"); + warn!("TODO: Handle RETRO_ENVIRONMENT_SET_CORE_OPTIONS_DISPLAY"); // Changed from println! false } 66 => { // TODO: need to return retro_game_info_ext retro_game_info_ext - println!("TODO: Handle ENVIRONMENT_GET_GAME_INFO_EXT"); - let game_info = CURRENT_EMULATOR_STATE.game_info.clone().unwrap_unchecked(); - - let data = (return_data as *mut GameInfoExt); - (*(return_data as *mut GameInfoExt)).full_path = CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; - (*(return_data as *mut GameInfoExt)).archive_path = ptr::null(); - (*(return_data as *mut GameInfoExt)).archive_file = ptr::null(); - (*(return_data as *mut GameInfoExt)).ext = CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; - (*(return_data as *mut GameInfoExt)).meta = ptr::null(); - (*(return_data as *mut GameInfoExt)).dir = CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; // TODO: Convert to Cstring - (*(return_data as *mut GameInfoExt)).name = CURRENT_EMULATOR_STATE.rom_name.as_ptr() as *const i8; // TODO: Convert to Cstring - (*(return_data as *mut GameInfoExt)).file_in_archive = false; - (*(return_data as *mut GameInfoExt)).persistent_data = true; - (*(return_data as *mut GameInfoExt)).size = CURRENT_EMULATOR_STATE.game_info.as_ref().unwrap().size; - (*(return_data as *mut GameInfoExt)).data = CURRENT_EMULATOR_STATE.game_info.as_ref().unwrap().data; - println!("Data size {}", (*(return_data as *mut GameInfoExt)).size); - + info!("Handle ENVIRONMENT_GET_GAME_INFO_EXT"); + let game_info_ext_ptr = return_data as *mut GameInfoExt; + + unsafe { + // Assign pointers from EmulatorState's CString fields + (*game_info_ext_ptr).full_path = CURRENT_EMULATOR_STATE.rom_full_path_c.as_ref().map_or(ptr::null(), |cs| cs.as_ptr()); + (*game_info_ext_ptr).dir = CURRENT_EMULATOR_STATE.rom_dir_c.as_ref().map_or(ptr::null(), |cs| cs.as_ptr()); + (*game_info_ext_ptr).name = CURRENT_EMULATOR_STATE.rom_name_c.as_ref().map_or(ptr::null(), |cs| cs.as_ptr()); + (*game_info_ext_ptr).ext = CURRENT_EMULATOR_STATE.rom_ext_c.as_ref().map_or(ptr::null(), |cs| cs.as_ptr()); + + // These fields are typically null if not loading from an archive directly + (*game_info_ext_ptr).archive_path = ptr::null(); + (*game_info_ext_ptr).archive_file = ptr::null(); + (*game_info_ext_ptr).file_in_archive = false; + + (*game_info_ext_ptr).meta = ptr::null(); // No specific metadata for now + + if let Some(game_info_ref) = (*(&raw const CURRENT_EMULATOR_STATE)).game_info.as_ref() { + (*game_info_ext_ptr).data = game_info_ref.data; + (*game_info_ext_ptr).size = game_info_ref.size; + } else { + (*game_info_ext_ptr).data = ptr::null(); + (*game_info_ext_ptr).size = 0; + warn!("GameInfo was None when populating GameInfoExt for data/size"); + } + + (*game_info_ext_ptr).persistent_data = false; // Assuming data is not persistent unless core indicates otherwise + debug!("GameInfoExt populated: full_path: {:?}, dir: {:?}, name: {:?}, ext: {:?}", + (*game_info_ext_ptr).full_path, (*game_info_ext_ptr).dir, (*game_info_ext_ptr).name, (*game_info_ext_ptr).ext); + debug!("GameInfoExt data size: {}", (*game_info_ext_ptr).size); + } true } _ => { - println!( + warn!( // Changed from println! "libretro_environment_callback Called with command: {}", command ); false } - }; + } } -unsafe fn load_core(library_path: &String) -> (CoreAPI) { +unsafe fn load_core(library_path: &String) -> CoreAPI { unsafe { let dylib = Box::leak(Box::new( Library::new(library_path).expect("Failed to load Core"), @@ -515,8 +612,8 @@ unsafe fn load_core(library_path: &String) -> (CoreAPI) { }; let api_version = (core_api.retro_api_version)(); - println!("API Version: {}", api_version); - if (api_version != EXPECTED_LIB_RETRO_VERSION) { + info!("API Version: {}", api_version); // Changed from println! + if api_version != EXPECTED_LIB_RETRO_VERSION { panic!("The Core has been compiled with a LibRetro API that is unexpected, we expected version to be: {} but it was: {}", EXPECTED_LIB_RETRO_VERSION, api_version) } (core_api.retro_set_environment)(libretro_environment_callback); @@ -526,7 +623,7 @@ unsafe fn load_core(library_path: &String) -> (CoreAPI) { (core_api.retro_set_input_state)(libretro_set_input_state_callback); (core_api.retro_set_audio_sample)(libretro_set_audio_sample_callback); (core_api.retro_set_audio_sample_batch)(libretro_set_audio_sample_batch_callback); - return core_api; + core_api } } @@ -562,13 +659,13 @@ fn setup_config() -> Result, String> { .collect(); match retro_arch_config { Ok(config) => merged_config.extend(config), - _ => println!("We don't have RetroArch config"), + _ => warn!("We don't have RetroArch config"), // Changed from println! } match our_config { Ok(config) => merged_config.extend(config), - _ => println!("We don't have RustroArch config",), + _ => warn!("We don't have RustroArch config"), // Changed from println! } - // println!("retro_arch_config_path: {} merged_config: {:?}", retro_arch_config_path.join("config/retroarch.cfg").display(), merged_config); + // debug!("retro_arch_config_path: {} merged_config: {:?}", retro_arch_config_path.join("config/retroarch.cfg").display(), merged_config); Ok(merged_config.clone()) } @@ -592,53 +689,78 @@ unsafe fn parse_command_line_arguments() { let library_name = matches .value_of("library_name") .unwrap_or("default_library"); - println!("ROM name: {}", rom_name); - println!("Core Library name: {}", library_name); + info!("ROM name: {}", rom_name); // Changed from println! + info!("Core Library name: {}", library_name); // Changed from println! CURRENT_EMULATOR_STATE.rom_name = rom_name.to_string(); CURRENT_EMULATOR_STATE.core_name = library_name.to_string(); } unsafe fn load_rom_file(core_api: &CoreAPI, rom_name: &String) -> bool { - println!("Loading ROM file: {:?}", rom_name); - let rom_name_cptr = CString::new(rom_name.clone()) - .expect("Failed to create CString") - .as_ptr(); + info!("Loading ROM file: {:?}", rom_name); + let rom_path = Path::new(rom_name); + + // Populate CString fields in EmulatorState + CURRENT_EMULATOR_STATE.rom_full_path_c = Some(CString::new(rom_name.clone()).expect("Failed to create CString for full_path")); + + if let Some(parent_dir) = rom_path.parent() { + if let Some(dir_str) = parent_dir.to_str() { + CURRENT_EMULATOR_STATE.rom_dir_c = Some(CString::new(dir_str.to_string()).expect("Failed to create CString for dir")); + } else { + CURRENT_EMULATOR_STATE.rom_dir_c = Some(CString::new(".").expect("Default dir CString failed")); // Default to current dir if path is weird + } + } else { + CURRENT_EMULATOR_STATE.rom_dir_c = Some(CString::new(".").expect("Default dir CString failed")); // Default if no parent + } + + if let Some(file_stem) = rom_path.file_stem() { + if let Some(name_str) = file_stem.to_str() { + CURRENT_EMULATOR_STATE.rom_name_c = Some(CString::new(name_str.to_string()).expect("Failed to create CString for name")); + } + } + + if let Some(extension) = rom_path.extension() { + if let Some(ext_str) = extension.to_str() { + CURRENT_EMULATOR_STATE.rom_ext_c = Some(CString::new(ext_str.to_string()).expect("Failed to create CString for ext")); + } + } + + // Use the CString from EmulatorState for GameInfo path if available, otherwise create locally (should always be available now) + let game_info_path_ptr = CURRENT_EMULATOR_STATE.rom_full_path_c.as_ref().map_or(ptr::null(), |cs| cs.as_ptr()); + let contents = fs::read(rom_name).expect("Failed to read file"); let data: *const c_void = contents.as_ptr() as *const c_void; let game_info = GameInfo { - path: rom_name_cptr, + path: game_info_path_ptr, // Use the CString from EmulatorState data, size: contents.len(), meta: ptr::null(), }; - CURRENT_EMULATOR_STATE.game_info = Some(game_info.clone()); + + CURRENT_EMULATOR_STATE.game_info = Some(game_info.clone()); // game_info still holds ptrs from EmulatorState CStrings - println!("INFO: Calling retro_load_game in Core"); + info!("INFO: Calling retro_load_game in Core"); let was_load_successful = (core_api.retro_load_game)(&game_info); - if (!was_load_successful) { + if !was_load_successful { panic!("Rom Load was not successful"); } - println!("ROM was successfully loaded"); - return was_load_successful; + info!("ROM was successfully loaded"); + was_load_successful } unsafe fn send_audio_to_thread(sender: &Sender<&Vec>) { // Send the audio samples to the audio thread using the channel - match &CURRENT_EMULATOR_STATE.audio_data { - Some(data) => { - sender.send(data).unwrap(); - }, - None => {}, + if let Some(data) = unsafe { &(*(&raw const CURRENT_EMULATOR_STATE)).audio_data } { + sender.send(data).unwrap(); }; - } -unsafe fn play_audio( sink: &Sink, audio_samples: &Vec, sample_rate: u32) { - if !audio_enable { +unsafe fn play_audio(sink: &Sink, audio_samples: &[i16], sample_rate: u32) { + if !AUDIO_ENABLE { return; } if sink.empty() { - let audio_slice = std::slice::from_raw_parts(audio_samples.as_ptr() as *const i16, audio_samples.len()); + let audio_slice = + std::slice::from_raw_parts(audio_samples.as_ptr(), audio_samples.len()); let source = SamplesBuffer::new(2, sample_rate, audio_slice); sink.append(source); sink.play(); @@ -650,34 +772,35 @@ fn get_save_state_path( save_directory: &String, game_file_name: &str, save_state_index: u8, -) -> Option { - // Create a subdirectory named "saves" in the current working directory +) -> PathBuf { // Return PathBuf directly let saves_dir = PathBuf::from(save_directory); - if !saves_dir.exists() { - match std::fs::create_dir(&saves_dir) { - Ok(_) => {} - Err(err) => panic!( - "Failed to create save directory: {:?} Error: {}", - &saves_dir, err - ), - } - } - - // Generate the save state filename + // Directory creation removed from here let game_name = Path::new(game_file_name) .file_stem() .unwrap_or_default() .to_string_lossy() .replace(" ", "_"); let save_state_file_name = format!("{}_{}.state", game_name, save_state_index); - - // Combine the saves directory and the save state filename to create the full path - let save_state_path = saves_dir.join(save_state_file_name); - - Some(save_state_path) + saves_dir.join(save_state_file_name) } unsafe fn save_state(core_api: &CoreAPI, save_directory: &String) { + // Create the save directory if it doesn't exist + let saves_dir_path = PathBuf::from(save_directory); + if !saves_dir_path.exists() { + match std::fs::create_dir_all(&saves_dir_path) { // Use create_dir_all + Ok(_) => info!("Created save state directory: {:?}", saves_dir_path), + Err(e) => { + //panic!("Failed to create save directory: {:?} Error: {}", saves_dir_path, e); + // Log error and return, or handle as appropriate for your application + // For now, maintaining panic to match original behavior if critical + error!("Failed to create save state directory {:?}: {}. Save will likely fail.", saves_dir_path, e); + // Optionally, to strictly match original panic: + panic!("Failed to create save directory: {:?} Error: {}", saves_dir_path, e); + } + } + } + let save_state_buffer_size = (core_api.retro_serialize_size)(); let mut state_buffer: Vec = vec![0; save_state_buffer_size]; // Call retro_serialize to create the save state @@ -685,14 +808,14 @@ unsafe fn save_state(core_api: &CoreAPI, save_directory: &String) { state_buffer.as_mut_ptr() as *mut c_void, save_state_buffer_size, ); + // Call the refactored get_save_state_path, .unwrap() is no longer needed let file_path = get_save_state_path( save_directory, - &CURRENT_EMULATOR_STATE.rom_name, - CURRENT_EMULATOR_STATE.current_save_slot, - ) - .unwrap(); + &(*(&raw const CURRENT_EMULATOR_STATE)).rom_name, // Accessing static mut safely + (*(&raw const CURRENT_EMULATOR_STATE)).current_save_slot, // Accessing static mut safely + ); std::fs::write(&file_path, &state_buffer).unwrap(); - println!( + info!( "Save state saved to: {} with size: {}", &file_path.display(), save_state_buffer_size @@ -702,10 +825,9 @@ unsafe fn save_state(core_api: &CoreAPI, save_directory: &String) { unsafe fn load_state(core_api: &CoreAPI, save_directory: &String) { let file_path = get_save_state_path( save_directory, - &CURRENT_EMULATOR_STATE.rom_name, - CURRENT_EMULATOR_STATE.current_save_slot, - ) - .unwrap(); + &(*(&raw const CURRENT_EMULATOR_STATE)).rom_name, // Accessing static mut safely + (*(&raw const CURRENT_EMULATOR_STATE)).current_save_slot, // Accessing static mut safely + ); let mut state_buffer = Vec::new(); match File::open(&file_path) { Ok(mut file) => { @@ -715,23 +837,23 @@ unsafe fn load_state(core_api: &CoreAPI, save_directory: &String) { // Call retro_unserialize to apply the save state let result = (core_api.retro_unserialize)( state_buffer.as_mut_ptr() as *mut c_void, - state_buffer.len() as usize, + state_buffer.len(), ); if result { - println!("Save state loaded from: {}", &file_path.display()); + info!("Save state loaded from: {}", &file_path.display()); // Changed from println! } else { - println!("Failed to load save state: error code {}", result); + error!("Failed to load save state: error code {}", result); // Changed from println! } } - Err(err) => println!("Error reading save state file: {}", err), + Err(err) => error!("Error reading save state file: {}", err), // Changed from println! } } - Err(_) => println!("Save state file not found"), + Err(_) => warn!("Save state file not found: {}", file_path.display()), // Changed from println! } } fn setup_key_device_map(config: &HashMap) -> HashMap<&String, usize> { - return HashMap::from([ + HashMap::from([ ( &config["input_player1_a"], libretro_sys::DEVICE_ID_JOYPAD_A as usize, @@ -780,26 +902,14 @@ fn setup_key_device_map(config: &HashMap) -> HashMap<&String, us &config["input_player1_select"], libretro_sys::DEVICE_ID_JOYPAD_SELECT as usize, ), - ]); + ]) } fn setup_joypad_device_map() -> HashMap { - return HashMap::from([ - ( - Button::South, - libretro_sys::DEVICE_ID_JOYPAD_A as usize, - ), - ( - Button::East, - libretro_sys::DEVICE_ID_JOYPAD_B as usize, - ), - ( - Button::West, - libretro_sys::DEVICE_ID_JOYPAD_X as usize, - ), - ( - Button::North, - libretro_sys::DEVICE_ID_JOYPAD_Y as usize, - ), + HashMap::from([ + (Button::South, libretro_sys::DEVICE_ID_JOYPAD_A as usize), + (Button::East, libretro_sys::DEVICE_ID_JOYPAD_B as usize), + (Button::West, libretro_sys::DEVICE_ID_JOYPAD_X as usize), + (Button::North, libretro_sys::DEVICE_ID_JOYPAD_Y as usize), ( Button::LeftTrigger, libretro_sys::DEVICE_ID_JOYPAD_L as usize, @@ -817,13 +927,10 @@ fn setup_joypad_device_map() -> HashMap { libretro_sys::DEVICE_ID_JOYPAD_R2 as usize, ), ( - Button::DPadDown, + Button::DPadDown, libretro_sys::DEVICE_ID_JOYPAD_DOWN as usize, ), - ( - Button::DPadUp, - libretro_sys::DEVICE_ID_JOYPAD_UP as usize, - ), + (Button::DPadUp, libretro_sys::DEVICE_ID_JOYPAD_UP as usize), ( Button::DPadRight, libretro_sys::DEVICE_ID_JOYPAD_RIGHT as usize, @@ -832,25 +939,281 @@ fn setup_joypad_device_map() -> HashMap { Button::DPadLeft, libretro_sys::DEVICE_ID_JOYPAD_LEFT as usize, ), - ( - Button::Start, - libretro_sys::DEVICE_ID_JOYPAD_START as usize, - ), + (Button::Start, libretro_sys::DEVICE_ID_JOYPAD_START as usize), ( Button::Select, libretro_sys::DEVICE_ID_JOYPAD_SELECT as usize, ), - ]); + ]) +} + +fn init_logger() { + // Set the RUST_LOG environment variable if it's not already set. + // This allows controlling log level via an environment variable. + // Example: RUST_LOG=info ./your_app + // Example: RUST_LOG=rustro_arch=debug ./your_app + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "info"); // Default to info level if not set + } + env_logger::init(); +} + +#[cfg(test)] +mod tests { + use super::*; // To import functions from the outer scope + use std::path::PathBuf; + use std::fs::{self, File}; + use std::io::Write; + use std::collections::HashMap; // Ensure HashMap is in scope for tests + + // Helper function to create a temporary config file for testing + fn create_test_config_file(filename: &str, content: &str) -> PathBuf { + let test_data_dir = PathBuf::from("test_data"); + if !test_data_dir.exists() { + fs::create_dir_all(&test_data_dir).expect("Failed to create test_data directory"); + } + let file_path = test_data_dir.join(filename); + let mut file = File::create(&file_path).expect("Failed to create test file"); + file.write_all(content.as_bytes()).expect("Failed to write to test file"); + file_path + } + + #[test] + fn test_parse_valid_config() { + let content = r#" +key1 = "value1" +key2 = value2 +key3 = " spaced_value " +# Comment line +invalid_line +another_key = "another_value" + "#; + let file_path = create_test_config_file("valid_config.cfg", content); + let result = parse_retroarch_config(&file_path); + assert!(result.is_ok()); + let config_map = result.unwrap(); + assert_eq!(config_map.get("key1"), Some(&"value1".to_string())); + assert_eq!(config_map.get("key2"), Some(&"value2".to_string())); + assert_eq!(config_map.get("key3"), Some(&" spaced_value ".to_string())); // Quotes are stripped, but internal spaces preserved by current logic + assert_eq!(config_map.get("another_key"), Some(&"another_value".to_string())); + assert_eq!(config_map.len(), 4); // Ensure only valid key-value pairs are parsed + fs::remove_file(file_path).expect("Failed to remove test file"); + } + + #[test] + fn test_parse_empty_config() { + let file_path = create_test_config_file("empty_config.cfg", ""); + let result = parse_retroarch_config(&file_path); + assert!(result.is_ok()); + assert!(result.unwrap().is_empty()); + fs::remove_file(file_path).expect("Failed to remove test file"); + } + + #[test] + fn test_parse_malformed_lines_no_equals() { + let content = "key1value1\nrandomtext\n another line without equals "; + let file_path = create_test_config_file("malformed_config.cfg", content); + let result = parse_retroarch_config(&file_path); + assert!(result.is_ok()); + assert!(result.unwrap().is_empty()); // Lines without '=' are ignored + fs::remove_file(file_path).expect("Failed to remove test file"); + } + + #[test] + fn test_parse_non_existent_file() { + let file_path = PathBuf::from("test_data/non_existent_config.cfg"); + let result = parse_retroarch_config(&file_path); + assert!(result.is_err()); + if let Err(e) = result { + assert!(e.contains("Failed to open file")); + } + } + + #[test] + fn test_parse_quoted_values() { + let content = r#" +setting_true = "true" +setting_false = "false" +path_setting = "/usr/local/bin" +empty_quotes = "" +string_with_quotes = "\"quoted string\"" + "#; + let file_path = create_test_config_file("quoted_values.cfg", content); + let result = parse_retroarch_config(&file_path); + assert!(result.is_ok()); + let config_map = result.unwrap(); + assert_eq!(config_map.get("setting_true"), Some(&"true".to_string())); + assert_eq!(config_map.get("setting_false"), Some(&"false".to_string())); + assert_eq!(config_map.get("path_setting"), Some(&"/usr/local/bin".to_string())); + assert_eq!(config_map.get("empty_quotes"), Some(&"".to_string())); + assert_eq!(config_map.get("string_with_quotes"), Some(&"\"quoted string\"".to_string())); // Current logic keeps internal quotes if value itself is quoted + fs::remove_file(file_path).expect("Failed to remove test file"); + } + + #[test] + fn test_get_retroarch_config_path_windows() { + let expected_path = PathBuf::from(r"C:\Users\TestUser\AppData\Roaming\retroarch"); // Use raw string for Windows paths + assert_eq!( + get_retroarch_config_path_for_os("windows", None, Some(r"C:\Users\TestUser\AppData\Roaming"), None), + expected_path + ); + } + + #[test] + fn test_get_retroarch_config_path_macos() { + let expected_path = PathBuf::from("/Users/testuser/Library/Application Support/RetroArch"); + assert_eq!( + get_retroarch_config_path_for_os("macos", Some("/Users/testuser"), None, None), + expected_path + ); + } + + #[test] + fn test_get_retroarch_config_path_linux_xdg_set() { + let expected_path = PathBuf::from("/home/testuser/.config/retroarch"); + assert_eq!( + get_retroarch_config_path_for_os("linux", Some("/home/testuser"), None, Some("/home/testuser/.config")), + expected_path + ); + } + + #[test] + #[should_panic(expected = "APPDATA environment variable not found or is invalid for Windows")] + fn test_get_retroarch_config_path_windows_panic() { + get_retroarch_config_path_for_os("windows", None, None, None); + } + + #[test] + #[should_panic(expected = "HOME environment variable not found or is invalid for macOS")] + fn test_get_retroarch_config_path_macos_panic() { + get_retroarch_config_path_for_os("macos", None, None, None); + } + + #[test] + #[should_panic(expected = "XDG_CONFIG_HOME environment variable not found or is invalid for Linux/other")] + fn test_get_retroarch_config_path_linux_panic() { + get_retroarch_config_path_for_os("linux", None, None, None); + } + + // Tests for get_save_state_path + #[test] + fn test_get_save_state_path_basic() { + let path = get_save_state_path(&String::from("./test_saves"), "My Game.rom", 0); + assert_eq!(path, PathBuf::from("./test_saves/My_Game_0.state")); + } + + #[test] + fn test_get_save_state_path_no_extension() { + let path = get_save_state_path(&String::from("saves"), "MyOtherGame", 15); + assert_eq!(path, PathBuf::from("saves/MyOtherGame_15.state")); + } + + #[test] + fn test_get_save_state_path_with_spaces_in_dir() { + let path = get_save_state_path(&String::from("./my save states"), "game name with spaces.core", 255); + assert_eq!(path, PathBuf::from("./my save states/game_name_with_spaces_255.state")); + } + + #[test] + fn test_get_save_state_path_empty_game_name() { + // Path::new("").file_stem() is Some("") + // .unwrap_or_default() is "" + // .to_string_lossy() is "" + // .replace(" ", "_") is "" + // so format!("{}_{}.state", "", 1) -> "_1.state" + let path = get_save_state_path(&String::from("."), "", 1); + assert_eq!(path, PathBuf::from("./_1.state")); + } + + // Tests for convert_pixel_array_from_rgb565_to_xrgb8888 + #[test] + fn test_convert_rgb565_to_xrgb8888_known_colors() { + // Black (0x0000) -> R=0, G=0, B=0 + // R5: 00000 -> R8: (0<<3)|(0>>2) = 0 + // G6: 000000 -> G8: (0<<2)|(0>>3) = 0 + // B5: 00000 -> B8: (0<<3)|(0>>2) = 0 + // Expected: 0x00000000 + let black_rgb565 = [0x00, 0x00]; + let expected_black_xrgb8888 = 0x00000000; + + // White (0xFFFF) -> R=31, G=63, B=31 + // R5: 11111 (31) -> R8: (31<<3)|(31>>2) = 248 | 7 = 255 + // G6: 111111 (63) -> G8: (63<<2)|(63>>3) = 252 | 7 = 255 + // B5: 11111 (31) -> B8: (31<<3)|(31>>2) = 248 | 7 = 255 + // Expected: 0x00FFFFFF + let white_rgb565 = [0xFF, 0xFF]; + let expected_white_xrgb8888 = 0x00FFFFFF; + + // Red (0xF800) -> R=31, G=0, B=0 + // R5: 11111 (31) -> R8: 255 + // G6: 000000 (0) -> G8: 0 + // B5: 00000 (0) -> B8: 0 + // Expected: 0x00FF0000 + let red_rgb565 = [0xF8, 0x00]; + let expected_red_xrgb8888 = 0x00FF0000; + + // Green (0x07E0) -> R=0, G=63, B=0 + // R5: 00000 (0) -> R8: 0 + // G6: 111110 (62 not 63, typo in prompt, 0x07E0 is G=62) -> G6: 111110 (62) -> G8: (62<<2)|(62>>3) = 248 | 7 = 255 + // Let's re-calculate for G=63 (0x07E0 is R=0, G=31, B=0 if middle 6 bits are G) + // R(5): 00000 = 0 + // G(6): 111110 = 62. (0x07E0 -> 00000 111110 00000) + // B(5): 00000 = 0 + // R8: 0 + // G8: (62<<2)|(62>>3) = 248 | 7 = 255 + // B8: 0 + // Expected for 0x07E0 : 0x0000FF00 + let green_rgb565 = [0x07, 0xE0]; // R=0, G=62, B=0 + let expected_green_xrgb8888 = 0x0000FF00; // Corrected calculation for G=62 + + // Blue (0x001F) -> R=0, G=0, B=31 + // R5: 00000 (0) -> R8: 0 + // G6: 000000 (0) -> G8: 0 + // B5: 11111 (31) -> B8: 255 + // Expected: 0x000000FF + let blue_rgb565 = [0x00, 0x1F]; + let expected_blue_xrgb8888 = 0x000000FF; + + let input_data = [ + &black_rgb565[..], + &white_rgb565[..], + &red_rgb565[..], + &green_rgb565[..], + &blue_rgb565[..], + ].concat(); + + let expected_output = vec![ + expected_black_xrgb8888, + expected_white_xrgb8888, + expected_red_xrgb8888, + expected_green_xrgb8888, + expected_blue_xrgb8888, + ].into_boxed_slice(); + + assert_eq!(convert_pixel_array_from_rgb565_to_xrgb8888(&input_data), expected_output); + } + + #[test] + fn test_convert_rgb565_empty_input() { + assert_eq!(convert_pixel_array_from_rgb565_to_xrgb8888(&[]), vec![].into_boxed_slice()); + } + + #[test] + #[should_panic(expected = "color_array length must be a multiple of 2 (16-bits per pixel)")] + fn test_convert_rgb565_invalid_length() { + convert_pixel_array_from_rgb565_to_xrgb8888(&[0x00]); + } } fn main() { + init_logger(); // Initialize the logger unsafe { parse_command_line_arguments() }; let config = setup_config().unwrap(); let key_device_map = setup_key_device_map(&config); let joypad_device_map = setup_joypad_device_map(); - println!("Setting up minifb window"); + info!("Setting up minifb window"); // Changed from println! let mut window = Window::new("RustroArch", 640, 480, WindowOptions::default()).unwrap_or_else(|e| { panic!("{}", e); @@ -860,30 +1223,33 @@ fn main() { let mut fps_counter = 0; let core_api; - println!("Setting up Audio Thread"); + info!("Setting up Audio Thread"); // Changed from println! // Create a channel for passing audio samples from the main thread to the audio thread let (sender, receiver) = channel(); - + // Spawn a new thread to play back audio - if (audio_enable) { - let audio_thread = thread::spawn(move || { - println!("Audio Thread Started"); - let sample_rate = unsafe { match &CURRENT_EMULATOR_STATE.av_info { - Some(av_info) => av_info.timing.sample_rate, - None => 0.0 - } + if AUDIO_ENABLE { + let _audio_thread = thread::spawn(move || { + info!("Audio Thread Started"); // Changed from println! + let sample_rate = unsafe { + match &(*(&raw const CURRENT_EMULATOR_STATE)).av_info { + Some(av_info) => av_info.timing.sample_rate, + None => 0.0, + } }; let (_stream, stream_handle) = OutputStream::try_default().unwrap(); let sink = Sink::try_new(&stream_handle).unwrap(); loop { // Receive the next set of audio samples from the channel let audio_samples = receiver.recv().unwrap(); - unsafe { play_audio(&sink, audio_samples, sample_rate as u32); } + unsafe { + play_audio(&sink, audio_samples, sample_rate as u32); + } } }); } - println!("Gamepad Setup"); + info!("Gamepad Setup"); // Changed from println! let mut gilrs = Gilrs::new().unwrap(); let mut active_gamepad = None; @@ -901,21 +1267,21 @@ fn main() { }, }; unsafe { - println!("Setting up Core"); + info!("Setting up Core"); // Changed from println! core_api = load_core(&CURRENT_EMULATOR_STATE.core_name); (core_api.retro_init)(); (core_api.retro_get_system_av_info)(&mut av_info); - println!("AV Info: {:?}", &av_info); + info!("AV Info: {:?}", &av_info); // Changed from println! CURRENT_EMULATOR_STATE.av_info = Some(av_info.clone()); // Environment variables CURRENT_EMULATOR_STATE.system_directory = Some(CString::new("System").unwrap()); - println!("About to load ROM: {:?}", CURRENT_EMULATOR_STATE.rom_name); + info!("About to load ROM: {:?}", (*(&raw const CURRENT_EMULATOR_STATE)).rom_name); // Changed from println! load_rom_file(&core_api, &CURRENT_EMULATOR_STATE.rom_name); } let fps = av_info.timing.fps as u64; - window.limit_update_rate(Some(std::time::Duration::from_micros(1000000/fps))); + window.limit_update_rate(Some(std::time::Duration::from_micros(1000000 / fps))); while window.is_open() && !window.is_key_down(Key::Escape) { // Call the libRetro core every frame unsafe { @@ -938,16 +1304,59 @@ fn main() { // Gamepad input Handling // Examine new events - while let Some(Event { id, event, time }) = gilrs.next_event() { - // println!("{:?} New event from {}: {:?}", time, id, event); - active_gamepad = Some(id); + while let Some(Event { id, event: event_type, time: _time }) = gilrs.next_event() { + match event_type { + EventType::Connected => { + info!("Gamepad connected: {:?}", id); + if active_gamepad.is_none() { + active_gamepad = Some(id); + } + } + EventType::Disconnected => { + info!("Gamepad disconnected: {:?}", id); + if active_gamepad == Some(id) { + active_gamepad = None; + // Optionally, try to find another connected gamepad + for (new_id, _gamepad) in gilrs.gamepads() { + active_gamepad = Some(new_id); + break; + } + } + } + _ => { + // Other event types like ButtonPressed, AxisChanged, etc. are handled below by checking state + } + } + // debug!("{:?} New event from {}: {:?}", _time, id, event_type); + if active_gamepad.is_none() { // if the current active gamepad got disconnected and no other was found + for (new_id, _gamepad) in gilrs.gamepads() { // check for any other connected gamepad + active_gamepad = Some(new_id); + info!("Switched active gamepad to {:?}", new_id); + break; + } + } } // You can also use cached gamepad state if let Some(gamepad) = active_gamepad.map(|id| gilrs.gamepad(id)) { - for button in [Button::South, Button::North, Button::East, Button::West, Button::Start, Button::Select, Button::DPadDown, Button::DPadUp, Button::DPadLeft, Button::DPadRight, Button::LeftTrigger, Button::LeftTrigger2, Button::RightTrigger, Button::RightTrigger2] { + for button in [ + Button::South, + Button::North, + Button::East, + Button::West, + Button::Start, + Button::Select, + Button::DPadDown, + Button::DPadUp, + Button::DPadLeft, + Button::DPadRight, + Button::LeftTrigger, + Button::LeftTrigger2, + Button::RightTrigger, + Button::RightTrigger2, + ] { if gamepad.is_pressed(button) { - println!("Button Pressed: {:?}", button); + debug!("Button Pressed: {:?}", button); // Changed from println! let libretro_button = joypad_device_map.get(&button).unwrap(); this_frames_pressed_buttons[*libretro_button] = 1; } @@ -963,62 +1372,62 @@ fn main() { this_frames_pressed_buttons[*libretro_button_id] = 1; continue; } - if &key_as_string == &config["input_save_state"] { + if key_as_string == config["input_save_state"] { save_state(&core_api, &config["savestate_directory"]); continue; } - if &key_as_string == &config["input_load_state"] { + if key_as_string == config["input_load_state"] { load_state(&core_api, &config["savestate_directory"]); continue; } - if &key_as_string == &config["input_state_slot_increase"] { - if CURRENT_EMULATOR_STATE.current_save_slot != 255 { + if key_as_string == config["input_state_slot_increase"] { + if unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).current_save_slot } != 255 { CURRENT_EMULATOR_STATE.current_save_slot += 1; - println!( + info!( // Changed from println! "Current save slot increased to: {}", - CURRENT_EMULATOR_STATE.current_save_slot + unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).current_save_slot } ) } continue; } - if &key_as_string == &config["input_state_slot_decrease"] { - if CURRENT_EMULATOR_STATE.current_save_slot != 0 { + if key_as_string == config["input_state_slot_decrease"] { + if unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).current_save_slot } != 0 { CURRENT_EMULATOR_STATE.current_save_slot -= 1; - println!( + info!( // Changed from println! "Current save slot decreased to: {}", - CURRENT_EMULATOR_STATE.current_save_slot + unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).current_save_slot } ) } continue; } - println!("Unhandled Key Pressed: {} ", key_as_string); + warn!("Unhandled Key Pressed: {} ", key_as_string); // Changed from println! } CURRENT_EMULATOR_STATE.buttons_pressed = Some(this_frames_pressed_buttons); send_audio_to_thread(&sender); - match &CURRENT_EMULATOR_STATE.frame_buffer { + match unsafe { &(*(&raw const CURRENT_EMULATOR_STATE)).frame_buffer } { Some(buffer) => { - let width = (CURRENT_EMULATOR_STATE.screen_pitch - / CURRENT_EMULATOR_STATE.bytes_per_pixel as u32) + let width = (unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).screen_pitch } + / unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).bytes_per_pixel } as u32) as usize; - let height = CURRENT_EMULATOR_STATE.screen_height as usize; + let height = unsafe { (*(&raw const CURRENT_EMULATOR_STATE)).screen_height } as usize; let slice_of_pixel_buffer: &[u32] = std::slice::from_raw_parts(buffer.as_ptr() as *const u32, buffer.len()); // convert to &[u32] slice reference if slice_of_pixel_buffer.len() < width * height * 4 { // The frame buffer isn't big enough so lets add additional pixels just so we can display it let mut vec: Vec = slice_of_pixel_buffer.to_vec(); - // println!("Frame Buffer wasn't big enough"); - vec.resize((width * height * 4) as usize, 0x0000FFFF); // Add any missing pixels with colour blue + // warn!("Frame Buffer wasn't big enough"); // Changed from println! + vec.resize(width * height * 4, 0x0000FFFF); // Add any missing pixels with colour blue window.update_with_buffer(&vec, width, height).unwrap(); } else { window - .update_with_buffer(&slice_of_pixel_buffer, width, height) + .update_with_buffer(slice_of_pixel_buffer, width, height) .unwrap(); } } None => { - println!("We don't have a buffer to display"); + warn!("We don't have a buffer to display"); // Changed from println! } } }