rewrite with structs

This commit is contained in:
beerpsi
2025-03-21 18:37:20 +07:00
parent 28fcd47be1
commit f14f3e2950
4 changed files with 190 additions and 57 deletions
+41 -12
View File
@@ -47,6 +47,7 @@ use anyhow::Result;
#[cfg(any(chuni, chusanapp))]
use rusb::{DeviceHandle, UsbContext};
use crate::TIMEOUT;
use super::ReadType;
pub const DEVICE_VID: u16 = 0x1973;
@@ -54,8 +55,22 @@ pub const DEVICE_PID: u16 = 0x2001;
pub const DEVICE_INTERFACE: u8 = 0;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x81;
pub const INPUT_MEMORY_SIZE: usize = 35;
pub const OUTPUT_MEMORY_SIZE: usize = 63;
pub const INPUT_MEMORY_SIZE: usize = std::mem::size_of::<LaveritaV2Input>();
pub const OUTPUT_MEMORY_SIZE: usize = std::mem::size_of::<LaveritaV2Output>();
#[repr(C)]
struct LaveritaV2Input {
pub report_id: u8,
pub ir: u8,
pub buttons: u8,
pub slider_pressure: [u8; 32],
}
#[repr(C)]
struct LaveritaV2Output {
pub report_id: u8,
pub slider_led: [u8; 62],
}
/// Poll JVS input.
///
@@ -69,17 +84,20 @@ pub const OUTPUT_MEMORY_SIZE: usize = 63;
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let mixed_beams = input[1] & 0x3F;
let input = input.as_ptr() as *const LaveritaV2Input;
let mixed_beams = unsafe { (*input).ir & 0x3F };
let beams = ((mixed_beams & 0xAA) >> 1) | ((mixed_beams & 0x55) << 1);
(input[2] & 3, beams)
(unsafe { (*input).buttons & 3 }, 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 {
(input[2] & 4) != 0
let input = input.as_ptr() as *const LaveritaV2Input;
unsafe { ((*input).buttons & 4) != 0 }
}
/// Reads slider pressure information from USB input.
@@ -103,10 +121,11 @@ pub fn is_coin_button_pressed(input: &[u8]) -> bool {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
let input = input.as_ptr() as *const LaveritaV2Input;
let mut pressure = [0u8; 32];
for i in 0..32 {
pressure[i] = input[3 + 31 - i];
pressure[i] = unsafe { (*input).slider_pressure[31 - i] };
}
pressure
@@ -117,7 +136,11 @@ pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn init_output_buffer(output: &mut [u8]) {
output[0] = 0;
let output = output.as_mut_ptr() as *mut LaveritaV2Output;
unsafe {
(*output).report_id = 0;
}
}
/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating
@@ -129,13 +152,19 @@ pub fn set_slider_leds<T: UsbContext>(
output: &mut [u8],
brg: &[u8],
) -> Result<()> {
use crate::TIMEOUT;
unsafe {
let output = output.as_mut_ptr() as *mut LaveritaV2Output;
for (buf_chunk, state_chunk) in output[1..].chunks_mut(2).take(31).zip(brg.chunks(3)) {
buf_chunk[0] = (state_chunk[0] << 3 & 0xE0) | (state_chunk[2] >> 3);
buf_chunk[1] = (state_chunk[1] & 0xF8) | (state_chunk[0] >> 5);
for (buf_chunk, state_chunk) in (*output).slider_led
.chunks_mut(2)
.take(31)
.zip(brg.chunks(3))
{
buf_chunk[0] = (state_chunk[0] << 3 & 0xE0) | (state_chunk[2] >> 3);
buf_chunk[1] = (state_chunk[1] & 0xF8) | (state_chunk[0] >> 5);
}
}
device.write_interrupt(0x02, output, TIMEOUT)?;
Ok(())
+53 -22
View File
@@ -31,6 +31,7 @@ use anyhow::Result;
#[cfg(any(chuni, chusanapp))]
use rusb::{DeviceHandle, UsbContext};
use crate::TIMEOUT;
use super::ReadType;
pub const DEVICE_VID: u16 = 0x0518;
@@ -38,8 +39,25 @@ pub const DEVICE_PID: u16 = 0x2022;
pub const DEVICE_INTERFACE: u8 = 3;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x83;
pub const INPUT_MEMORY_SIZE: usize = 34;
pub const OUTPUT_MEMORY_SIZE: usize = 122;
pub const INPUT_MEMORY_SIZE: usize = std::mem::size_of::<LaveritaV3Input>();
pub const OUTPUT_MEMORY_SIZE: usize = std::mem::size_of::<LaveritaV3Output>();
#[repr(C)]
struct LaveritaV3Input {
pub ir_buttons: u8,
pub magic: u8,
pub slider_pressure: [u8; 32],
}
#[repr(C)]
struct LaveritaV3Output {
pub report_id_zero: u8,
pub slider_led_zero: [u8; 60],
pub report_id_one: u8,
pub slider_led_one: [u8; 33],
pub padding: [u8; 27],
}
/// Poll JVS input.
///
@@ -54,10 +72,11 @@ pub const OUTPUT_MEMORY_SIZE: usize = 122;
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
// Taken from laverita_v2
let mixed_beams = input[0] & 0x3F;
let input = input.as_ptr() as *const LaveritaV3Input;
let mixed_beams = unsafe { (*input).ir_buttons & 0x3F };
let beams = ((mixed_beams & 0xAA) >> 1) | ((mixed_beams & 0x55) << 1);
(input[0] >> 6, beams)
(unsafe { (*input).ir_buttons >> 6 }, beams)
}
/// Checks the current state of the coin button (if there is one).
@@ -90,10 +109,13 @@ pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
let input = input.as_ptr() as *const LaveritaV3Input;
let mut pressure = [0u8; 32];
for (i, p) in input[2..34].iter().enumerate() {
pressure[if i % 2 == 0 { 30 - i } else { 32 - i }] = *p;
unsafe {
for (i, p) in (*input).slider_pressure.iter().enumerate() {
pressure[if i % 2 == 0 { 30 - i } else { 32 - i }] = *p;
}
}
pressure
@@ -104,9 +126,13 @@ pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn init_output_buffer(output: &mut [u8]) {
let output = output.as_mut_ptr() as *mut LaveritaV3Output;
// Packet indices
output[0] = 0;
output[61] = 1;
unsafe {
(*output).report_id_zero = 0;
(*output).report_id_one = 1;
}
}
/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating
@@ -118,21 +144,26 @@ pub fn set_slider_leds<T: UsbContext>(
output: &mut [u8],
brg: &[u8],
) -> Result<()> {
use crate::TIMEOUT;
let output_data = output.as_mut_ptr() as *mut LaveritaV3Output;
for (output_chunk, brg_chunk) in output[1..61].chunks_mut(3).zip(brg.chunks(3).take(20)) {
output_chunk[0] = brg_chunk[1]; // R
output_chunk[1] = brg_chunk[2]; // G
output_chunk[2] = brg_chunk[0]; // B
}
for (output_chunk, brg_chunk) in output[62..95]
.chunks_mut(3)
.zip(brg.chunks(3).skip(20).take(11))
{
output_chunk[0] = brg_chunk[1]; // R
output_chunk[1] = brg_chunk[2]; // G
output_chunk[2] = brg_chunk[0]; // B
unsafe {
for (output_chunk, brg_chunk) in (*output_data).slider_led_zero
.chunks_mut(3)
.zip(brg.chunks(3).take(20))
{
output_chunk[0] = brg_chunk[1]; // R
output_chunk[1] = brg_chunk[2]; // G
output_chunk[2] = brg_chunk[0]; // B
}
for (output_chunk, brg_chunk) in (*output_data).slider_led_one
.chunks_mut(3)
.zip(brg.chunks(3).skip(20).take(11))
{
output_chunk[0] = brg_chunk[1]; // R
output_chunk[1] = brg_chunk[2]; // G
output_chunk[2] = brg_chunk[0]; // B
}
}
device.write_interrupt(0x03, &output[0..61], TIMEOUT)?;
+44 -10
View File
@@ -35,8 +35,24 @@ pub const DEVICE_PID: u16 = 0x1231;
pub const DEVICE_INTERFACE: u8 = 0;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x84;
pub const INPUT_MEMORY_SIZE: usize = 36;
pub const OUTPUT_MEMORY_SIZE: usize = 114;
pub const INPUT_MEMORY_SIZE: usize = std::mem::size_of::<TasollerPlusInput>();
pub const OUTPUT_MEMORY_SIZE: usize = std::mem::size_of::<TasollerPlusOutput>();
#[repr(C)]
pub struct TasollerPlusInput {
pub magic: [u8; 3],
pub ir_buttons: u8,
pub slider_pressure: [u8; 32],
}
#[repr(C)]
pub struct TasollerPlusOutput {
pub magic: [u8; 2],
pub protocol_version: u8,
pub slider_led: [u8; 93],
pub left_air_tower_led: [u8; 9],
pub right_air_tower_led: [u8; 9],
}
/// Poll JVS input.
///
@@ -50,12 +66,13 @@ pub const OUTPUT_MEMORY_SIZE: usize = 114;
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let input = input.as_ptr() as *const TasollerPlusInput;
// 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 reversed = unsafe { (*input).ir_buttons.reverse_bits() };
let opbtn = reversed & 3;
let beams = reversed >> 2;
@@ -91,9 +108,10 @@ pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
let input = input.as_ptr() as *const TasollerPlusInput;
let mut pressure = [0u8; 32];
pressure.copy_from_slice(&input[4..36]);
pressure.copy_from_slice(unsafe { (*input).slider_pressure.as_slice() });
pressure
}
@@ -103,9 +121,13 @@ pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn init_output_buffer(output: &mut [u8]) {
output[0] = 0x44;
output[1] = 0x4C;
output[2] = 0x02;
let output = output.as_mut_ptr() as *mut TasollerPlusOutput;
unsafe {
(*output).magic[0] = 0x44;
(*output).magic[1] = 0x4C;
(*output).protocol_version = 0x02;
}
}
/// Update the RGB lighting on the slider. A slice `rgb` is provided, alternating
@@ -117,7 +139,13 @@ pub fn set_slider_leds<T: UsbContext>(
output: &mut [u8],
brg: &[u8],
) -> Result<()> {
for (output_chunk, brg_chunk) in output[3..96].chunks_mut(3).take(31).zip(brg.chunks(3)) {
let output_data = output.as_mut_ptr() as *mut TasollerPlusOutput;
for (output_chunk, brg_chunk) in unsafe { (*output_data).slider_led }
.chunks_mut(3)
.take(31)
.zip(brg.chunks(3))
{
output_chunk[0] = brg_chunk[1]; // R
output_chunk[1] = brg_chunk[2]; // G
output_chunk[2] = brg_chunk[0]; // B
@@ -150,10 +178,16 @@ pub fn set_led_colors<T: UsbContext>(
return Ok(());
}
let output_start = if board == 0 { 0x60 } else { 0x69 };
let output_data = output.as_mut_ptr() as *mut TasollerPlusOutput;
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]);
unsafe {
if board == 0 {
(*output_data).left_air_tower_led.copy_from_slice(&rgb[air_rgb_start..air_rgb_start + 9]);
} else {
(*output_data).right_air_tower_led.copy_from_slice(&rgb[air_rgb_start..air_rgb_start + 9]);
}
}
device.write_bulk(0x03, output, TIMEOUT)?;
+52 -13
View File
@@ -34,21 +34,39 @@ pub const DEVICE_PID: u16 = 0x2333;
pub const DEVICE_INTERFACE: u8 = 0;
pub const READ_TYPE: ReadType = ReadType::Interrupt;
pub const READ_ENDPOINT: u8 = 0x84;
pub const INPUT_MEMORY_SIZE: usize = 36;
pub const OUTPUT_MEMORY_SIZE: usize = 240;
pub const INPUT_MEMORY_SIZE: usize = std::mem::size_of::<TasollerV2Input>();
pub const OUTPUT_MEMORY_SIZE: usize = std::mem::size_of::<TasollerV2Output>();
#[repr(C)]
struct TasollerV2Input {
pub magic: [u8; 3],
pub ir_buttons: u8,
pub slider_pressure: [u8; 32],
}
#[repr(C)]
struct TasollerV2Output {
pub magic: [u8; 3],
pub slider_led: [u8; 93],
pub left_air_tower_led: [u8; 72],
pub right_air_tower_led: [u8; 72],
}
#[cfg(any(chuni, amdaemon))]
#[inline(always)]
pub fn jvs_poll(input: &[u8]) -> (u8, u8) {
let input = input.as_ptr() as *const TasollerV2Input;
let ir_buttons = unsafe { (*input).ir_buttons };
// Drop the IR bits from the input...
let opbtn = input[3] >> 6;
let opbtn = ir_buttons >> 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;
// Mask for last 6 bits to get the IR beams
let beams = input[3] & 63;
let beams = ir_buttons & 63;
(opbtn, beams)
}
@@ -63,9 +81,12 @@ pub fn is_coin_button_pressed(_input: &[u8]) -> bool {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
let input = input.as_ptr() as *const TasollerV2Input;
let slider_pressure = unsafe { (*input).slider_pressure.as_slice() };
let mut pressure = [0u8; 32];
for (i, p) in input.iter().skip(4).take(32).enumerate() {
for (i, p) in slider_pressure.iter().enumerate() {
pressure[if i % 2 == 0 { 30 - i } else { 32 - i }] = *p;
}
@@ -75,9 +96,13 @@ pub fn read_pressure_data(input: &[u8]) -> [u8; 32] {
#[cfg(any(chuni, chusanapp))]
#[inline(always)]
pub fn init_output_buffer(output: &mut [u8]) {
output[0] = 0x42;
output[1] = 0x4C;
output[2] = 0;
let output = output.as_mut_ptr() as *mut TasollerV2Output;
unsafe {
(*output).magic[0] = 0x42;
(*output).magic[1] = 0x4C;
(*output).magic[2] = 0;
}
}
#[cfg(any(chuni, chusanapp))]
@@ -87,7 +112,13 @@ pub fn set_slider_leds<T: UsbContext>(
output: &mut [u8],
brg: &[u8],
) -> Result<()> {
for (output_chunk, brg_chunk) in output[3..96].chunks_mut(3).take(31).zip(brg.chunks(3)) {
let output_data = output.as_mut_ptr() as *mut TasollerV2Output;
for (output_chunk, brg_chunk) in unsafe { (*output_data).slider_led }
.chunks_mut(3)
.take(31)
.zip(brg.chunks(3))
{
output_chunk[0] = brg_chunk[2]; // G
output_chunk[1] = brg_chunk[1]; // R
output_chunk[2] = brg_chunk[0]; // B
@@ -110,13 +141,21 @@ pub fn set_led_colors<T: UsbContext>(
return Ok(());
}
let air_rgb = if board == 0 { 0x96 } else { 0xB4 };
let output_start = if board == 0 { 96 } else { 168 };
let output_data = output.as_mut_ptr() as *mut TasollerV2Output;
let air_rgb_start = if board == 0 { 0x96 } else { 0xB4 };
let mut air_tower_buf = unsafe {
if board == 0 {
(*output_data).left_air_tower_led
} else {
(*output_data).right_air_tower_led
}
};
for (buf_chunk, state_chunk) in output[output_start..output_start + 72]
for (buf_chunk, state_chunk) in air_tower_buf
.chunks_mut(3)
.take(24)
.zip(rgb[air_rgb..air_rgb + 9].chunks(3).cycle().take(24))
.zip(rgb[air_rgb_start..air_rgb_start + 9].chunks(3).cycle().take(24))
{
buf_chunk[0] = state_chunk[1]; // R
buf_chunk[1] = state_chunk[0]; // B