implement ENVIRONMENT_SET_SUBSYSTEM_INFO

This commit is contained in:
lif 2019-11-19 00:31:14 -08:00
parent 7b50e6b975
commit 49aa491904
2 changed files with 248 additions and 71 deletions

View File

@ -5,7 +5,7 @@ extern crate sdl2;
use rustro::retro;
use rustro::retro::ffi::{GameGeometry, SystemInfo, SystemAvInfo};
use rustro::retro::constants::{Input, DeviceIndex, JoypadButton, AnalogAxis, DeviceType};
use rustro::retro::wrapper::{LibretroWrapper, VariableDescriptor, ControllerTypeDescriptor};
use rustro::retro::wrapper::{LibretroWrapper, VariableDescriptor, ControllerDescription2, SubsystemInfo2};
use std::ffi::CStr;
use std::io::Read;
@ -275,6 +275,10 @@ impl retro::wrapper::Handler for MyEmulator {
};
true
}
fn set_subsystem_info(&mut self, subsystem_info: Vec<SubsystemInfo2>) -> bool {
println!("subsystem info: {:?}", subsystem_info);
true
}
fn get_variable(&mut self, key: &str) -> Option<String> {
match key {
@ -311,7 +315,7 @@ impl retro::wrapper::Handler for MyEmulator {
true
}
fn set_controller_info(&mut self, controller_info: Vec<ControllerTypeDescriptor>) -> bool {
fn set_controller_info(&mut self, controller_info: Vec<ControllerDescription2>) -> bool {
for ci in controller_info {
// so we can have analog support in beetle/mednafen saturn
if ci.name.as_str() == "3D Control Pad" {

View File

@ -3,9 +3,9 @@ use core::ffi::c_void;
use core::slice::from_raw_parts;
use std::convert::TryFrom;
use std::ffi::{CString, CStr};
use std::ffi::{CStr, CString};
use std::ops::Deref;
use std::os::raw::{c_uint, c_char};
use std::os::raw::{c_char, c_uint};
use std::path::{Path, PathBuf};
use std::pin::Pin;
use std::time::Duration;
@ -16,19 +16,18 @@ use super::constants::*;
use super::ffi::*;
use super::loading::*;
static mut CB_SINGLETON: StaticCallbacks = StaticCallbacks {
handler: None,
};
static mut CB_SINGLETON: StaticCallbacks = StaticCallbacks { handler: None };
// stable Rust doesn't have varargs, so we can't represent a callback with the signature of
// void (*retro_log_printf_t)(enum retro_log_level level, const char* fmt, ...)
// without a little help from an Actual-C wrapper.
pub type WrappedLogPrintFn = extern "C" fn(level: LogLevel, fmt: *const c_char);
extern "C" {
extern "C" {
fn c_ext_handle_get_log_interface(cb: *mut LogCallback) -> bool;
fn c_ext_set_log_print_cb(cb: WrappedLogPrintFn);
}
#[rustfmt::skip]
#[allow(unused)]
pub trait Handler: Unpin + 'static {
fn libretro_core(&mut self) -> &mut LibretroWrapper;
@ -67,8 +66,8 @@ pub trait Handler: Unpin + 'static {
fn get_save_directory(&mut self) -> Option<PathBuf> { None }
fn set_system_av_info(&mut self, system_av_info: SystemAvInfo) -> bool { false }
fn set_proc_address_callback(&mut self, cb: GetProcAddressInterface) -> bool { false }
fn set_subsystem_info(&mut self, subsystem_info: SubsystemInfo) -> bool { false }
fn set_controller_info(&mut self, controller_info: Vec<ControllerTypeDescriptor>) -> bool { false }
fn set_subsystem_info(&mut self, subsystem_info: Vec<SubsystemInfo2>) -> bool { false }
fn set_controller_info(&mut self, controller_info: Vec<ControllerDescription2>) -> bool { false }
fn set_memory_maps(&mut self, memory_map: MemoryMap) -> bool { false }
fn set_geometry(&mut self, game_geometry: GameGeometry) -> bool { false }
fn get_username(&mut self) -> Option<String> { None }
@ -87,14 +86,38 @@ pub struct VariableDescriptor {
pub options: Vec<String>,
}
impl From<&Variable> for VariableDescriptor {
fn from(var: &Variable) -> Self {
let key = unsafe { CStr::from_ptr(var.key) }
.to_string_lossy()
.to_string();
let value = unsafe { CStr::from_ptr(var.value) }
.to_string_lossy()
.to_string();
let split: Vec<&str> = value.splitn(2, "; ").collect();
let description = split.get(0).unwrap_or(&"").to_string();
let options = split
.get(1)
.unwrap_or(&"")
.split('|')
.map(String::from)
.collect();
VariableDescriptor {
key,
description,
options,
}
}
}
#[derive(Debug)]
pub struct ControllerTypeDescriptor {
pub struct ControllerDescription2 {
pub name: String,
pub base_type: DeviceType,
pub subclass: Option<c_uint>,
}
impl ControllerTypeDescriptor {
impl ControllerDescription2 {
pub fn device_id(&self) -> c_uint {
match self.subclass {
None => c_uint::from(self.base_type),
@ -103,6 +126,97 @@ impl ControllerTypeDescriptor {
}
}
impl TryFrom<&ControllerDescription> for ControllerDescription2 {
type Error = ();
fn try_from(t: &ControllerDescription) -> Result<Self, Self::Error> {
let base_type = DeviceType::try_from(t.id & DEVICE_MASK).map_err(|_| ())?;
let subclass = match t.id >> DEVICE_TYPE_SHIFT {
0 => None,
n => Some(n - 1),
};
let name = unsafe { CStr::from_ptr(t.desc) }
.to_str()
.map_err(|_| ())?
.to_string();
Ok(ControllerDescription2 {
base_type,
subclass,
name,
})
}
}
#[derive(Debug)]
pub struct SubsystemInfo2 {
pub description: String,
pub identifier: String,
pub roms: Vec<SubsystemRomInfo2>,
pub id: c_uint,
}
impl From<&SubsystemInfo> for SubsystemInfo2 {
fn from(si: &SubsystemInfo) -> Self {
let rom_slice = unsafe { from_raw_parts(si.roms, si.num_roms as usize) };
SubsystemInfo2 {
description: unsafe { CStr::from_ptr(si.desc) }
.to_string_lossy()
.to_string(),
identifier: unsafe { CStr::from_ptr(si.ident) }
.to_string_lossy()
.to_string(),
roms: rom_slice.iter().map(Into::into).collect(),
id: si.id,
}
}
}
#[derive(Debug)]
pub struct SubsystemRomInfo2 {
pub description: String,
pub valid_extensions: Vec<String>,
pub need_fullpath: bool,
pub block_extract: bool,
pub required: bool,
pub memory: Vec<SubsystemMemoryInfo2>,
}
impl From<&SubsystemRomInfo> for SubsystemRomInfo2 {
fn from(sri: &SubsystemRomInfo) -> Self {
let mem_slice = unsafe { from_raw_parts(sri.memory, sri.num_memory as usize) };
SubsystemRomInfo2 {
description: unsafe { CStr::from_ptr(sri.desc) }
.to_string_lossy()
.to_string(),
valid_extensions: unsafe { CStr::from_ptr(sri.valid_extensions) }
.to_string_lossy()
.split('|')
.map(str::to_string)
.collect(),
need_fullpath: sri.need_fullpath,
block_extract: sri.block_extract,
required: sri.required,
memory: mem_slice.iter().map(Into::into).collect(),
}
}
}
#[derive(Debug)]
pub struct SubsystemMemoryInfo2 {
pub extension: String,
pub kind: c_uint,
}
impl From<&SubsystemMemoryInfo> for SubsystemMemoryInfo2 {
fn from(smi: &SubsystemMemoryInfo) -> Self {
SubsystemMemoryInfo2 {
extension: unsafe { CStr::from_ptr(smi.extension) }
.to_string_lossy()
.to_string(),
kind: smi.kind,
}
}
}
#[derive(Default)]
struct StaticCallbacks {
handler: Option<Pin<&'static mut dyn Handler>>,
@ -117,7 +231,8 @@ impl StaticCallbacks {
Some(true)
}
fn string_into_void(data: *mut c_void, source: impl AsRef<str>) -> Option<bool> {
*unsafe { (data as *mut *const c_char).as_mut()? } = CString::new(source.as_ref()).ok()?.into_raw();
*unsafe { (data as *mut *const c_char).as_mut()? } =
CString::new(source.as_ref()).ok()?.into_raw();
Some(true)
}
fn path_into_void(data: *mut c_void, source: impl AsRef<Path>) -> Option<bool> {
@ -127,7 +242,9 @@ impl StaticCallbacks {
unsafe { (data as *mut T).as_mut() }
}
fn enum_from_void<T>(data: *mut c_void) -> Option<T>
where T: TryFromPrimitive, <T as TryFromPrimitive>::Primitive: 'static
where
T: TryFromPrimitive,
<T as TryFromPrimitive>::Primitive: 'static,
{
let number = Self::from_void(data).cloned()?;
T::try_from_primitive(number).ok()
@ -148,7 +265,11 @@ impl StaticCallbacks {
if cfg!(debug) && parsed_cmd.is_none() {
eprintln!(
"Unknown{} env cmd: {}",
if cmd >= ENVIRONMENT_EXPERIMENTAL { ", experimental" } else { "" },
if cmd >= ENVIRONMENT_EXPERIMENTAL {
", experimental"
} else {
""
},
cmd % ENVIRONMENT_EXPERIMENTAL
);
}
@ -159,14 +280,21 @@ impl StaticCallbacks {
EnvCmd::SetMessage => handler.set_message(Self::from_void::<Message>(data)?.clone()),
EnvCmd::Shutdown => handler.shutdown(),
EnvCmd::SetPerformanceLevel => handler.set_performance_level(*Self::from_void(data)?),
EnvCmd::GetSystemDirectory => Self::path_into_void(data, handler.get_system_directory()?)?,
EnvCmd::SetPixelFormat => handler.set_pixel_format(PixelFormat::from_uint(*Self::from_void(data)?)?),
EnvCmd::GetSystemDirectory => {
Self::path_into_void(data, handler.get_system_directory()?)?
}
EnvCmd::SetPixelFormat => {
handler.set_pixel_format(PixelFormat::from_uint(*Self::from_void(data)?)?)
}
// TODO EnvCmd::SetInputDescriptors => {},
EnvCmd::SetKeyboardCallback => {
let kc: &mut KeyboardCallback = Self::from_void(data)?;
handler.libretro_core().keyboard_event_cb.replace(kc.callback);
handler
.libretro_core()
.keyboard_event_cb
.replace(kc.callback);
true
},
}
EnvCmd::SetDiskControlInterface => {
let dcc: &mut DiskControlCallback = Self::from_void(data)?;
let core = handler.libretro_core();
@ -175,18 +303,25 @@ impl StaticCallbacks {
core.disk_set_image_index_cb.replace(dcc.set_image_index);
core.disk_get_image_index_cb.replace(dcc.get_image_index);
core.disk_get_num_images_cb.replace(dcc.get_num_images);
core.disk_replace_image_index_cb.replace(dcc.replace_image_index);
core.disk_replace_image_index_cb
.replace(dcc.replace_image_index);
core.disk_add_image_index_cb.replace(dcc.add_image_index);
true
},
}
EnvCmd::SetHwRender => {
let hwr = Self::from_void::<HwRenderCallback>(data)?;
handler.libretro_core().hw_context_reset_cb.replace(hwr.context_reset);
handler.libretro_core().hw_context_destroy_cb.replace(hwr.context_destroy);
handler
.libretro_core()
.hw_context_reset_cb
.replace(hwr.context_reset);
handler
.libretro_core()
.hw_context_destroy_cb
.replace(hwr.context_destroy);
hwr.get_current_framebuffer = Self::hw_get_current_framebuffer_fn;
hwr.get_proc_address = Self::hw_get_proc_address_fn;
false // TODO: finish
},
}
EnvCmd::GetVariable => {
let mut var = Self::from_void::<Variable>(data)?;
let key = unsafe { CStr::from_ptr(var.key) }.to_str().ok()?;
@ -194,44 +329,50 @@ impl StaticCallbacks {
// leaks memory.
var.value = CString::new(value).ok()?.into_raw();
true
},
}
EnvCmd::SetVariables => {
let mut var = data as *const Variable;
let mut descriptors = Vec::new();
while !unsafe { var.as_ref() }?.key.is_null() {
let key = unsafe { CStr::from_ptr({ var.as_ref() }?.key) }.to_str().ok()?.to_string();
let value = unsafe { CStr::from_ptr({ var.as_ref() }?.value) }.to_str().ok()?;
let split: Vec<&str> = value.splitn(2, "; ").collect();
let description = split.get(0)?.to_string();
let options = split.get(1)?.split('|').map(String::from).collect();
descriptors.push(VariableDescriptor { key, description, options });
descriptors.push(unsafe { var.as_ref() }?.into());
var = var.wrapping_add(1);
}
handler.set_variables(descriptors)
},
EnvCmd::GetVariableUpdate => Self::clone_into_void(data, &handler.get_variable_update()?)?,
}
EnvCmd::GetVariableUpdate => {
Self::clone_into_void(data, &handler.get_variable_update()?)?
}
EnvCmd::SetSupportNoGame => handler.set_support_no_game(*Self::from_void(data)?),
EnvCmd::GetLibretroPath => Self::path_into_void(data, handler.get_libretro_path()?)?,
EnvCmd::SetFrameTimeCallback => {
let ftc: &mut FrameTimeCallback = Self::from_void(data)?;
handler.libretro_core().frame_time_cb.replace(ftc.callback);
true
},
}
EnvCmd::SetAudioCallback => {
let ac: &mut AudioCallback = Self::from_void(data)?;
handler.libretro_core().audio_ready_cb.replace(ac.callback);
handler.libretro_core().audio_set_state_cb.replace(ac.set_state);
handler
.libretro_core()
.audio_set_state_cb
.replace(ac.set_state);
true
},
}
EnvCmd::GetRumbleInterface => {
let ri = RumbleInterface { set_rumble_state: StaticCallbacks::set_rumble_state_cb };
let ri = RumbleInterface {
set_rumble_state: StaticCallbacks::set_rumble_state_cb,
};
Self::clone_into_void(data, &ri)?
},
}
// TODO: wrap this in something nicer than a raw u64 bit field
EnvCmd::GetInputDeviceCapabilities => Self::clone_into_void(data, &handler.get_input_device_capabilities()?)?,
EnvCmd::GetInputDeviceCapabilities => {
Self::clone_into_void(data, &handler.get_input_device_capabilities()?)?
}
// TODO EnvCmd::GetSensorInterface => {},
// TODO EnvCmd::GetCameraInterface => {},
EnvCmd::GetLogInterface => unsafe { c_ext_handle_get_log_interface(data as *mut LogCallback) },
EnvCmd::GetLogInterface => unsafe {
c_ext_handle_get_log_interface(data as *mut LogCallback)
},
EnvCmd::GetPerfInterface => {
let pc = PerfCallback {
get_time_usec: Self::perf_get_time_usec_cb,
@ -243,36 +384,47 @@ impl StaticCallbacks {
perf_log: Self::perf_log_cb,
};
Self::clone_into_void(data, &pc)?
},
}
// TODO EnvCmd::GetLocationInterface => {},
EnvCmd::GetCoreAssetsDirectory => Self::path_into_void(data, handler.get_core_assets_directory()?)?,
EnvCmd::GetCoreAssetsDirectory => {
Self::path_into_void(data, handler.get_core_assets_directory()?)?
}
EnvCmd::GetSaveDirectory => Self::path_into_void(data, handler.get_save_directory()?)?,
EnvCmd::SetSystemAvInfo => handler.set_system_av_info(Self::from_void::<SystemAvInfo>(data)?.clone()),
EnvCmd::SetSystemAvInfo => {
handler.set_system_av_info(Self::from_void::<SystemAvInfo>(data)?.clone())
}
EnvCmd::SetProcAddressCallback => {
let gpa = Self::from_void::<GetProcAddressInterface>(data)?;
handler.libretro_core().get_proc_address_cb.replace(gpa.get_proc_address);
handler
.libretro_core()
.get_proc_address_cb
.replace(gpa.get_proc_address);
true
},
// TODO EnvCmd::SetSubsystemInfo => {},
}
EnvCmd::SetSubsystemInfo => {
let mut info = data as *const SubsystemInfo;
let mut descriptors = Vec::new();
while !unsafe { info.as_ref() }?.desc.is_null() {
descriptors.push(unsafe { info.as_ref() }?.into());
info = info.wrapping_add(1);
}
handler.set_subsystem_info(descriptors)
}
EnvCmd::SetControllerInfo => {
let info = unsafe { (data as *const ControllerInfo).as_ref() }?;
let mut controller_info = Vec::new();
// FIXME: beetle/mednafen saturn crashes without this -1... add conditional on name?
let len = info.num_types as usize - 1;
let types = unsafe { from_raw_parts(info.types, len) };
for t in types {
let base_type = DeviceType::try_from(t.id & DEVICE_MASK).ok()?;
let subclass = match t.id >> DEVICE_TYPE_SHIFT {
0 => None,
n => Some(n - 1),
};
let name = unsafe { CStr::from_ptr(t.desc) }.to_str().ok()?.to_string();
controller_info.push(ControllerTypeDescriptor { base_type, subclass, name });
}
let controller_info = unsafe { from_raw_parts(info.types, len) }
.iter()
.map(TryInto::try_into)
.filter_map(|x| x.ok())
.collect();
handler.set_controller_info(controller_info)
},
}
// TODO EnvCmd::SetMemoryMaps => {},
EnvCmd::SetGeometry => handler.set_geometry(Self::from_void::<GameGeometry>(data)?.clone()),
EnvCmd::SetGeometry => {
handler.set_geometry(Self::from_void::<GameGeometry>(data)?.clone())
}
EnvCmd::GetUsername => Self::string_into_void(data, handler.get_username()?)?,
EnvCmd::GetLanguage => Self::clone_into_void(data, &handler.get_language()?)?,
// EnvCmd::SetSerializationQuirks => handler.set_serialization_quirks(Self::from_void(data)?),
@ -281,8 +433,9 @@ impl StaticCallbacks {
eprintln!("Known but unsupported env cmd: {:?}", x);
}
false
},
}.into()
}
}
.into()
}
extern "C" fn environment_cb(cmd: u32, data: *mut c_void) -> bool {
Self::environment_cb_inner(cmd, data).unwrap_or(false)
@ -317,7 +470,7 @@ impl StaticCallbacks {
let len = frames * 2; // stereo
let result = cb.audio_sample_batch(from_raw_parts(data, len));
result / 2
},
}
},
None => 0,
}
@ -350,9 +503,15 @@ impl StaticCallbacks {
}
}
// TODO: trait methods
extern "C" fn perf_get_time_usec_cb() -> Time { 0 }
extern "C" fn perf_get_counter_cb() -> PerfTick { 0 }
extern "C" fn perf_get_cpu_features_cb() -> u64 { 0 }
extern "C" fn perf_get_time_usec_cb() -> Time {
0
}
extern "C" fn perf_get_counter_cb() -> PerfTick {
0
}
extern "C" fn perf_get_cpu_features_cb() -> u64 {
0
}
extern "C" fn perf_log_cb() {}
extern "C" fn perf_register_cb(_counter: *mut PerfCounter) {}
extern "C" fn perf_start_cb(_counter: *mut PerfCounter) {}
@ -363,7 +522,9 @@ impl StaticCallbacks {
Self::hw_dummy_fn // FIXME: obvious hack
}
// note: libretro.h claims this is obsolete
extern "C" fn hw_get_current_framebuffer_fn() -> usize { 0 }
extern "C" fn hw_get_current_framebuffer_fn() -> usize {
0
}
}
pub struct LibretroWrapper {
@ -408,11 +569,19 @@ impl From<LibretroApi> for LibretroWrapper {
impl LibretroWrapper {
// TODO: enum for the RETROK_* constants instead of c_uint for keycode...
pub fn keyboard_event(&self, down: bool, keycode: c_uint, character: u32, key_modifiers: u16) -> Option<()> {
self.keyboard_event_cb.map(|f| unsafe { f(down, keycode, character, key_modifiers) })
pub fn keyboard_event(
&self,
down: bool,
keycode: c_uint,
character: u32,
key_modifiers: u16,
) -> Option<()> {
self.keyboard_event_cb
.map(|f| unsafe { f(down, keycode, character, key_modifiers) })
}
pub fn frame_time(&self, time: Duration) -> Option<()> {
self.frame_time_cb.map(|f| unsafe { f(time.as_micros() as Usec) })
self.frame_time_cb
.map(|f| unsafe { f(time.as_micros() as Usec) })
}
pub fn audio_ready(&self) -> Option<()> {
self.audio_ready_cb.map(|f| unsafe { f() })
@ -473,13 +642,17 @@ impl Deref for LibretroWrapper {
pub fn set_handler(handler: Pin<&'_ mut (dyn Handler + '_)>) {
unsafe {
let ptr = handler.get_unchecked_mut() as *mut dyn Handler;
CB_SINGLETON.handler.replace(Pin::new_unchecked(ptr.as_mut().unwrap()));
CB_SINGLETON
.handler
.replace(Pin::new_unchecked(ptr.as_mut().unwrap()));
c_ext_set_log_print_cb(StaticCallbacks::log_print_cb);
}
// some cores start calling the environment cb as soon as we call retro_set_environment,
// not waiting for a call to retro_init - so we'd better have CB_SINGLETON set first
let api = unsafe { CB_SINGLETON.handler.as_mut() }.unwrap().libretro_core();
let api = unsafe { CB_SINGLETON.handler.as_mut() }
.unwrap()
.libretro_core();
api.set_environment(StaticCallbacks::environment_cb);
api.set_video_refresh(StaticCallbacks::video_refresh_cb);
api.set_audio_sample(StaticCallbacks::audio_sample_cb);