diff --git a/Cargo.lock b/Cargo.lock index fb16db1..2426408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 10abc4a..39ff6bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/build-all.sh b/build-all.sh index 34244c5..0f76c36 100644 --- a/build-all.sh +++ b/build-all.sh @@ -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 \ No newline at end of file +done diff --git a/src/backends/laverita_v2.rs b/src/backends/laverita_v2.rs index 7594460..bfee4cb 100644 --- a/src/backends/laverita_v2.rs +++ b/src/backends/laverita_v2.rs @@ -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 diff --git a/src/backends/mod.rs b/src/backends/mod.rs index 26946d5..34010cf 100644 --- a/src/backends/mod.rs +++ b/src/backends/mod.rs @@ -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; diff --git a/src/backends/redboard.rs b/src/backends/tasoller_plus.rs similarity index 56% rename from src/backends/redboard.rs rename to src/backends/tasoller_plus.rs index e90e07c..89acf24 100644 --- a/src/backends/redboard.rs +++ b/src/backends/tasoller_plus.rs @@ -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( - _device: &DeviceHandle, - _output: &mut [u8], - _rgb: &[u8], + device: &DeviceHandle, + 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( #[cfg(any(chuni, chusanapp))] #[inline(always)] pub fn set_led_colors( - _device: &DeviceHandle, - _output: &mut [u8], - _board: u8, - _rgb: &[u8], + device: &DeviceHandle, + 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(()) } diff --git a/src/backends/tasoller_v1.rs b/src/backends/tasoller_v1.rs index 586a85c..3867ead 100644 --- a/src/backends/tasoller_v1.rs +++ b/src/backends/tasoller_v1.rs @@ -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) } diff --git a/src/backends/tasoller_v2.rs b/src/backends/tasoller_v2.rs index 8d6de43..fdb6592 100644 --- a/src/backends/tasoller_v2.rs +++ b/src/backends/tasoller_v2.rs @@ -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( 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( 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)?; diff --git a/src/lib.rs b/src/lib.rs index c1828c9..cc8304a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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 }