retro_frontend: actually implement variable storage
I'm not certain I'll keep it like this (it should probably be in `FrontendInterface`, so the driver code can serialize how it sees fit, rather than us), but it works to get it done. There's also a lot of janky code I need to clean up before I'm really happy about it.
This commit is contained in:
parent
1624810e55
commit
ad634bf1fa
6 changed files with 284 additions and 49 deletions
96
Cargo.lock
generated
96
Cargo.lock
generated
|
@ -175,6 +175,12 @@ version = "1.13.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.9"
|
version = "0.3.9"
|
||||||
|
@ -191,6 +197,12 @@ version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
version = "0.5.9"
|
version = "0.5.9"
|
||||||
|
@ -200,6 +212,16 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
|
@ -398,7 +420,9 @@ dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
"libretro-sys",
|
"libretro-sys",
|
||||||
"rgb565",
|
"rgb565",
|
||||||
|
"serde",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -440,6 +464,35 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.204"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.204"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_spanned"
|
||||||
|
version = "0.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sharded-slab"
|
name = "sharded-slab"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
|
@ -508,6 +561,40 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml"
|
||||||
|
version = "0.8.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.22.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_spanned",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.40"
|
version = "0.1.40"
|
||||||
|
@ -689,3 +776,12 @@ name = "windows_x86_64_msvc"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.6.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
|
@ -10,7 +10,10 @@ libc = "0.2.155"
|
||||||
libloading = "0.8.3"
|
libloading = "0.8.3"
|
||||||
libretro-sys = "0.1.1"
|
libretro-sys = "0.1.1"
|
||||||
rgb565 = "0.1.3"
|
rgb565 = "0.1.3"
|
||||||
|
serde = { version = "1.0.204", features = ["derive"] }
|
||||||
thiserror = "1.0.61"
|
thiserror = "1.0.61"
|
||||||
|
toml = "0.8.19"
|
||||||
|
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
use crate::joypad::Joypad;
|
use crate::joypad::Joypad;
|
||||||
use crate::libretro_callbacks;
|
use crate::libretro_callbacks;
|
||||||
|
use crate::libretro_core_variable::CoreVariable;
|
||||||
use crate::result::{Error, Result};
|
use crate::result::{Error, Result};
|
||||||
use ffi::CString;
|
use ffi::CString;
|
||||||
use libloading::Library;
|
use libloading::Library;
|
||||||
use libretro_sys::*;
|
use libretro_sys::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi;
|
use std::ffi;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
|
@ -12,10 +14,6 @@ use std::{fs, mem::MaybeUninit};
|
||||||
|
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
|
||||||
// FIXME(lily): Rust 2024 will make a good chunk of this code illegal.
|
|
||||||
// It might be wise to just bind some "simpler" C++ code and make it safe with lifetimes here,
|
|
||||||
// or something. It's a bit of a pickle.
|
|
||||||
|
|
||||||
/// The currently running frontend.
|
/// The currently running frontend.
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
@ -23,19 +21,29 @@ use tracing::{error, info};
|
||||||
/// Only one instance of Frontend can be active in an application.
|
/// Only one instance of Frontend can be active in an application.
|
||||||
pub(crate) static mut FRONTEND: *mut Frontend = std::ptr::null_mut();
|
pub(crate) static mut FRONTEND: *mut Frontend = std::ptr::null_mut();
|
||||||
|
|
||||||
|
|
||||||
/// Interface for the frontend to call to user code.
|
/// Interface for the frontend to call to user code.
|
||||||
pub trait FrontendInterface {
|
pub trait FrontendInterface {
|
||||||
|
/// Called when video is updated.
|
||||||
fn video_update(&mut self, slice: &[u32]);
|
fn video_update(&mut self, slice: &[u32]);
|
||||||
|
|
||||||
|
/// Called when resize occurs.
|
||||||
fn video_resize(&mut self, width: u32, height: u32);
|
fn video_resize(&mut self, width: u32, height: u32);
|
||||||
|
|
||||||
// TODO(lily): This should probably return the amount of consumed frames,
|
// TODO(lily): This should probably return the amount of consumed frames,
|
||||||
// as in some cases that *might* differ?
|
// as in some cases that *might* differ?
|
||||||
fn audio_sample(&mut self, slice: &[i16], size: usize);
|
fn audio_sample(&mut self, slice: &[i16], size: usize);
|
||||||
|
|
||||||
|
/// Called to poll input
|
||||||
fn input_poll(&mut self);
|
fn input_poll(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Per-core settings
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct CoreSettingsFile {
|
||||||
|
#[serde(flatten)]
|
||||||
|
variables: HashMap<String, CoreVariable>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Frontend {
|
pub struct Frontend {
|
||||||
/// The current core's libretro functions.
|
/// The current core's libretro functions.
|
||||||
pub(crate) core_api: Option<CoreAPI>,
|
pub(crate) core_api: Option<CoreAPI>,
|
||||||
|
@ -47,10 +55,11 @@ pub struct Frontend {
|
||||||
|
|
||||||
pub(crate) av_info: Option<SystemAvInfo>,
|
pub(crate) av_info: Option<SystemAvInfo>,
|
||||||
|
|
||||||
/// Core requested pixel format.
|
/// The core's requested pixel format.
|
||||||
|
/// TODO: HW accel. (or just not care)
|
||||||
pub(crate) pixel_format: PixelFormat,
|
pub(crate) pixel_format: PixelFormat,
|
||||||
|
|
||||||
// Converted pixel buffer. We store it here so we don't keep allocating over and over.
|
/// Converted pixel buffer. We store it here so we don't keep allocating over and over.
|
||||||
pub(crate) converted_pixel_buffer: Vec<u32>,
|
pub(crate) converted_pixel_buffer: Vec<u32>,
|
||||||
|
|
||||||
pub(crate) fb_width: u32,
|
pub(crate) fb_width: u32,
|
||||||
|
@ -59,6 +68,9 @@ pub struct Frontend {
|
||||||
|
|
||||||
pub(crate) system_directory: CString,
|
pub(crate) system_directory: CString,
|
||||||
pub(crate) save_directory: CString,
|
pub(crate) save_directory: CString,
|
||||||
|
pub(crate) config_directory: String,
|
||||||
|
|
||||||
|
pub(crate) variables: HashMap<String, CoreVariable>,
|
||||||
|
|
||||||
pub(crate) joypads: HashMap<u32 /* port */, *mut dyn Joypad>,
|
pub(crate) joypads: HashMap<u32 /* port */, *mut dyn Joypad>,
|
||||||
|
|
||||||
|
@ -88,6 +100,9 @@ impl Frontend {
|
||||||
// For now, this is probably fine.
|
// For now, this is probably fine.
|
||||||
system_directory: CString::new("system").unwrap(),
|
system_directory: CString::new("system").unwrap(),
|
||||||
save_directory: CString::new("save").unwrap(),
|
save_directory: CString::new("save").unwrap(),
|
||||||
|
config_directory: "config".into(),
|
||||||
|
|
||||||
|
variables: HashMap::new(),
|
||||||
|
|
||||||
joypads: HashMap::new(),
|
joypads: HashMap::new(),
|
||||||
|
|
||||||
|
@ -122,13 +137,68 @@ impl Frontend {
|
||||||
|
|
||||||
// clear_input_port_device?
|
// clear_input_port_device?
|
||||||
|
|
||||||
|
fn get_config_file_path(&self) -> String {
|
||||||
|
let path = unsafe {
|
||||||
|
let core_api = self.core_api.as_ref().unwrap();
|
||||||
|
|
||||||
|
let mut system_info: MaybeUninit<SystemInfo> = MaybeUninit::uninit();
|
||||||
|
(core_api.retro_get_system_info)(system_info.as_mut_ptr());
|
||||||
|
|
||||||
|
let info = system_info.assume_init();
|
||||||
|
|
||||||
|
let c_name = ffi::CStr::from_ptr(info.library_name);
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"{}/{}.toml",
|
||||||
|
self.config_directory,
|
||||||
|
c_name.to_str().expect("ughh")
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make this a bit less janky (and use Results)
|
||||||
|
|
||||||
|
pub fn load_settings(&mut self) {
|
||||||
|
let path = self.get_config_file_path();
|
||||||
|
|
||||||
|
match fs::exists(path.clone()) {
|
||||||
|
Ok(exists) => {
|
||||||
|
if exists {
|
||||||
|
let data = fs::read_to_string(path).expect("Could not read config");
|
||||||
|
let config =
|
||||||
|
toml::from_str::<CoreSettingsFile>(&data).expect("Could not parse config");
|
||||||
|
self.variables = config.variables;
|
||||||
|
} else {
|
||||||
|
// Save the core's initial settings to disk
|
||||||
|
self.save_settings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Can't seem to read {path}: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_settings(&mut self) {
|
||||||
|
let path = self.get_config_file_path();
|
||||||
|
|
||||||
|
let settings = CoreSettingsFile {
|
||||||
|
variables: self.variables.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let string = toml::to_string(&settings).expect("Could not serialize settings");
|
||||||
|
fs::write(path.clone(), string).expect("Could not save settings to disk");
|
||||||
|
|
||||||
|
info!("Saved settings to {path}");
|
||||||
|
}
|
||||||
|
|
||||||
pub fn load_core<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
pub fn load_core<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
|
||||||
if self.core_loaded() {
|
if self.core_loaded() {
|
||||||
return Err(Error::CoreAlreadyLoaded);
|
return Err(Error::CoreAlreadyLoaded);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("load_core()");
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let lib = Box::new(Library::new(path.as_ref())?);
|
let lib = Box::new(Library::new(path.as_ref())?);
|
||||||
|
|
||||||
|
@ -174,7 +244,7 @@ impl Frontend {
|
||||||
let core_api = api_uninitialized.assume_init();
|
let core_api = api_uninitialized.assume_init();
|
||||||
|
|
||||||
// Let's sanity check the libretro API version against bindings to make sure we can actually use this core.
|
// Let's sanity check the libretro API version against bindings to make sure we can actually use this core.
|
||||||
// If we can't then fail the load.
|
// If we can't, then fail the load.
|
||||||
let api_version = (core_api.retro_api_version)();
|
let api_version = (core_api.retro_api_version)();
|
||||||
if api_version != libretro_sys::API_VERSION {
|
if api_version != libretro_sys::API_VERSION {
|
||||||
error!(
|
error!(
|
||||||
|
@ -187,37 +257,39 @@ impl Frontend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.core_library = Some(lib);
|
||||||
|
self.core_api = Some(core_api);
|
||||||
|
|
||||||
|
let core_api_ref = self.core_api.as_ref().unwrap();
|
||||||
|
|
||||||
// Set required libretro callbacks before calling libretro_init.
|
// Set required libretro callbacks before calling libretro_init.
|
||||||
// Some cores expect some callbacks to be set before libretro_init is called,
|
// Some cores expect some callbacks to be set before libretro_init is called,
|
||||||
// some cores don't. For maximum compatibility, pamper the cores which do.
|
// some cores don't. For maximum compatibility, pamper the cores which do.
|
||||||
(core_api.retro_set_environment)(libretro_callbacks::environment_callback);
|
(core_api_ref.retro_set_environment)(libretro_callbacks::environment_callback);
|
||||||
|
|
||||||
// Initalize the libretro core. We do this first because
|
// Initalize the libretro core. We do this first because
|
||||||
// there are a Few cores which initalize resources that later
|
// there are a Few cores which initalize resources that later
|
||||||
// are poked by the later callback setting that could break if we don't.
|
// are poked by the later callback setting that could break if we don't.
|
||||||
(core_api.retro_init)();
|
(core_api_ref.retro_init)();
|
||||||
|
|
||||||
// Set more libretro callbacks now that we have initalized the core.
|
// Set more libretro callbacks now that we have initalized the core.
|
||||||
(core_api.retro_set_video_refresh)(libretro_callbacks::video_refresh_callback);
|
(core_api_ref.retro_set_video_refresh)(libretro_callbacks::video_refresh_callback);
|
||||||
(core_api.retro_set_input_poll)(libretro_callbacks::input_poll_callback);
|
(core_api_ref.retro_set_input_poll)(libretro_callbacks::input_poll_callback);
|
||||||
(core_api.retro_set_input_state)(libretro_callbacks::input_state_callback);
|
(core_api_ref.retro_set_input_state)(libretro_callbacks::input_state_callback);
|
||||||
(core_api.retro_set_audio_sample_batch)(
|
(core_api_ref.retro_set_audio_sample_batch)(
|
||||||
libretro_callbacks::audio_sample_batch_callback,
|
libretro_callbacks::audio_sample_batch_callback,
|
||||||
);
|
);
|
||||||
|
|
||||||
(core_api.retro_reset)();
|
(core_api_ref.retro_reset)();
|
||||||
|
|
||||||
info!("Core {} loaded", path.as_ref().display());
|
info!("Core {} loaded", path.as_ref().display());
|
||||||
|
|
||||||
// Get AV info
|
// Get AV info
|
||||||
// Like core API, we have to MaybeUninit again.
|
// Like core API, we have to MaybeUninit again.
|
||||||
let mut av_info: MaybeUninit<SystemAvInfo> = MaybeUninit::uninit();
|
let mut av_info: MaybeUninit<SystemAvInfo> = MaybeUninit::uninit();
|
||||||
(core_api.retro_get_system_av_info)(av_info.as_mut_ptr());
|
(core_api_ref.retro_get_system_av_info)(av_info.as_mut_ptr());
|
||||||
|
|
||||||
self.av_info = Some(av_info.assume_init());
|
self.av_info = Some(av_info.assume_init());
|
||||||
|
|
||||||
self.core_library = Some(lib);
|
|
||||||
self.core_api = Some(core_api);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! A libretro frontend as a reusable library crate.
|
//! A libretro frontend as a reusable library crate.
|
||||||
|
|
||||||
mod libretro_callbacks;
|
mod libretro_callbacks;
|
||||||
|
mod libretro_core_variable;
|
||||||
mod libretro_log;
|
mod libretro_log;
|
||||||
|
|
||||||
pub mod libretro_sys_new;
|
pub mod libretro_sys_new;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! Callbacks for libretro
|
//! Callbacks for libretro
|
||||||
use crate::libretro_sys_new::*;
|
|
||||||
use crate::{frontend::*, libretro_log, util};
|
use crate::{frontend::*, libretro_log, util};
|
||||||
|
use crate::{libretro_core_variable, libretro_sys_new::*};
|
||||||
|
|
||||||
use rgb565::Rgb565;
|
use rgb565::Rgb565;
|
||||||
|
|
||||||
|
@ -101,31 +101,25 @@ pub(crate) unsafe extern "C" fn environment_callback(
|
||||||
|
|
||||||
ENVIRONMENT_GET_VARIABLE => {
|
ENVIRONMENT_GET_VARIABLE => {
|
||||||
// Make sure the core actually is giving us a pointer to a *Variable
|
// Make sure the core actually is giving us a pointer to a *Variable
|
||||||
// so we can (if we have it!) fill it in.
|
// so we can fill it in.
|
||||||
if data.is_null() {
|
if data.is_null() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let var = (data as *mut Variable).as_mut().unwrap();
|
let libretro_variable = (data as *mut Variable).as_mut().unwrap();
|
||||||
|
|
||||||
match ffi::CStr::from_ptr(var.key).to_str() {
|
match ffi::CStr::from_ptr(libretro_variable.key).to_str() {
|
||||||
Ok(key) => {
|
Ok(key) => {
|
||||||
debug!("Core wants to get variable \"{key}\"");
|
if (*FRONTEND).variables.contains_key(key) {
|
||||||
|
let value = (*FRONTEND).variables.get_mut(key).unwrap();
|
||||||
// HACK for SwanStation. I really should just serialize these to TOML or something.
|
let value_str = value.get_value();
|
||||||
// (each core iirc provides its own name, so i can just do config/[core].toml)
|
libretro_variable.value = value_str.as_ptr() as *const i8;
|
||||||
if key == "swanstation_MemoryCards_Card1Type"
|
|
||||||
|| key == "swanstation_MemoryCards_Card2Type"
|
|
||||||
{
|
|
||||||
const MC_TYPE: &'static [u8] = b"Shared\0";
|
|
||||||
info!("Forcing {key} to be shared");
|
|
||||||
|
|
||||||
// what an amazing api with a lot
|
|
||||||
// *(data as *mut *const ffi::c_char)
|
|
||||||
var.value = MC_TYPE.as_ptr() as *const i8;
|
|
||||||
return true;
|
return true;
|
||||||
|
} else {
|
||||||
|
// value doesn't exist, tell the core that
|
||||||
|
libretro_variable.value = std::ptr::null();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!(
|
error!(
|
||||||
|
@ -144,22 +138,22 @@ pub(crate) unsafe extern "C" fn environment_callback(
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Fully implement, we'll need to implement above more fully.
|
|
||||||
// Ideas:
|
|
||||||
// - FrontendStateImpl can have a HashMap<CString, CString> which will then
|
|
||||||
// be where we can store stuff. Also the consumer application could in theory
|
|
||||||
// use that to save/restore (by injecting keys from another source)
|
|
||||||
ENVIRONMENT_SET_VARIABLES => {
|
ENVIRONMENT_SET_VARIABLES => {
|
||||||
let ptr = data as *const Variable;
|
let ptr = data as *const Variable;
|
||||||
|
let slice = util::terminated_array(ptr, |item| item.key.is_null());
|
||||||
|
|
||||||
let _slice = util::terminated_array(ptr, |item| item.key.is_null());
|
// populate variables hashmap
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
for var in slice {
|
for var in slice {
|
||||||
let key = std::ffi::CStr::from_ptr(var.key).to_str().unwrap();
|
let key = std::ffi::CStr::from_ptr(var.key).to_str().unwrap();
|
||||||
let value = std::ffi::CStr::from_ptr(var.value).to_str().unwrap();
|
let value = std::ffi::CStr::from_ptr(var.value).to_str().unwrap();
|
||||||
}*/
|
|
||||||
|
let parsed = libretro_core_variable::CoreVariable::parse(value);
|
||||||
|
|
||||||
|
(*FRONTEND).variables.insert(key.to_string(), parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load settings
|
||||||
|
(*FRONTEND).load_settings();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
69
crates/retro_frontend/src/libretro_core_variable.rs
Normal file
69
crates/retro_frontend/src/libretro_core_variable.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
//! Helpers for dealing with Libretro configuration values.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CoreVariable {
|
||||||
|
/// Description of variable
|
||||||
|
pub description: String,
|
||||||
|
|
||||||
|
/// possible choices
|
||||||
|
pub choices: Vec<String>,
|
||||||
|
|
||||||
|
/// Value. May not be pressent; if so, assume choices[0]
|
||||||
|
pub value: Option<String>,
|
||||||
|
|
||||||
|
/// C value. Passed/cached to libretro.
|
||||||
|
#[serde(skip)]
|
||||||
|
c_value: Option<CString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CoreVariable {
|
||||||
|
|
||||||
|
/// Parses this core variable.
|
||||||
|
pub fn parse(str: &str) -> Self {
|
||||||
|
let string = str.to_string();
|
||||||
|
|
||||||
|
match string.find(';') {
|
||||||
|
Some(index) => {
|
||||||
|
let name = &string[0..index];
|
||||||
|
|
||||||
|
if string.chars().nth(index + 1).unwrap() != ' ' {
|
||||||
|
panic!("Improperly formatted core variable");
|
||||||
|
}
|
||||||
|
|
||||||
|
let raw_choices = string[index + 2..].to_string();
|
||||||
|
let choices = raw_choices.split('|').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
description: name.to_string(),
|
||||||
|
choices: choices,
|
||||||
|
value: None,
|
||||||
|
c_value: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => panic!("??? Couldn't find"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets this variable's value
|
||||||
|
pub fn get_value(&mut self) -> &CString {
|
||||||
|
let rust_value = if self.value.is_some() {
|
||||||
|
self.value.as_ref().unwrap()
|
||||||
|
} else {
|
||||||
|
&self.choices[0]
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.c_value.is_none() {
|
||||||
|
self.c_value = Some(CString::new(rust_value.as_bytes()).expect("aaa"));
|
||||||
|
}
|
||||||
|
self.c_value.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets a new value
|
||||||
|
pub fn set_value(&mut self, value: &String) {
|
||||||
|
self.value = Some(value.clone());
|
||||||
|
self.c_value = None;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue