mirror of
https://gitea.tendokyu.moe/beerpsi/chuniio-rs.git
synced 2026-10-14 18:19:13 +03:00
Add Tasoller+ support (untested)
This commit is contained in:
Generated
+2
-2
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
@@ -43,7 +43,7 @@ checksum = "77e53693616d3075149f4ead59bdeecd204ac6b8192d8969757601b74bddf00f"
|
||||
|
||||
[[package]]
|
||||
name = "chuniio-rs"
|
||||
version = "0.1.0"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cfg-if",
|
||||
|
||||
+5
-2
@@ -13,12 +13,15 @@ lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(chusanapp)', 'cfg(chuni)', 'cfg(amdaemon)'] }
|
||||
|
||||
[features]
|
||||
chusan = []
|
||||
tasoller_v1 = []
|
||||
tasoller_v2 = []
|
||||
laverita_v3 = []
|
||||
redboard = []
|
||||
laverita_v2 = []
|
||||
tasoller_plus = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.76"
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@
|
||||
mkdir -p dist/chusan
|
||||
mkdir -p dist/chuni
|
||||
|
||||
for backend in tasoller_v1 tasoller_v2 laverita_v3
|
||||
for backend in tasoller_v1 tasoller_v2 laverita_v2 tasoller_plus
|
||||
do
|
||||
cargo build --target i686-pc-windows-msvc --release --features chusan,$backend
|
||||
cargo build --target x86_64-pc-windows-msvc --release --features chusan,$backend
|
||||
@@ -13,4 +13,4 @@ do
|
||||
|
||||
cargo build --target i686-pc-windows-msvc --release --features $backend
|
||||
cp target/i686-pc-windows-msvc/release/chuniio_rs.dll dist/chuni/chuniio_${backend}.dll
|
||||
done
|
||||
done
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
//! # Yuancon Laverita v2
|
||||
//! (does anyone still use this?)
|
||||
//!
|
||||
//!
|
||||
//! Notes: I'm getting conflicting information on the protocol.
|
||||
//! CrazyRedMachine is claiming that there is [a leading byte][1], while 4yn's
|
||||
//! [slidershim][2] as well as their earlier [Yuancon HID test script][3] is
|
||||
//! claiming the opposite. This implementation is based on CrazyRedMachine,
|
||||
//! CrazyRedMachine is claiming that there is [a leading byte][1], while 4yn's
|
||||
//! [slidershim][2] as well as their earlier [Yuancon HID test script][3] is
|
||||
//! claiming the opposite. This implementation is based on CrazyRedMachine,
|
||||
//! since the chuniio open-sourced by ZhouSensor himself seems to agree.
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
//! ```c
|
||||
//! struct outputdata_gaosan
|
||||
//! {
|
||||
@@ -15,11 +15,11 @@
|
||||
//! unsigned char Touch[62];
|
||||
//! }OutputDataGaosan;
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! [1]: https://github.com/CrazyRedMachine/yuancon_laverita_v2-docs/blob/main/pcb_protocol/hid_reports.txt
|
||||
//! [2]: https://github.com/4yn/slidershim/blob/main/src-slider_io/src/device/hid.rs#L174-L207
|
||||
//! [3]: https://gist.github.com/4yn/53a6b6b681e40fd5658bf5b7f9fe9c15
|
||||
//!
|
||||
//!
|
||||
//! USB device: 1973:2001
|
||||
//!
|
||||
//! USB interface: 0
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
pub mod dummy;
|
||||
pub mod laverita_v2;
|
||||
pub mod redboard;
|
||||
pub mod tasoller_plus;
|
||||
pub mod tasoller_v1;
|
||||
pub mod tasoller_v2;
|
||||
|
||||
|
||||
@@ -1,46 +1,40 @@
|
||||
//! # RedBoard
|
||||
//! USB device: 0F0D:0092
|
||||
//! # Tasoller Plus
|
||||
//! USB device: 0E8F:1231
|
||||
//!
|
||||
//! USB interface: 0
|
||||
//!
|
||||
//! ## Protocol information
|
||||
//! - Content length: 9 bytes (72 bits)
|
||||
//! - Byte 1: Report ID
|
||||
//! - Byte 2:
|
||||
//! - 0x01: Service
|
||||
//! - 0x02: Start (?)
|
||||
//! - 0x10: Test
|
||||
//! - Byte 3:
|
||||
//! - 0x01: IR2
|
||||
//! - 0x02: IR3
|
||||
//! - 0x04: IR4
|
||||
//! - 0x08: IR1
|
||||
//! - 0x10: IR5
|
||||
//! - 0x20: IR6
|
||||
//! - Byte 4: HAT switch, whatever this is
|
||||
//! - Byte
|
||||
//!
|
||||
//! ### IN Interrupt (0x84)
|
||||
//! - Content length: 36 bytes
|
||||
//! - Bytes 0..3: Magic bytes [0x44, 0x42, 0x54] ("DBT")
|
||||
//! - Byte 3: the order of the bits from MSB to LSB are:
|
||||
//! FN1 FN2 IR1 IR2 IR3 IR4 IR5 IR6
|
||||
//! - Bytes 4..36: Touch sensor pressure (top right, top to bottom, right to left,
|
||||
//! like segatools basically)
|
||||
//!
|
||||
//! ### OUT Bulk (0x03)
|
||||
//! - Content length: 114 bytes
|
||||
//! - Byte 0..3: Magic bytes [0x44, 0x4C, 0x02] ("DL\02")
|
||||
//! - Byte 3..96: Slider LED (RGB order, right -> left)
|
||||
//! - Byte 96..105: Left tower LED (RGB)
|
||||
//! - Byte 105..114: Right tower LED (RGB)
|
||||
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
use anyhow::Result;
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
use rusb::{DeviceHandle, UsbContext};
|
||||
|
||||
use super::ReadType;
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
use crate::TIMEOUT;
|
||||
|
||||
pub const DEVICE_VID: u16 = 0x0F0D;
|
||||
pub const DEVICE_PID: u16 = 0x0092;
|
||||
pub const DEVICE_VID: u16 = 0x0E8F;
|
||||
pub const DEVICE_PID: u16 = 0x1231;
|
||||
pub const READ_TYPE: ReadType = ReadType::Interrupt;
|
||||
pub const READ_ENDPOINT: u8 = 0x01;
|
||||
pub const INPUT_MEMORY_SIZE: usize = 9;
|
||||
pub const OUTPUT_MEMORY_SIZE: usize = 98;
|
||||
|
||||
#[repr(C, packed)]
|
||||
struct ControllerReport {
|
||||
pub report_id: u8,
|
||||
pub buttons: u16,
|
||||
pub _hat: u8,
|
||||
pub axis: u32,
|
||||
pub vendor_spec: u8,
|
||||
}
|
||||
pub const READ_ENDPOINT: u8 = 0x84;
|
||||
pub const INPUT_MEMORY_SIZE: usize = 36;
|
||||
pub const OUTPUT_MEMORY_SIZE: usize = 114;
|
||||
|
||||
/// Poll JVS input.
|
||||
///
|
||||
@@ -54,14 +48,23 @@ struct ControllerReport {
|
||||
#[cfg(any(chuni, amdaemon))]
|
||||
#[inline(always)]
|
||||
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
||||
let buttons = (input[1] as u16) << 8 | (input[2] as u16);
|
||||
(0, 0)
|
||||
// The order of the bits from MSB to LSB is
|
||||
// FN1 FN2 IR1 IR2 IR3 IR4 IR5 IR6
|
||||
// so when reversed, it becomes
|
||||
// IR6 IR5 IR4 IR3 IR2 IR1 FN2 FN1
|
||||
// which is basically what segatools expect.
|
||||
let reversed = input[3].reverse_bits();
|
||||
let opbtn = reversed & 3;
|
||||
let beams = reversed >> 2;
|
||||
|
||||
(opbtn, beams)
|
||||
}
|
||||
|
||||
/// Checks the current state of the coin button (if there is one).
|
||||
#[cfg(any(chuni, amdaemon))]
|
||||
#[inline(always)]
|
||||
pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
|
||||
// Tasoller PLUS still doesn't have a coin button
|
||||
false
|
||||
}
|
||||
|
||||
@@ -85,25 +88,41 @@ pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
|
||||
/// ```
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
#[inline(always)]
|
||||
pub fn read_pressure_data(_input: &[u8]) -> [u8; 32] {
|
||||
[0u8; 32]
|
||||
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
|
||||
let mut pressure = [0u8; 32];
|
||||
|
||||
pressure.copy_from_slice(&input[4..36]);
|
||||
|
||||
pressure
|
||||
}
|
||||
|
||||
/// Do some one-time initialization to the USB out buffer
|
||||
/// (e.g. set magic bytes).
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
#[inline(always)]
|
||||
pub fn init_output_buffer(_output: &mut [u8]) {}
|
||||
pub fn init_output_buffer(output: &mut [u8]) {
|
||||
output[0] = 0x44;
|
||||
output[1] = 0x4C;
|
||||
output[2] = 0x02;
|
||||
}
|
||||
|
||||
/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating
|
||||
/// between 16 touch pad pixels and 15 divider pixels, going from right to left.
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
#[inline(always)]
|
||||
pub fn set_slider_leds<T: UsbContext>(
|
||||
_device: &DeviceHandle<T>,
|
||||
_output: &mut [u8],
|
||||
_rgb: &[u8],
|
||||
device: &DeviceHandle<T>,
|
||||
output: &mut [u8],
|
||||
rgb: &[u8],
|
||||
) -> Result<()> {
|
||||
for (output_chunk, rgb_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
|
||||
output_chunk[0] = rgb_chunk[1]; // R
|
||||
output_chunk[1] = rgb_chunk[2]; // G
|
||||
output_chunk[2] = rgb_chunk[0]; // B
|
||||
}
|
||||
|
||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -120,10 +139,21 @@ pub fn set_slider_leds<T: UsbContext>(
|
||||
#[cfg(any(chuni, chusanapp))]
|
||||
#[inline(always)]
|
||||
pub fn set_led_colors<T: UsbContext>(
|
||||
_device: &DeviceHandle<T>,
|
||||
_output: &mut [u8],
|
||||
_board: u8,
|
||||
_rgb: &[u8],
|
||||
device: &DeviceHandle<T>,
|
||||
output: &mut [u8],
|
||||
board: u8,
|
||||
rgb: &[u8],
|
||||
) -> Result<()> {
|
||||
if board > 1 {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
let output_start = if board == 0 { 0x60 } else { 0x69 };
|
||||
let air_rgb_start = if board == 0 { 0x96 } else { 0xB4 };
|
||||
|
||||
output[output_start..output_start + 9].copy_from_slice(&rgb[air_rgb_start..air_rgb_start + 9]);
|
||||
|
||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -58,8 +58,7 @@ pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
||||
let opbtn = input[3] >> 3 & 1 | input[3] >> 1 & 2;
|
||||
|
||||
// IR1-6 in order
|
||||
let beams = input[3] >> 4
|
||||
| (input[4] & 3) << 4;
|
||||
let beams = input[3] >> 4 | (input[4] & 3) << 4;
|
||||
|
||||
(opbtn, beams)
|
||||
}
|
||||
|
||||
+18
-14
@@ -8,17 +8,15 @@
|
||||
//! ### IN Interrupt (0x84)
|
||||
//! - Content length: 36 bytes
|
||||
//! - Bytes 0..3: Magic bytes [0x68, 0x66, 0x84]
|
||||
//! - Byte 3
|
||||
//! - Bit 0..6: ir 1-6 (1 = blocked)
|
||||
//! - Bit 6: FN1 (1 = pressed)
|
||||
//! - Bit 7: FN2 (1 = pressed)
|
||||
//! - Byte 3: the order of the bits from MSB to LSB are:
|
||||
//! FN1 FN2 IR6 IR5 IR4 IR3 IR2 IR1
|
||||
//! - Bytes 4..36: Touch sensor pressure (starts from top left,
|
||||
//! top to bottom then left to right)
|
||||
//!
|
||||
//! ### OUT Bulk (0x03)
|
||||
//! - Content length: 240 bytes
|
||||
//! - Bytes 0..3: magic bytes [0x42, 0x4C, 0x00]
|
||||
//! - Bytes 3..96: Slider LED (BGR order, right -> left)
|
||||
//! - Bytes 3..96: Slider LED (GRB order, right -> left)
|
||||
//! - Bytes 96..168: Left tower LEDs (GRB)
|
||||
//! - Bytes 168..240: Right tower LEDs (GRB)
|
||||
|
||||
@@ -41,9 +39,15 @@ pub const OUTPUT_MEMORY_SIZE: usize = 240;
|
||||
#[cfg(any(chuni, amdaemon))]
|
||||
#[inline(always)]
|
||||
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
|
||||
// Drop the IR bits from the input...
|
||||
let opbtn = input[3] >> 6;
|
||||
|
||||
// Swap bit position of FN1 and FN2 so that FN1 maps to Test (bit 0)
|
||||
// and FN2 maps to service (bit 1)
|
||||
let opbtn = (opbtn & 1) << 1 | (opbtn & 2) >> 1;
|
||||
let beams = input[3] & 63; // mask for last 6 bits
|
||||
|
||||
// Mask for last 6 bits to get the IR beams
|
||||
let beams = input[3] & 63;
|
||||
|
||||
(opbtn, beams)
|
||||
}
|
||||
@@ -82,10 +86,10 @@ pub fn set_slider_leds<T: UsbContext>(
|
||||
output: &mut [u8],
|
||||
rgb: &[u8],
|
||||
) -> Result<()> {
|
||||
for (buf_chunk, state_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
|
||||
buf_chunk[0] = state_chunk[2];
|
||||
buf_chunk[1] = state_chunk[1];
|
||||
buf_chunk[2] = state_chunk[0];
|
||||
for (output_chunk, rgb_chunk) in output[3..96].chunks_mut(3).take(31).zip(rgb.chunks(3)) {
|
||||
output_chunk[0] = rgb_chunk[2]; // G
|
||||
output_chunk[1] = rgb_chunk[1]; // R
|
||||
output_chunk[2] = rgb_chunk[0]; // B
|
||||
}
|
||||
|
||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||
@@ -107,15 +111,15 @@ pub fn set_led_colors<T: UsbContext>(
|
||||
|
||||
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
|
||||
let output_start = if board == 0 { 96 } else { 168 };
|
||||
|
||||
|
||||
for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
|
||||
.chunks_mut(3)
|
||||
.take(24)
|
||||
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
|
||||
{
|
||||
buf_chunk[0] = state_chunk[1];
|
||||
buf_chunk[1] = state_chunk[0];
|
||||
buf_chunk[2] = state_chunk[2];
|
||||
buf_chunk[0] = state_chunk[1]; // R
|
||||
buf_chunk[1] = state_chunk[0]; // B
|
||||
buf_chunk[2] = state_chunk[2]; // G
|
||||
}
|
||||
|
||||
device.write_bulk(0x03, output, TIMEOUT)?;
|
||||
|
||||
+28
-18
@@ -36,8 +36,8 @@ cfg_if::cfg_if! {
|
||||
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 if #[cfg(feature = "tasoller_plus")] {
|
||||
use crate::backends::tasoller_plus as con_impl;
|
||||
} else {
|
||||
use crate::backends::dummy as con_impl;
|
||||
}
|
||||
@@ -155,12 +155,12 @@ pub unsafe extern "C" fn chuni_io_jvs_poll(opbtn: *mut u8, beams: *mut u8) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(input_shmem) = &INPUT_SHMEM else {
|
||||
let input_shmem_ptr = &raw const INPUT_SHMEM;
|
||||
let Some(ref input_shmem) = *input_shmem_ptr else {
|
||||
return;
|
||||
};
|
||||
|
||||
(*opbtn, *beams) = con_impl::jvs_poll(input_shmem.as_slice());
|
||||
|
||||
if GetAsyncKeyState(CONFIGURATION.test_key as c_int) != 0 {
|
||||
*opbtn |= 1;
|
||||
}
|
||||
@@ -177,7 +177,8 @@ 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 {
|
||||
let input_shmem_ptr = &raw const INPUT_SHMEM;
|
||||
let Some(ref input_shmem) = *input_shmem_ptr else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -237,7 +238,8 @@ pub unsafe extern "C" fn chuni_io_slider_start(callback: *const c_void) {
|
||||
let callback = std::mem::transmute::<_, SliderCallbackFn>(callback);
|
||||
|
||||
thread::spawn(move || {
|
||||
let Some(input_shmem) = (unsafe { &INPUT_SHMEM }) else {
|
||||
let input_shmem_ptr = &raw const INPUT_SHMEM;
|
||||
let Some(ref input_shmem) = *input_shmem_ptr else {
|
||||
return;
|
||||
};
|
||||
let usb_in = input_shmem.as_slice();
|
||||
@@ -274,7 +276,9 @@ pub unsafe extern "C" fn chuni_io_slider_set_leds(rgb: *const u8) {
|
||||
}
|
||||
|
||||
let device_rg = DEVICE.read();
|
||||
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
|
||||
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();
|
||||
@@ -311,8 +315,14 @@ pub unsafe extern "C" fn chuni_io_led_set_colors(board: u8, rgb: *const u8) {
|
||||
return;
|
||||
}
|
||||
|
||||
if board > 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let device_rg = DEVICE.read();
|
||||
let Some(device) = device_rg.as_ref().map(|z| z) else { return };
|
||||
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();
|
||||
|
||||
@@ -350,7 +360,7 @@ fn device_init() -> Result<()> {
|
||||
|
||||
thread::spawn(input_thread_proc);
|
||||
|
||||
return Ok(())
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,14 +368,14 @@ fn device_init() -> Result<()> {
|
||||
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 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();
|
||||
@@ -400,7 +410,7 @@ fn create_input_shared_memory() -> HRESULT {
|
||||
unsafe {
|
||||
s.as_slice_mut().iter_mut().for_each(|m| *m = 0);
|
||||
|
||||
INPUT_SHMEM = Some(Rc::new(s))
|
||||
INPUT_SHMEM = Some(Rc::new(s));
|
||||
};
|
||||
S_OK
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user