Files
beerpsi_chuniio-rs/src/lib.rs
T

436 lines
12 KiB
Rust

//! Common code between all controllers.
//!
//! Mostly the boring stuff, e.g. thread and state management.
#![allow(clippy::missing_safety_doc)]
// Make it public so Rust shuts up about unused backends
pub mod backends;
mod configuration;
mod log;
use std::{
ffi::c_void,
fmt::Display,
rc::Rc,
sync::atomic::{AtomicBool, Ordering},
time::Duration,
};
use ::log::error;
use anyhow::{anyhow, Result};
use shared_memory::{Shmem, ShmemConf, ShmemError};
use winapi::{
shared::{
minwindef::{BOOL, DWORD, HINSTANCE, LPVOID, TRUE},
winerror::{E_FAIL, S_OK},
},
um::winnt::{DLL_PROCESS_ATTACH, HRESULT},
};
use crate::log::init_logger;
cfg_if::cfg_if! {
if #[cfg(feature = "tasoller_v1")] {
use crate::backends::tasoller_v1 as con_impl;
} else if #[cfg(feature = "tasoller_v2")] {
use crate::backends::tasoller_v2 as con_impl;
} else if #[cfg(feature = "laverita_v2")] {
use crate::backends::laverita_v2 as con_impl;
} else if #[cfg(feature = "redboard")] {
use crate::backends::redboard as con_impl;
} else {
use crate::backends::dummy as con_impl;
}
}
cfg_if::cfg_if! {
if #[cfg(any(chuni, chusanapp))] {
type SliderCallbackFn = unsafe extern "C" fn(data: *const u8);
use std::thread;
use ::log::info;
use rusb::{DeviceHandle, GlobalContext};
use parking_lot::RwLock;
use crate::backends::ReadType;
static DEVICE: RwLock<Option<DeviceHandle<GlobalContext>>> = RwLock::new(None);
static SLIDER_ACTIVE: AtomicBool = AtomicBool::new(false);
static SLIDER_OUTPUT: RwLock<[u8; con_impl::OUTPUT_MEMORY_SIZE]> = RwLock::new([0u8; con_impl::OUTPUT_MEMORY_SIZE]);
}
}
cfg_if::cfg_if! {
if #[cfg(any(chuni, amdaemon))] {
use std::{ffi::{c_int, CString}, sync::atomic::AtomicU16};
use lazy_static::lazy_static;
use winapi::um::{winbase::GetPrivateProfileIntA, winuser::GetAsyncKeyState};
use crate::configuration::Configuration;
static COIN_COUNT: AtomicU16 = AtomicU16::new(0);
static COIN_PRESSED: AtomicBool = AtomicBool::new(false);
lazy_static! {
static ref CONFIGURATION: Configuration = {
let io3 = CString::new("io3").unwrap();
let test = CString::new("test").unwrap();
let service = CString::new("service").unwrap();
let coin = CString::new("coin").unwrap();
let cfg_file = CString::new(".\\segatools.ini").unwrap();
unsafe {
Configuration {
test_key: GetPrivateProfileIntA(
io3.as_ptr(),
test.as_ptr(),
0x31,
cfg_file.as_ptr(),
),
service_key: GetPrivateProfileIntA(
io3.as_ptr(),
service.as_ptr(),
0x32,
cfg_file.as_ptr(),
),
coin_key: GetPrivateProfileIntA(
io3.as_ptr(),
coin.as_ptr(),
0x33,
cfg_file.as_ptr(),
),
}
}
};
}
}
}
pub static TIMEOUT: Duration = Duration::from_millis(20);
pub static DEVICE_POLLING_INTERVAL: Duration = Duration::from_millis(100);
static mut INPUT_SHMEM: Option<Rc<Shmem>> = None;
const INPUT_SHMEM_OS_ID: &str = "fcfe5b1100568d65af167d81acbed71d";
#[no_mangle]
extern "system" fn DllMain(_dll_module: HINSTANCE, call_reason: DWORD, _reserved: LPVOID) -> BOOL {
if call_reason != DLL_PROCESS_ATTACH {
return TRUE;
}
init_logger();
TRUE
}
#[no_mangle]
pub extern "C" fn chuni_io_get_api_version() -> u16 {
0x0102
}
#[no_mangle]
#[cfg(any(chuni, amdaemon))]
pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
#[cfg(chuni)]
{
// We don't care if we can't find the device at this time, it'll
// probably come eventually.
thread::spawn(device_init);
}
create_input_shared_memory()
}
#[no_mangle]
#[cfg(chusanapp)]
pub extern "C" fn chuni_io_jvs_init() -> HRESULT {
S_OK
}
#[no_mangle]
#[cfg(any(chuni, amdaemon))]
pub unsafe extern "C" fn chuni_io_jvs_poll(opbtn: *mut u8, beams: *mut u8) {
if opbtn.is_null() || beams.is_null() {
return;
}
let Some(input_shmem) = &INPUT_SHMEM else {
return;
};
(*opbtn, *beams) = con_impl::jvs_poll(input_shmem.as_slice());
if GetAsyncKeyState(CONFIGURATION.test_key as c_int) != 0 {
*opbtn |= 1;
}
if GetAsyncKeyState(CONFIGURATION.service_key as c_int) != 0 {
*opbtn |= 2;
}
}
#[no_mangle]
#[cfg(chusanapp)]
pub extern "C" fn chuni_io_jvs_poll(_opbtn: *mut u8, _beams: *mut u8) {}
#[no_mangle]
#[cfg(any(chuni, amdaemon))]
pub unsafe extern "C" fn chuni_io_jvs_read_coin_counter(total: *mut u16) {
let Some(input_shmem) = &INPUT_SHMEM else {
return;
};
if con_impl::is_coin_button_pressed(input_shmem.as_slice())
|| GetAsyncKeyState(CONFIGURATION.coin_key as c_int) != 0
{
if !COIN_PRESSED.load(Ordering::Relaxed) {
COIN_PRESSED.store(true, Ordering::Relaxed);
COIN_COUNT.fetch_add(1, Ordering::Relaxed);
}
} else {
COIN_PRESSED.store(false, Ordering::Relaxed);
}
*total = COIN_COUNT.load(Ordering::Relaxed);
}
#[no_mangle]
#[cfg(chusanapp)]
pub extern "C" fn chuni_io_jvs_read_coin_counter(_total: *mut u16) {}
#[no_mangle]
#[cfg(any(chuni, chusanapp))]
#[allow(unreachable_code)]
pub unsafe extern "C" fn chuni_io_slider_init() -> HRESULT {
#[cfg(not(feature = "chusan"))]
{
// Already initialized in chuni_io_jvs_init()
return S_OK;
}
// We don't care if we can't find the device at this time, it'll
// probably come eventually.
thread::spawn(device_init);
create_input_shared_memory()
}
#[no_mangle]
#[cfg(amdaemon)]
pub extern "C" fn chuni_io_slider_init() -> HRESULT {
S_OK
}
#[no_mangle]
#[cfg(any(chuni, chusanapp))]
pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
if callback.is_null() {
return;
}
if SLIDER_ACTIVE.load(Ordering::SeqCst) {
return;
}
SLIDER_ACTIVE.store(true, Ordering::SeqCst);
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
thread::spawn(move || {
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
return;
};
let usb_in = input_shmem.as_slice();
while SLIDER_ACTIVE.load(Ordering::SeqCst) {
let pressure = con_impl::read_pressure_data(usb_in);
callback(pressure.as_ptr());
thread::sleep(Duration::from_nanos(1_000_000));
}
});
}
#[no_mangle]
#[cfg(amdaemon)]
pub extern "C" fn chuni_io_slider_start(_callback: *const c_void) {}
#[no_mangle]
#[cfg(any(chuni, chusanapp))]
pub extern "C" fn chuni_io_slider_stop() {
SLIDER_ACTIVE.store(false, Ordering::SeqCst);
}
#[no_mangle]
#[cfg(amdaemon)]
pub extern "C" fn chuni_io_slider_stop() {}
#[no_mangle]
#[cfg(any(chuni, chusanapp))]
pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
if rgb.is_null() {
return;
}
let device_rg = DEVICE.read();
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
let rgb = std::slice::from_raw_parts(rgb, 93);
let mut output = SLIDER_OUTPUT.write();
if let Err(e) = con_impl::set_slider_leds(device, output.as_mut_slice(), rgb) {
error!("Could not write slider LED data: {e:#?}");
}
}
#[no_mangle]
#[cfg(amdaemon)]
pub extern "C" fn chuni_io_slider_set_leds(_rgb: *const u8) {}
#[no_mangle]
#[cfg(any(chuni, chusanapp))]
pub extern "C" fn chuni_io_led_init() -> HRESULT {
let mut output = SLIDER_OUTPUT.write();
con_impl::init_output_buffer(output.as_mut_slice());
S_OK
}
#[no_mangle]
#[cfg(amdaemon)]
pub extern "C" fn chuni_io_led_init() -> HRESULT {
S_OK
}
#[no_mangle]
#[cfg(any(chuni, chusanapp))]
pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
if rgb.is_null() {
return;
}
let device_rg = DEVICE.read();
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
let rgb = std::slice::from_raw_parts(rgb, 256);
let mut output = SLIDER_OUTPUT.write();
if let Err(e) = con_impl::set_led_colors(device, output.as_mut_slice(), board, rgb) {
error!("Could not write RGB LED data: {e:#?}");
}
}
#[no_mangle]
#[cfg(amdaemon)]
pub extern "C" fn chuni_io_led_set_colors(_rgb: *const u8) {}
#[cfg(any(chuni, chusanapp))]
fn device_init() -> Result<()> {
{
let mut global_device = DEVICE.write();
*global_device = None;
}
info!("Waiting for device...");
loop {
let Some(mut device) =
rusb::open_device_with_vid_pid(con_impl::DEVICE_VID, con_impl::DEVICE_PID)
else {
thread::sleep(DEVICE_POLLING_INTERVAL);
continue;
};
device.set_active_configuration(1)?;
device.claim_interface(0)?;
let mut global_device = DEVICE.write();
*global_device = Some(device);
thread::spawn(input_thread_proc);
return Ok(())
}
}
#[cfg(any(chuni, chusanapp))]
fn input_thread_proc() {
info!("Input thread started");
let mut shmem = match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false)
{
Ok(s) => s,
Err(e) => {
error!("Could not obtain shared memory for controller input: {e:#?}");
return;
}
};
let usb_in = unsafe { shmem.as_slice_mut() };
let device_rg = DEVICE.read();
let device = device_rg.as_ref().unwrap();
loop {
let result = match con_impl::READ_TYPE {
ReadType::Bulk => device.read_bulk(con_impl::READ_ENDPOINT, usb_in, TIMEOUT),
ReadType::Interrupt => device.read_interrupt(con_impl::READ_ENDPOINT, usb_in, TIMEOUT),
};
if let Err(e) = result {
match e {
rusb::Error::NoDevice | rusb::Error::Io => {
error!("Controller disconnected.");
usb_in.iter_mut().for_each(|m| *m = 0);
// Spawn a thread polling for a connection again
thread::spawn(device_init);
return;
}
_ => error!("Could not read data from controller: {e:#?}"),
}
}
}
}
fn create_input_shared_memory() -> HRESULT {
match create_shared_memory(INPUT_SHMEM_OS_ID, con_impl::INPUT_MEMORY_SIZE, false) {
Ok(mut s) => {
unsafe {
s.as_slice_mut().iter_mut().for_each(|m| *m = 0);
INPUT_SHMEM = Some(Rc::new(s))
};
S_OK
}
Err(e) => {
error!("Could not acquire shared memory: {e:#?}");
E_FAIL
}
}
}
fn create_shared_memory<S>(os_id: S, size: usize, is_owner: bool) -> Result<Shmem>
where
S: AsRef<str> + Display + Copy,
{
let shmem_conf = ShmemConf::new().size(size).os_id(os_id);
let shmem_result = match shmem_conf.clone().create() {
Ok(s) => Ok(s),
Err(ShmemError::MappingIdExists) => shmem_conf.open(),
Err(e) => {
return Err(anyhow!(
"Failed to create/open shared memory {os_id}: {e:#}"
));
}
};
shmem_result
.map(|mut m| {
m.set_owner(is_owner);
m
})
.map_err(|e| anyhow!("Failed to create/open shared memory {os_id}: {e:#}"))
}