328 lines
10 KiB
Rust
328 lines
10 KiB
Rust
use core::convert::{TryFrom, TryInto};
|
|
use core::mem::size_of;
|
|
use std::ffi::CStr;
|
|
use std::fmt::{Debug, Formatter};
|
|
use std::os::raw::{c_char, c_uint};
|
|
use std::slice::from_raw_parts;
|
|
|
|
use super::constants::*;
|
|
use super::ffi::*;
|
|
|
|
fn delimited_cstr(cstr: *const c_char, delim: char) -> Vec<String> {
|
|
if cstr.is_null() {
|
|
Vec::new()
|
|
} else {
|
|
unsafe { CStr::from_ptr(cstr) }
|
|
.to_string_lossy()
|
|
.split(delim)
|
|
.map(str::to_string)
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
pub enum VideoFrame<'a> {
|
|
XRGB1555 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
|
|
RGB565 { data: &'a [u16], width: c_uint, height: c_uint, pitch_u16: usize },
|
|
XRGB8888 { data: &'a [u32], width: c_uint, height: c_uint, pitch_u32: usize },
|
|
Duplicate { width: c_uint, height: c_uint, pitch_u8: usize, },
|
|
HardwareRender { width: c_uint, height: c_uint, },
|
|
}
|
|
|
|
impl<'a> VideoFrame<'a> {
|
|
pub fn data_pitch_as_bytes(&self) -> Option<(&'a [u8], usize)> {
|
|
match self {
|
|
VideoFrame::RGB565 { data, pitch_u16, .. }
|
|
| VideoFrame::XRGB1555 { data, pitch_u16, .. } => {
|
|
let ptr = data.as_ptr() as *const u8;
|
|
let len = data.len() * size_of::<u16>();
|
|
Some((unsafe { from_raw_parts(ptr, len) }, pitch_u16 * size_of::<u16>()))
|
|
}
|
|
VideoFrame::XRGB8888 { data, pitch_u32, .. } => {
|
|
let ptr = data.as_ptr() as *const u8;
|
|
let len = data.len() * size_of::<u32>();
|
|
Some((unsafe { from_raw_parts(ptr, len) }, pitch_u32 * size_of::<u32>()))
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn dimensions(&self) -> (c_uint, c_uint) {
|
|
match self {
|
|
VideoFrame::XRGB1555 { width, height, .. }
|
|
| VideoFrame::RGB565 { width, height, .. }
|
|
| VideoFrame::XRGB8888 { width, height, .. }
|
|
| VideoFrame::Duplicate { width, height, .. }
|
|
| VideoFrame::HardwareRender { width, height, .. } => (*width, *height),
|
|
}
|
|
}
|
|
|
|
pub fn pixel_format(&self) -> Option<PixelFormat> {
|
|
match self {
|
|
VideoFrame::XRGB1555 { .. } => Some(PixelFormat::ARGB1555),
|
|
VideoFrame::RGB565 { .. } => Some(PixelFormat::RGB565),
|
|
VideoFrame::XRGB8888 { .. } => Some(PixelFormat::ARGB8888),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Debug for VideoFrame<'a> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
VideoFrame::XRGB1555 { data, width, height, pitch_u16 } => {
|
|
write!(
|
|
f,
|
|
"XRGB1555 {{ data: &[u16; {}], width: {}, height: {}, pitch_u16: {} }}",
|
|
data.len(), width, height, pitch_u16
|
|
)
|
|
}
|
|
VideoFrame::RGB565 { data, width, height, pitch_u16 } => {
|
|
write!(
|
|
f,
|
|
"RGB565 {{ data: &[u16; {}], width: {}, height: {}, pitch_u16: {} }}",
|
|
data.len(), width, height, pitch_u16
|
|
)
|
|
}
|
|
VideoFrame::XRGB8888 { data, width, height, pitch_u32 } => {
|
|
write!(
|
|
f,
|
|
"XRGB8888 {{ data: &[u32; {}], width: {}, height: {}, pitch_u32: {} }}",
|
|
data.len(), width, height, pitch_u32
|
|
)
|
|
}
|
|
VideoFrame::Duplicate { width, height, pitch_u8 } => {
|
|
write!(
|
|
f,
|
|
"Duplicate {{ width: {}, height: {}, pitch_u8: {} }}",
|
|
width, height, pitch_u8
|
|
)
|
|
}
|
|
VideoFrame::HardwareRender { width, height } => {
|
|
write!(f, "HardwareRender {{ width: {}, height: {} }}", width, height)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
|
pub enum InputDeviceId {
|
|
None(c_uint),
|
|
Joypad(JoypadButton),
|
|
Mouse(MouseButton),
|
|
Keyboard(c_uint),
|
|
LightGun(LightGunButton),
|
|
Analog(AnalogAxis),
|
|
Pointer(PointerStat),
|
|
}
|
|
|
|
impl<D> TryFrom<(D, c_uint)> for InputDeviceId
|
|
where D: TryInto<DeviceType>,
|
|
<D as std::convert::TryInto<DeviceType>>::Error: std::error::Error + Send + Sync + 'static,
|
|
{
|
|
type Error = Box<dyn std::error::Error + Send + Sync>;
|
|
|
|
fn try_from(pair: (D, c_uint)) -> Result<Self, Self::Error> {
|
|
let (device, id) = pair;
|
|
Ok(match device.try_into()? {
|
|
DeviceType::None => InputDeviceId::None(id),
|
|
DeviceType::Joypad => InputDeviceId::Joypad(JoypadButton::try_from(id)?),
|
|
DeviceType::Mouse => InputDeviceId::Mouse(MouseButton::try_from(id)?),
|
|
DeviceType::Keyboard => InputDeviceId::Keyboard(id),
|
|
DeviceType::LightGun => InputDeviceId::LightGun(LightGunButton::try_from(id)?),
|
|
DeviceType::Analog => InputDeviceId::Analog(AnalogAxis::try_from(id)?),
|
|
DeviceType::Pointer => InputDeviceId::Pointer(PointerStat::try_from(id)?),
|
|
})
|
|
}
|
|
}
|
|
|
|
pub struct SystemInfo2 {
|
|
pub library_name: String,
|
|
pub library_version: String,
|
|
pub valid_extensions: Vec<String>,
|
|
pub need_fullpath: bool,
|
|
pub block_extract: bool,
|
|
}
|
|
|
|
impl From<&SystemInfo> for SystemInfo2 {
|
|
fn from(info: &SystemInfo) -> Self {
|
|
SystemInfo2 {
|
|
library_name: unsafe { CStr::from_ptr(info.library_name) }.to_string_lossy().to_string(),
|
|
library_version: unsafe { CStr::from_ptr(info.library_version) }.to_string_lossy().to_string(),
|
|
valid_extensions: delimited_cstr(info.valid_extensions, '|'),
|
|
need_fullpath: info.need_fullpath,
|
|
block_extract: info.block_extract,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Variable2 {
|
|
pub key: String,
|
|
pub description: String,
|
|
pub options: Vec<String>,
|
|
}
|
|
|
|
impl From<&Variable> for Variable2 {
|
|
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(str::to_string)
|
|
.collect();
|
|
Variable2 {
|
|
key,
|
|
description,
|
|
options,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct MemoryDescriptor2 {
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct MemoryMap2 {
|
|
pub descriptors: Vec<MemoryDescriptor2>
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ControllerDescription2 {
|
|
pub name: String,
|
|
pub base_type: DeviceType,
|
|
pub subclass: Option<c_uint>,
|
|
}
|
|
|
|
impl ControllerDescription2 {
|
|
pub fn device_id(&self) -> c_uint {
|
|
match self.subclass {
|
|
None => c_uint::from(self.base_type),
|
|
Some(sc) => self.base_type.device_subclass(sc),
|
|
}
|
|
}
|
|
}
|
|
|
|
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(Clone, Debug)]
|
|
pub struct InputDescriptor2 {
|
|
pub port: c_uint,
|
|
pub device_id: InputDeviceId,
|
|
pub index: InputIndex,
|
|
pub description: String,
|
|
}
|
|
|
|
impl TryFrom<&InputDescriptor> for InputDescriptor2 {
|
|
type Error = ();
|
|
fn try_from(t: &InputDescriptor) -> Result<Self, Self::Error> {
|
|
if t.id >> DEVICE_TYPE_SHIFT != 0 {
|
|
eprintln!("Device subclass encountered in retro_input_descriptor, ignoring");
|
|
}
|
|
let description = unsafe { CStr::from_ptr(t.description) }
|
|
.to_str()
|
|
.map_err(|_| ())?
|
|
.to_string();
|
|
Ok(InputDescriptor2 {
|
|
port: t.port,
|
|
device_id: InputDeviceId::try_from((t.device & DEVICE_MASK, t.id)).map_err(|_| ())?,
|
|
index: InputIndex::try_from(t.index).map_err(|_| ())?,
|
|
description,
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, 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(Clone, 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: delimited_cstr(sri.valid_extensions, '|'),
|
|
need_fullpath: sri.need_fullpath,
|
|
block_extract: sri.block_extract,
|
|
required: sri.required,
|
|
memory: mem_slice.iter().map(Into::into).collect(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, 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,
|
|
}
|
|
}
|
|
}
|