Compare commits
2 commits
Author | SHA1 | Date | |
---|---|---|---|
9838c4ee64 | |||
6475e844a9 |
11 changed files with 763 additions and 60 deletions
10
.changelogs/v1.3.0.md
Normal file
10
.changelogs/v1.3.0.md
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
This release introduces XInput mode for the NaxGCC. This mode is mostly useful for playing games on PC, as it offers the best out-of-the-box compatiblity experience with most titles. Similar to Pro-Controller Mode, this is not a permanent configuration that you set, but a mode that is activated by pressing a button while plugging your controller in.
|
||||||
|
|
||||||
|
To enter XInput Mode, press and hold the `X` button while plugging in your controller. While in XInput mode, your NaxGCC will _always_ poll at 1ms intervals, regardless of your chosen input consistency mode setting. Once you go back to GCC or Pro-Controller Mode, your desired input consistency setting will be restored.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> As of this version, rumble will _not_ work while in XInput mode.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
To update your firmware, plug in your controller to your computer while keeping the `A+X+Y` buttons held. Then drag & drop the `.uf2` file (found below, under Downloads) onto the storage device that appears.
|
|
@ -10,8 +10,6 @@ jobs:
|
||||||
check:
|
check:
|
||||||
runs-on: nix-flakes
|
runs-on: nix-flakes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
@ -25,4 +23,4 @@ jobs:
|
||||||
|
|
||||||
- name: Run Clippy
|
- name: Run Clippy
|
||||||
run: |
|
run: |
|
||||||
nix build .#clippy --print-build-logs -j auto
|
nix flake check . --print-build-logs -j auto
|
||||||
|
|
|
@ -21,9 +21,9 @@ jobs:
|
||||||
cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
|
cache: "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Run Clippy
|
- name: Run flake checks
|
||||||
run: |
|
run: |
|
||||||
nix build .#clippy --print-build-logs -j auto
|
nix flake check . --print-build-logs -j auto
|
||||||
|
|
||||||
- name: Build firmware image
|
- name: Build firmware image
|
||||||
run: |
|
run: |
|
||||||
|
@ -38,7 +38,7 @@ jobs:
|
||||||
git tag nightly -m "Nightly Release"
|
git tag nightly -m "Nightly Release"
|
||||||
git checkout nightly
|
git checkout nightly
|
||||||
git push --set-upstream origin nightly --force
|
git push --set-upstream origin nightly --force
|
||||||
|
|
||||||
- name: Publish nightly release
|
- name: Publish nightly release
|
||||||
uses: https://gitea.com/actions/gitea-release-action@v1.3.0
|
uses: https://gitea.com/actions/gitea-release-action@v1.3.0
|
||||||
with:
|
with:
|
||||||
|
|
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -866,7 +866,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "naxgcc-fw"
|
name = "naxgcc-fw"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cortex-m",
|
"cortex-m",
|
||||||
"cortex-m-rt",
|
"cortex-m-rt",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "naxgcc-fw"
|
name = "naxgcc-fw"
|
||||||
version = "1.2.0"
|
version = "1.3.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
44
flake.nix
44
flake.nix
|
@ -47,33 +47,37 @@
|
||||||
CARGO_BUILD_TARGET = "thumbv6m-none-eabi";
|
CARGO_BUILD_TARGET = "thumbv6m-none-eabi";
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
packages.default = self.packages.${system}.naxgcc-fw-uf2;
|
packages = {
|
||||||
|
default = self.packages.${system}.naxgcc-fw-uf2;
|
||||||
|
|
||||||
packages.naxgcc-fw-uf2 = pkgs.runCommandLocal "${self.packages.${system}.naxgcc-fw.pname}-uf2-${self.packages.${system}.naxgcc-fw.version}" { } ''
|
naxgcc-fw-uf2 = pkgs.runCommandLocal "${self.packages.${system}.naxgcc-fw.pname}-uf2-${self.packages.${system}.naxgcc-fw.version}" { } ''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2
|
${pkgs.elf2uf2-rs}/bin/elf2uf2-rs ${self.packages.${system}.naxgcc-fw}/bin/${self.packages.${system}.naxgcc-fw.pname} $out/bin/${self.packages.${system}.naxgcc-fw.pname}.uf2
|
||||||
'';
|
'';
|
||||||
|
|
||||||
packages.naxgcc-fw = pkgs.callPackage
|
naxgcc-fw = pkgs.callPackage
|
||||||
({ mode ? "build" }: naersk_lib.buildPackage {
|
({ mode ? "build" }: naersk_lib.buildPackage {
|
||||||
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
pname = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.name;
|
||||||
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
|
version = (builtins.fromTOML (builtins.readFile ./Cargo.toml)).package.version;
|
||||||
|
|
||||||
inherit mode;
|
inherit mode;
|
||||||
|
|
||||||
src = self;
|
src = self;
|
||||||
|
|
||||||
cargoBuildOptions = _orig: _orig ++ [
|
cargoBuildOptions = _orig: _orig ++ [
|
||||||
"--target=${CARGO_BUILD_TARGET}"
|
"--target=${CARGO_BUILD_TARGET}"
|
||||||
];
|
];
|
||||||
|
|
||||||
# if a tree falls in the forest and no one is around to hear it, does it make a sound?
|
# if a tree falls in the forest and no one is around to hear it, does it make a sound?
|
||||||
DEFMT_LOG = "off";
|
DEFMT_LOG = "off";
|
||||||
})
|
})
|
||||||
{ };
|
{ };
|
||||||
|
};
|
||||||
|
|
||||||
packages.clippy = self.packages.${system}.naxgcc-fw.override {
|
checks = {
|
||||||
mode = "clippy";
|
clippy = self.packages.${system}.naxgcc-fw.override {
|
||||||
|
mode = "clippy";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
|
|
@ -571,6 +571,8 @@ pub enum ControllerMode {
|
||||||
GcAdapter = 0,
|
GcAdapter = 0,
|
||||||
/// Pretend to be a Nintendo Switch Pro Controller connected via USB.
|
/// Pretend to be a Nintendo Switch Pro Controller connected via USB.
|
||||||
Procon = 1,
|
Procon = 1,
|
||||||
|
/// Act as an XInput device, and also advertise itself with 1000Hz polling capability.
|
||||||
|
XInput = 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Format, PackedStruct)]
|
#[derive(Debug, Clone, Format, PackedStruct)]
|
||||||
|
|
|
@ -1,2 +1,70 @@
|
||||||
|
use embassy_usb::{
|
||||||
|
class::hid::{HidReader, HidReaderWriter, HidWriter, ReadError, RequestHandler},
|
||||||
|
driver::{Driver, EndpointError},
|
||||||
|
};
|
||||||
|
|
||||||
pub mod gcc;
|
pub mod gcc;
|
||||||
pub mod procon;
|
pub mod procon;
|
||||||
|
pub mod xinput;
|
||||||
|
|
||||||
|
/// Custom trait to unify the API between embassy's HID writer, and our XInput reader/writer (and any
|
||||||
|
/// custom writers we may create in the future)
|
||||||
|
pub trait HidReaderWriterSplit<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
|
||||||
|
fn split(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
impl UsbReader<'d, D, READ_N>,
|
||||||
|
impl UsbWriter<'d, D, WRITE_N>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Custom trait to unify the API between embassy's HID writer, and our XInput reader (and any
|
||||||
|
/// custom writers we may create in the future)
|
||||||
|
pub trait UsbReader<'d, D: Driver<'d>, const READ_N: usize> {
|
||||||
|
async fn run<T: RequestHandler>(self, use_report_ids: bool, handler: &mut T) -> !;
|
||||||
|
|
||||||
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Custom trait to unify the API between embassy's HID writer, and our XInput writer (and any
|
||||||
|
/// custom writers we may create in the future)
|
||||||
|
pub trait UsbWriter<'d, D: Driver<'d>, const WRITE_N: usize> {
|
||||||
|
async fn ready(&mut self);
|
||||||
|
|
||||||
|
async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize>
|
||||||
|
HidReaderWriterSplit<'d, D, READ_N, WRITE_N> for HidReaderWriter<'d, D, READ_N, WRITE_N>
|
||||||
|
{
|
||||||
|
fn split(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
impl UsbReader<'d, D, READ_N>,
|
||||||
|
impl UsbWriter<'d, D, WRITE_N>,
|
||||||
|
) {
|
||||||
|
self.split()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const READ_N: usize> UsbReader<'d, D, READ_N> for HidReader<'d, D, READ_N> {
|
||||||
|
async fn run<T: RequestHandler>(self, use_report_ids: bool, handler: &mut T) -> ! {
|
||||||
|
self.run(use_report_ids, handler).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
|
||||||
|
self.read(buf).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const WRITE_N: usize> UsbWriter<'d, D, WRITE_N>
|
||||||
|
for HidWriter<'d, D, WRITE_N>
|
||||||
|
{
|
||||||
|
async fn ready(&mut self) {
|
||||||
|
self.ready().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> {
|
||||||
|
self.write(report).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
544
src/hid/xinput.rs
Normal file
544
src/hid/xinput.rs
Normal file
|
@ -0,0 +1,544 @@
|
||||||
|
///
|
||||||
|
/// # XInput Protocol Implementation
|
||||||
|
///
|
||||||
|
/// The implementations for `XInputReader` and `XInputWriter` and the logic surrounding them is
|
||||||
|
/// mostly taken from embassy.
|
||||||
|
///
|
||||||
|
/// Unfortunately, the embassy hid classes don't allow us to specify a custom interface protocol,
|
||||||
|
/// hence the little bit of code duplication.
|
||||||
|
///
|
||||||
|
use core::{
|
||||||
|
mem::MaybeUninit,
|
||||||
|
sync::atomic::{AtomicUsize, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
|
use defmt::{info, trace, warn, Format};
|
||||||
|
use embassy_usb::{
|
||||||
|
class::hid::{Config, ReadError, ReportId, RequestHandler},
|
||||||
|
control::{InResponse, OutResponse, Recipient, Request, RequestType},
|
||||||
|
driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut},
|
||||||
|
types::InterfaceNumber,
|
||||||
|
Builder, Handler,
|
||||||
|
};
|
||||||
|
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||||
|
|
||||||
|
use crate::usb_comms::HidReportBuilder;
|
||||||
|
|
||||||
|
use super::{gcc::GcState, HidReaderWriterSplit, UsbReader, UsbWriter};
|
||||||
|
|
||||||
|
/// lol
|
||||||
|
pub const XINPUT_REPORT_DESCRIPTOR: &[u8] = &[];
|
||||||
|
|
||||||
|
const HID_DESC_DESCTYPE_HID: u8 = 0x21;
|
||||||
|
const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22;
|
||||||
|
|
||||||
|
const HID_REQ_SET_IDLE: u8 = 0x0a;
|
||||||
|
const HID_REQ_GET_IDLE: u8 = 0x02;
|
||||||
|
const HID_REQ_GET_REPORT: u8 = 0x01;
|
||||||
|
const HID_REQ_SET_REPORT: u8 = 0x09;
|
||||||
|
const HID_REQ_GET_PROTOCOL: u8 = 0x03;
|
||||||
|
const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||||
|
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||||
|
pub struct XInputButtons1 {
|
||||||
|
#[packed_field(bits = "0")]
|
||||||
|
pub dpad_up: bool,
|
||||||
|
#[packed_field(bits = "1")]
|
||||||
|
pub dpad_down: bool,
|
||||||
|
#[packed_field(bits = "2")]
|
||||||
|
pub dpad_left: bool,
|
||||||
|
#[packed_field(bits = "3")]
|
||||||
|
pub dpad_right: bool,
|
||||||
|
#[packed_field(bits = "4")]
|
||||||
|
pub button_menu: bool,
|
||||||
|
#[packed_field(bits = "5")]
|
||||||
|
pub button_back: bool,
|
||||||
|
#[packed_field(bits = "6")]
|
||||||
|
pub button_stick_l: bool,
|
||||||
|
#[packed_field(bits = "7")]
|
||||||
|
pub button_stick_r: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||||
|
#[packed_struct(bit_numbering = "lsb0", size_bytes = "1")]
|
||||||
|
pub struct XInputButtons2 {
|
||||||
|
#[packed_field(bits = "0")]
|
||||||
|
pub bumper_l: bool,
|
||||||
|
#[packed_field(bits = "1")]
|
||||||
|
pub bumper_r: bool,
|
||||||
|
#[packed_field(bits = "2")]
|
||||||
|
pub button_guide: bool,
|
||||||
|
#[packed_field(bits = "3")]
|
||||||
|
pub blank_1: bool,
|
||||||
|
#[packed_field(bits = "4")]
|
||||||
|
pub button_a: bool,
|
||||||
|
#[packed_field(bits = "5")]
|
||||||
|
pub button_b: bool,
|
||||||
|
#[packed_field(bits = "6")]
|
||||||
|
pub button_x: bool,
|
||||||
|
#[packed_field(bits = "7")]
|
||||||
|
pub button_y: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// HID report that is sent back to the host.
|
||||||
|
///
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
||||||
|
#[packed_struct(bit_numbering = "msb0", endian = "lsb", size_bytes = "32")]
|
||||||
|
pub struct XInputReport {
|
||||||
|
#[packed_field(bits = "0..=7")]
|
||||||
|
pub report_id: u8,
|
||||||
|
#[packed_field(bits = "8..=15")]
|
||||||
|
pub report_size: u8,
|
||||||
|
#[packed_field(bits = "16..=23")]
|
||||||
|
pub buttons_1: XInputButtons1,
|
||||||
|
#[packed_field(bits = "24..=31")]
|
||||||
|
pub buttons_2: XInputButtons2,
|
||||||
|
#[packed_field(bits = "32..=39")]
|
||||||
|
pub analog_trigger_l: u8,
|
||||||
|
#[packed_field(bits = "40..=47")]
|
||||||
|
pub analog_trigger_r: u8,
|
||||||
|
#[packed_field(bits = "48..=63")]
|
||||||
|
pub stick_left_x: i16,
|
||||||
|
#[packed_field(bits = "64..=79")]
|
||||||
|
pub stick_left_y: i16,
|
||||||
|
#[packed_field(bits = "80..=95")]
|
||||||
|
pub stick_right_x: i16,
|
||||||
|
#[packed_field(bits = "96..=111")]
|
||||||
|
pub stick_right_y: i16,
|
||||||
|
#[packed_field(bits = "112..=255")]
|
||||||
|
pub reserved: [u8; 18],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&GcState> for XInputReport {
|
||||||
|
fn from(value: &GcState) -> Self {
|
||||||
|
Self {
|
||||||
|
report_id: 0,
|
||||||
|
report_size: 20,
|
||||||
|
buttons_1: XInputButtons1 {
|
||||||
|
dpad_up: value.buttons_1.dpad_up,
|
||||||
|
dpad_down: value.buttons_1.dpad_down,
|
||||||
|
dpad_right: value.buttons_1.dpad_right,
|
||||||
|
dpad_left: value.buttons_1.dpad_left,
|
||||||
|
button_menu: value.buttons_2.button_start,
|
||||||
|
button_back: false,
|
||||||
|
button_stick_l: false,
|
||||||
|
button_stick_r: false,
|
||||||
|
},
|
||||||
|
buttons_2: XInputButtons2 {
|
||||||
|
blank_1: false,
|
||||||
|
bumper_l: false,
|
||||||
|
bumper_r: value.buttons_2.button_z,
|
||||||
|
button_a: value.buttons_1.button_a,
|
||||||
|
button_b: value.buttons_1.button_b,
|
||||||
|
button_x: value.buttons_1.button_x,
|
||||||
|
button_y: value.buttons_1.button_y,
|
||||||
|
button_guide: false,
|
||||||
|
},
|
||||||
|
analog_trigger_l: value.trigger_l,
|
||||||
|
analog_trigger_r: value.trigger_r,
|
||||||
|
stick_left_x: (value.stick_x as i16 - 127).clamp(-127, 127) * 257,
|
||||||
|
stick_left_y: (value.stick_y as i16 - 127).clamp(-127, 127) * 257,
|
||||||
|
stick_right_x: (value.cstick_x as i16 - 127).clamp(-127, 127) * 257,
|
||||||
|
stick_right_y: (value.cstick_y as i16 - 127).clamp(-127, 127) * 257,
|
||||||
|
reserved: [0u8; 18],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Takes in a GcState, converts it to an `XInputReport` and returns its packed version.
|
||||||
|
///
|
||||||
|
pub struct XInputReportBuilder;
|
||||||
|
|
||||||
|
impl HidReportBuilder<32> for XInputReportBuilder {
|
||||||
|
async fn get_hid_report(&mut self, state: &super::gcc::GcState) -> [u8; 32] {
|
||||||
|
XInputReport::from(state)
|
||||||
|
.pack()
|
||||||
|
.expect("Failed to pack XInput State")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Handles packets sent from the host.
|
||||||
|
///
|
||||||
|
pub struct XInputRequestHandler;
|
||||||
|
|
||||||
|
impl RequestHandler for XInputRequestHandler {
|
||||||
|
fn get_report(
|
||||||
|
&mut self,
|
||||||
|
id: embassy_usb::class::hid::ReportId,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Option<usize> {
|
||||||
|
let _ = (id, buf);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_report(
|
||||||
|
&mut self,
|
||||||
|
id: embassy_usb::class::hid::ReportId,
|
||||||
|
data: &[u8],
|
||||||
|
) -> embassy_usb::control::OutResponse {
|
||||||
|
let _ = (id, data);
|
||||||
|
info!("Set report for {:?}: {:x}", id, data);
|
||||||
|
embassy_usb::control::OutResponse::Accepted
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_idle_ms(&mut self, id: Option<embassy_usb::class::hid::ReportId>) -> Option<u32> {
|
||||||
|
let _ = id;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_idle_ms(&mut self, id: Option<embassy_usb::class::hid::ReportId>, duration_ms: u32) {
|
||||||
|
let _ = (id, duration_ms);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Taken from embassy.
|
||||||
|
pub struct XInputWriter<'d, D: Driver<'d>, const N: usize> {
|
||||||
|
ep_in: D::EndpointIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const N: usize> UsbWriter<'d, D, N> for XInputWriter<'d, D, N> {
|
||||||
|
/// Waits for the interrupt in endpoint to be enabled.
|
||||||
|
async fn ready(&mut self) {
|
||||||
|
self.ep_in.wait_enabled().await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `report` to its interrupt endpoint.
|
||||||
|
async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> {
|
||||||
|
assert!(report.len() <= N);
|
||||||
|
|
||||||
|
let max_packet_size = usize::from(self.ep_in.info().max_packet_size);
|
||||||
|
let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0);
|
||||||
|
for chunk in report.chunks(max_packet_size) {
|
||||||
|
self.ep_in.write(chunk).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if zlp_needed {
|
||||||
|
self.ep_in.write(&[]).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Taken from embassy.
|
||||||
|
pub struct XInputReader<'d, D: Driver<'d>, const N: usize> {
|
||||||
|
ep_out: D::EndpointOut,
|
||||||
|
offset: &'d AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const N: usize> UsbReader<'d, D, N> for XInputReader<'d, D, N> {
|
||||||
|
async fn run<T: RequestHandler>(mut self, use_report_ids: bool, handler: &mut T) -> ! {
|
||||||
|
let offset = self.offset.load(Ordering::Acquire);
|
||||||
|
assert!(offset == 0);
|
||||||
|
let mut buf = [0; N];
|
||||||
|
loop {
|
||||||
|
match self.read(&mut buf).await {
|
||||||
|
Ok(len) => {
|
||||||
|
let id = if use_report_ids { buf[0] } else { 0 };
|
||||||
|
handler.set_report(ReportId::Out(id), &buf[..len]);
|
||||||
|
}
|
||||||
|
Err(ReadError::BufferOverflow) => warn!(
|
||||||
|
"Host ent output report larger than the configured maximum output report length ({})",
|
||||||
|
N
|
||||||
|
),
|
||||||
|
Err(ReadError::Disabled) => self.ep_out.wait_enabled().await,
|
||||||
|
Err(ReadError::Sync(_)) => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> {
|
||||||
|
assert!(N != 0);
|
||||||
|
assert!(buf.len() >= N);
|
||||||
|
|
||||||
|
// Read packets from the endpoint
|
||||||
|
let max_packet_size = usize::from(self.ep_out.info().max_packet_size);
|
||||||
|
let starting_offset = self.offset.load(Ordering::Acquire);
|
||||||
|
let mut total = starting_offset;
|
||||||
|
loop {
|
||||||
|
for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) {
|
||||||
|
match self.ep_out.read(chunk).await {
|
||||||
|
Ok(size) => {
|
||||||
|
total += size;
|
||||||
|
if size < max_packet_size || total == N {
|
||||||
|
self.offset.store(0, Ordering::Release);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.offset.store(total, Ordering::Release);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.offset.store(0, Ordering::Release);
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0.
|
||||||
|
if total > 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if starting_offset > 0 {
|
||||||
|
Err(ReadError::Sync(starting_offset..total))
|
||||||
|
} else {
|
||||||
|
Ok(total)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Taken from embassy, with a few modifications to the descriptor.
|
||||||
|
pub struct XInputReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
|
||||||
|
reader: XInputReader<'d, D, READ_N>,
|
||||||
|
writer: XInputWriter<'d, D, WRITE_N>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize>
|
||||||
|
XInputReaderWriter<'d, D, READ_N, WRITE_N>
|
||||||
|
{
|
||||||
|
pub fn new(
|
||||||
|
builder: &mut Builder<'d, D>,
|
||||||
|
state: &'d mut XInputState<'d>,
|
||||||
|
config: Config<'d>,
|
||||||
|
) -> Self {
|
||||||
|
let mut func = builder.function(0xff, 0x5d, 0x01);
|
||||||
|
let mut iface = func.interface();
|
||||||
|
let if_num = iface.interface_number();
|
||||||
|
let mut alt = iface.alt_setting(0xff, 0x5d, 0x01, None);
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
alt.descriptor(0x21, &[
|
||||||
|
0x10, 0x01, // bcdHID 1.10
|
||||||
|
0x01, // bCountryCode
|
||||||
|
0x24, // bNumDescriptors
|
||||||
|
0x81, // bDescriptorType[0] (Unknown 0x81)
|
||||||
|
0x14, 0x03, // wDescriptorLength[0] 788
|
||||||
|
0x00, // bDescriptorType[1] (Unknown 0x00)
|
||||||
|
0x03, 0x13, // wDescriptorLength[1] 4867
|
||||||
|
0x02, // bDescriptorType[2] (Unknown 0x02)
|
||||||
|
0x00, 0x03, // wDescriptorLength[2] 768
|
||||||
|
0x00, // bDescriptorType[3] (Unknown 0x00)
|
||||||
|
]);
|
||||||
|
|
||||||
|
let ep_in = alt.endpoint_interrupt_in(config.max_packet_size_in, config.poll_ms);
|
||||||
|
let ep_out = alt.endpoint_interrupt_out(config.max_packet_size_out, config.poll_ms);
|
||||||
|
|
||||||
|
drop(func);
|
||||||
|
|
||||||
|
let control = state.control.write(XInputControl::new(
|
||||||
|
if_num,
|
||||||
|
config.report_descriptor,
|
||||||
|
config.request_handler,
|
||||||
|
&state.out_report_offset,
|
||||||
|
));
|
||||||
|
builder.handler(control);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
reader: XInputReader {
|
||||||
|
ep_out,
|
||||||
|
offset: &state.out_report_offset,
|
||||||
|
},
|
||||||
|
writer: XInputWriter { ep_in },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize>
|
||||||
|
HidReaderWriterSplit<'d, D, READ_N, WRITE_N> for XInputReaderWriter<'d, D, READ_N, WRITE_N>
|
||||||
|
{
|
||||||
|
fn split(
|
||||||
|
self,
|
||||||
|
) -> (
|
||||||
|
impl UsbReader<'d, D, READ_N>,
|
||||||
|
impl UsbWriter<'d, D, WRITE_N>,
|
||||||
|
) {
|
||||||
|
(self.reader, self.writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct XInputState<'d> {
|
||||||
|
control: MaybeUninit<XInputControl<'d>>,
|
||||||
|
out_report_offset: AtomicUsize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Default for XInputState<'d> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> XInputState<'d> {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
XInputState {
|
||||||
|
control: MaybeUninit::uninit(),
|
||||||
|
out_report_offset: AtomicUsize::new(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Taken from embassy.
|
||||||
|
struct XInputControl<'d> {
|
||||||
|
if_num: InterfaceNumber,
|
||||||
|
report_descriptor: &'d [u8],
|
||||||
|
request_handler: Option<&'d mut dyn RequestHandler>,
|
||||||
|
out_report_offset: &'d AtomicUsize,
|
||||||
|
hid_descriptor: [u8; 16],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> XInputControl<'d> {
|
||||||
|
fn new(
|
||||||
|
if_num: InterfaceNumber,
|
||||||
|
report_descriptor: &'d [u8],
|
||||||
|
request_handler: Option<&'d mut dyn RequestHandler>,
|
||||||
|
out_report_offset: &'d AtomicUsize,
|
||||||
|
) -> Self {
|
||||||
|
XInputControl {
|
||||||
|
if_num,
|
||||||
|
report_descriptor,
|
||||||
|
request_handler,
|
||||||
|
out_report_offset,
|
||||||
|
#[rustfmt::skip]
|
||||||
|
hid_descriptor: [
|
||||||
|
0x10, // bLength
|
||||||
|
0x21, // bDescriptorType (HID)
|
||||||
|
0x10, 0x01, // bcdHID 1.10
|
||||||
|
0x01, // bCountryCode
|
||||||
|
0x24, // bNumDescriptors
|
||||||
|
0x81, // bDescriptorType[0] (Unknown 0x81)
|
||||||
|
0x14, 0x03, // wDescriptorLength[0] 788
|
||||||
|
0x00, // bDescriptorType[1] (Unknown 0x00)
|
||||||
|
0x03, 0x13, // wDescriptorLength[1] 4867
|
||||||
|
0x02, // bDescriptorType[2] (Unknown 0x02)
|
||||||
|
0x00, 0x03, // wDescriptorLength[2] 768
|
||||||
|
0x00, // bDescriptorType[3] (Unknown 0x00)
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function, since the function in `ReportId` is private.
|
||||||
|
const fn try_u16_to_report_id(value: u16) -> Result<ReportId, ()> {
|
||||||
|
match value >> 8 {
|
||||||
|
1 => Ok(ReportId::In(value as u8)),
|
||||||
|
2 => Ok(ReportId::Out(value as u8)),
|
||||||
|
3 => Ok(ReportId::Feature(value as u8)),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d> Handler for XInputControl<'d> {
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.out_report_offset.store(0, Ordering::Release);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_out(&mut self, req: Request, data: &[u8]) -> Option<OutResponse> {
|
||||||
|
if (req.request_type, req.recipient, req.index)
|
||||||
|
!= (
|
||||||
|
RequestType::Class,
|
||||||
|
Recipient::Interface,
|
||||||
|
self.if_num.0 as u16,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("HID control_out {:?} {=[u8]:x}", req, data);
|
||||||
|
|
||||||
|
match req.request {
|
||||||
|
HID_REQ_SET_IDLE => {
|
||||||
|
if let Some(handler) = self.request_handler.as_mut() {
|
||||||
|
let id = req.value as u8;
|
||||||
|
let id = (id != 0).then_some(ReportId::In(id));
|
||||||
|
let dur = u32::from(req.value >> 8);
|
||||||
|
let dur = if dur == 0 { u32::MAX } else { 4 * dur };
|
||||||
|
handler.set_idle_ms(id, dur);
|
||||||
|
}
|
||||||
|
Some(OutResponse::Accepted)
|
||||||
|
}
|
||||||
|
HID_REQ_SET_REPORT => {
|
||||||
|
match (
|
||||||
|
try_u16_to_report_id(req.value),
|
||||||
|
self.request_handler.as_mut(),
|
||||||
|
) {
|
||||||
|
(Ok(id), Some(handler)) => Some(handler.set_report(id, data)),
|
||||||
|
_ => Some(OutResponse::Rejected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HID_REQ_SET_PROTOCOL => {
|
||||||
|
if req.value == 1 {
|
||||||
|
Some(OutResponse::Accepted)
|
||||||
|
} else {
|
||||||
|
warn!("HID Boot Protocol is unsupported.");
|
||||||
|
Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Some(OutResponse::Rejected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
|
||||||
|
if req.index != self.if_num.0 as u16 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
match (req.request_type, req.recipient) {
|
||||||
|
(RequestType::Standard, Recipient::Interface) => match req.request {
|
||||||
|
Request::GET_DESCRIPTOR => match (req.value >> 8) as u8 {
|
||||||
|
HID_DESC_DESCTYPE_HID_REPORT => {
|
||||||
|
Some(InResponse::Accepted(self.report_descriptor))
|
||||||
|
}
|
||||||
|
HID_DESC_DESCTYPE_HID => Some(InResponse::Accepted(&self.hid_descriptor)),
|
||||||
|
_ => Some(InResponse::Rejected),
|
||||||
|
},
|
||||||
|
|
||||||
|
_ => Some(InResponse::Rejected),
|
||||||
|
},
|
||||||
|
(RequestType::Class, Recipient::Interface) => {
|
||||||
|
trace!("HID control_in {:?}", req);
|
||||||
|
match req.request {
|
||||||
|
HID_REQ_GET_REPORT => {
|
||||||
|
let size = match try_u16_to_report_id(req.value) {
|
||||||
|
Ok(id) => self
|
||||||
|
.request_handler
|
||||||
|
.as_mut()
|
||||||
|
.and_then(|x| x.get_report(id, buf)),
|
||||||
|
Err(_) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(size) = size {
|
||||||
|
Some(InResponse::Accepted(&buf[0..size]))
|
||||||
|
} else {
|
||||||
|
Some(InResponse::Rejected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HID_REQ_GET_IDLE => {
|
||||||
|
if let Some(handler) = self.request_handler.as_mut() {
|
||||||
|
let id = req.value as u8;
|
||||||
|
let id = (id != 0).then_some(ReportId::In(id));
|
||||||
|
if let Some(dur) = handler.get_idle_ms(id) {
|
||||||
|
let dur = u8::try_from(dur / 4).unwrap_or(0);
|
||||||
|
buf[0] = dur;
|
||||||
|
Some(InResponse::Accepted(&buf[0..1]))
|
||||||
|
} else {
|
||||||
|
Some(InResponse::Rejected)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Some(InResponse::Rejected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HID_REQ_GET_PROTOCOL => {
|
||||||
|
// UNSUPPORTED: Boot Protocol
|
||||||
|
buf[0] = 1;
|
||||||
|
Some(InResponse::Accepted(&buf[0..1]))
|
||||||
|
}
|
||||||
|
_ => Some(InResponse::Rejected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -428,6 +428,8 @@ pub async fn update_button_state_task(
|
||||||
let mut m = MUTEX_CONTROLLER_MODE.lock().await;
|
let mut m = MUTEX_CONTROLLER_MODE.lock().await;
|
||||||
*m = if btn_start.is_low() {
|
*m = if btn_start.is_low() {
|
||||||
Some(ControllerMode::Procon)
|
Some(ControllerMode::Procon)
|
||||||
|
} else if btn_x.is_low() {
|
||||||
|
Some(ControllerMode::XInput)
|
||||||
} else {
|
} else {
|
||||||
Some(ControllerMode::GcAdapter)
|
Some(ControllerMode::GcAdapter)
|
||||||
};
|
};
|
||||||
|
|
139
src/usb_comms.rs
139
src/usb_comms.rs
|
@ -15,7 +15,7 @@ use embassy_rp::{
|
||||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
|
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
|
||||||
use embassy_time::{Duration, Instant, Timer};
|
use embassy_time::{Duration, Instant, Timer};
|
||||||
use embassy_usb::{
|
use embassy_usb::{
|
||||||
class::hid::{HidReader, HidReaderWriter, HidWriter, RequestHandler, State},
|
class::hid::{HidReaderWriter, RequestHandler, State},
|
||||||
driver::Driver,
|
driver::Driver,
|
||||||
msos::{self, windows_version},
|
msos::{self, windows_version},
|
||||||
Builder, Handler, UsbDevice,
|
Builder, Handler, UsbDevice,
|
||||||
|
@ -27,6 +27,11 @@ use crate::{
|
||||||
hid::{
|
hid::{
|
||||||
gcc::{GcReportBuilder, GcState, GccRequestHandler, GCC_REPORT_DESCRIPTOR},
|
gcc::{GcReportBuilder, GcState, GccRequestHandler, GCC_REPORT_DESCRIPTOR},
|
||||||
procon::{ProconReportBuilder, ProconRequestHandler, PROCON_REPORT_DESCRIPTOR},
|
procon::{ProconReportBuilder, ProconRequestHandler, PROCON_REPORT_DESCRIPTOR},
|
||||||
|
xinput::{
|
||||||
|
XInputReaderWriter, XInputReportBuilder, XInputRequestHandler, XInputState,
|
||||||
|
XINPUT_REPORT_DESCRIPTOR,
|
||||||
|
},
|
||||||
|
HidReaderWriterSplit, UsbReader, UsbWriter,
|
||||||
},
|
},
|
||||||
input::CHANNEL_GCC_STATE,
|
input::CHANNEL_GCC_STATE,
|
||||||
};
|
};
|
||||||
|
@ -73,6 +78,11 @@ impl From<ControllerMode> for UsbConfig {
|
||||||
pid: 0x2009,
|
pid: 0x2009,
|
||||||
report_descriptor: PROCON_REPORT_DESCRIPTOR,
|
report_descriptor: PROCON_REPORT_DESCRIPTOR,
|
||||||
},
|
},
|
||||||
|
ControllerMode::XInput => Self {
|
||||||
|
vid: 0x045e,
|
||||||
|
pid: 0x028e,
|
||||||
|
report_descriptor: XINPUT_REPORT_DESCRIPTOR,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,27 +132,41 @@ impl Handler for MyDeviceHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
|
fn mk_hid_reader_writer<'d, S, D, T, F, const R: usize, const W: usize>(
|
||||||
input_consistency_mode: InputConsistencyMode,
|
input_consistency_mode: InputConsistencyMode,
|
||||||
|
controller_mode: ControllerMode,
|
||||||
report_descriptor: &'d [u8],
|
report_descriptor: &'d [u8],
|
||||||
mut builder: Builder<'d, D>,
|
mut builder: Builder<'d, D>,
|
||||||
state: &'d mut State<'d>,
|
state: &'d mut S,
|
||||||
) -> (UsbDevice<'d, D>, HidReader<'d, D, R>, HidWriter<'d, D, W>) {
|
mut init_func: F,
|
||||||
|
) -> (
|
||||||
|
UsbDevice<'d, D>,
|
||||||
|
impl UsbReader<'d, D, R>,
|
||||||
|
impl UsbWriter<'d, D, W>,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
D: Driver<'d>,
|
||||||
|
T: HidReaderWriterSplit<'d, D, R, W> + 'd,
|
||||||
|
F: FnMut(&mut Builder<'d, D>, &'d mut S, embassy_usb::class::hid::Config<'d>) -> T + 'd,
|
||||||
|
{
|
||||||
let hid_config = embassy_usb::class::hid::Config {
|
let hid_config = embassy_usb::class::hid::Config {
|
||||||
report_descriptor,
|
report_descriptor,
|
||||||
request_handler: None,
|
request_handler: None,
|
||||||
poll_ms: match input_consistency_mode {
|
poll_ms: if let ControllerMode::XInput = controller_mode {
|
||||||
InputConsistencyMode::Original => 8,
|
1
|
||||||
InputConsistencyMode::ConsistencyHack
|
} else {
|
||||||
| InputConsistencyMode::SuperHack
|
match input_consistency_mode {
|
||||||
| InputConsistencyMode::PC => 1,
|
InputConsistencyMode::Original => 8,
|
||||||
|
InputConsistencyMode::ConsistencyHack
|
||||||
|
| InputConsistencyMode::SuperHack
|
||||||
|
| InputConsistencyMode::PC => 1,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
max_packet_size_in: W as u16,
|
max_packet_size_in: W as u16,
|
||||||
max_packet_size_out: R as u16,
|
max_packet_size_out: R as u16,
|
||||||
};
|
};
|
||||||
|
|
||||||
let hid: HidReaderWriter<'d, D, R, W> =
|
let hid = init_func(&mut builder, state, hid_config);
|
||||||
HidReaderWriter::<'_, D, R, W>::new(&mut builder, state, hid_config);
|
|
||||||
|
|
||||||
let usb = builder.build();
|
let usb = builder.build();
|
||||||
|
|
||||||
|
@ -151,13 +175,16 @@ fn mk_hid_reader_writer<'d, D: Driver<'d>, const R: usize, const W: usize>(
|
||||||
(usb, reader, writer)
|
(usb, reader, writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mk_usb_transfer_futures<'d, D, H, Rq, const R: usize, const W: usize>(
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn mk_usb_transfer_futures<'d, S, D, H, F, Rq, T, const R: usize, const W: usize>(
|
||||||
input_consistency_mode: InputConsistencyMode,
|
input_consistency_mode: InputConsistencyMode,
|
||||||
|
controller_mode: ControllerMode,
|
||||||
usb_config: &UsbConfig,
|
usb_config: &UsbConfig,
|
||||||
request_handler: &'d mut Rq,
|
request_handler: &'d mut Rq,
|
||||||
builder: Builder<'d, D>,
|
builder: Builder<'d, D>,
|
||||||
state: &'d mut State<'d>,
|
|
||||||
mut hid_report_builder: H,
|
mut hid_report_builder: H,
|
||||||
|
state: &'d mut S,
|
||||||
|
init_func: F,
|
||||||
) -> (
|
) -> (
|
||||||
impl Future<Output = ()> + 'd,
|
impl Future<Output = ()> + 'd,
|
||||||
impl Future<Output = ()> + 'd,
|
impl Future<Output = ()> + 'd,
|
||||||
|
@ -167,12 +194,16 @@ where
|
||||||
D: Driver<'d> + 'd,
|
D: Driver<'d> + 'd,
|
||||||
H: HidReportBuilder<W> + 'd,
|
H: HidReportBuilder<W> + 'd,
|
||||||
Rq: RequestHandler,
|
Rq: RequestHandler,
|
||||||
|
T: HidReaderWriterSplit<'d, D, R, W> + 'd,
|
||||||
|
F: FnMut(&mut Builder<'d, D>, &'d mut S, embassy_usb::class::hid::Config<'d>) -> T + 'd,
|
||||||
{
|
{
|
||||||
let (mut usb, reader, mut writer) = mk_hid_reader_writer::<_, R, W>(
|
let (mut usb, reader, mut writer) = mk_hid_reader_writer(
|
||||||
input_consistency_mode,
|
input_consistency_mode,
|
||||||
|
controller_mode,
|
||||||
usb_config.report_descriptor,
|
usb_config.report_descriptor,
|
||||||
builder,
|
builder,
|
||||||
state,
|
state,
|
||||||
|
init_func,
|
||||||
);
|
);
|
||||||
|
|
||||||
let usb_fut = async move {
|
let usb_fut = async move {
|
||||||
|
@ -200,12 +231,14 @@ where
|
||||||
// From the console's perspective, we are basically a laggy adapter, taking
|
// From the console's perspective, we are basically a laggy adapter, taking
|
||||||
// a minimum of 333 extra us to send a report every time it's polled, but it
|
// a minimum of 333 extra us to send a report every time it's polled, but it
|
||||||
// works to our advantage.
|
// works to our advantage.
|
||||||
match input_consistency_mode {
|
if controller_mode != ControllerMode::XInput {
|
||||||
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
|
match input_consistency_mode {
|
||||||
// "Ticker at home", so we can use this for both consistency and SuperHack mode
|
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
|
||||||
Timer::at(rate_limit_end_time).await;
|
// "Ticker at home", so we can use this for both consistency and SuperHack mode
|
||||||
|
Timer::at(rate_limit_end_time).await;
|
||||||
|
}
|
||||||
|
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
|
||||||
}
|
}
|
||||||
InputConsistencyMode::Original | InputConsistencyMode::PC => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.ready().await;
|
writer.ready().await;
|
||||||
|
@ -224,6 +257,7 @@ where
|
||||||
debug!("Report written in {}us", micros);
|
debug!("Report written in {}us", micros);
|
||||||
if input_consistency_mode != InputConsistencyMode::Original
|
if input_consistency_mode != InputConsistencyMode::Original
|
||||||
&& input_consistency_mode != InputConsistencyMode::PC
|
&& input_consistency_mode != InputConsistencyMode::PC
|
||||||
|
&& controller_mode != ControllerMode::XInput
|
||||||
{
|
{
|
||||||
while rate_limit_end_time < currtime {
|
while rate_limit_end_time < currtime {
|
||||||
rate_limit_end_time += Duration::from_micros(8333);
|
rate_limit_end_time += Duration::from_micros(8333);
|
||||||
|
@ -291,20 +325,37 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
|
||||||
trace!("Start of config");
|
trace!("Start of config");
|
||||||
let mut usb_config = embassy_usb::Config::new(config.vid, config.pid);
|
let mut usb_config = embassy_usb::Config::new(config.vid, config.pid);
|
||||||
usb_config.manufacturer = Some("Naxdy");
|
usb_config.manufacturer = Some("Naxdy");
|
||||||
usb_config.product = Some(match input_consistency_mode {
|
usb_config.product = Some(if controller_mode == ControllerMode::XInput {
|
||||||
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
|
"NaxGCC (XInput Mode)"
|
||||||
InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)",
|
} else {
|
||||||
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
|
match input_consistency_mode {
|
||||||
InputConsistencyMode::PC => "NaxGCC (PC Mode)",
|
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
|
||||||
|
InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)",
|
||||||
|
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
|
||||||
|
InputConsistencyMode::PC => "NaxGCC (PC Mode)",
|
||||||
|
}
|
||||||
});
|
});
|
||||||
usb_config.serial_number = Some(serial);
|
usb_config.serial_number = Some(serial);
|
||||||
usb_config.max_power = 200;
|
usb_config.max_power = 200;
|
||||||
usb_config.max_packet_size_0 = 64;
|
usb_config.max_packet_size_0 = 64;
|
||||||
usb_config.device_class = 0;
|
usb_config.device_class = if controller_mode == ControllerMode::XInput {
|
||||||
|
0xff
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
usb_config.device_protocol = 0;
|
usb_config.device_protocol = 0;
|
||||||
usb_config.self_powered = false;
|
usb_config.self_powered = false;
|
||||||
usb_config.device_sub_class = 0;
|
usb_config.device_sub_class = if controller_mode == ControllerMode::XInput {
|
||||||
|
0xff
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
usb_config.supports_remote_wakeup = true;
|
usb_config.supports_remote_wakeup = true;
|
||||||
|
usb_config.device_release = if controller_mode == ControllerMode::XInput {
|
||||||
|
0x0572
|
||||||
|
} else {
|
||||||
|
0x0010
|
||||||
|
};
|
||||||
|
|
||||||
let mut config_descriptor = [0; 256];
|
let mut config_descriptor = [0; 256];
|
||||||
let mut bos_descriptor = [0; 256];
|
let mut bos_descriptor = [0; 256];
|
||||||
|
@ -313,8 +364,6 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
|
||||||
|
|
||||||
let mut device_handler = MyDeviceHandler::new();
|
let mut device_handler = MyDeviceHandler::new();
|
||||||
|
|
||||||
let mut state = State::new();
|
|
||||||
|
|
||||||
let mut builder = Builder::new(
|
let mut builder = Builder::new(
|
||||||
driver,
|
driver,
|
||||||
usb_config,
|
usb_config,
|
||||||
|
@ -337,29 +386,55 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: EmbassyDriver<'stati
|
||||||
|
|
||||||
match controller_mode {
|
match controller_mode {
|
||||||
ControllerMode::GcAdapter => {
|
ControllerMode::GcAdapter => {
|
||||||
|
let mut state = State::new();
|
||||||
|
|
||||||
let mut request_handler = GccRequestHandler;
|
let mut request_handler = GccRequestHandler;
|
||||||
|
|
||||||
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 5, 37>(
|
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures(
|
||||||
input_consistency_mode,
|
input_consistency_mode,
|
||||||
|
controller_mode,
|
||||||
&config,
|
&config,
|
||||||
&mut request_handler,
|
&mut request_handler,
|
||||||
builder,
|
builder,
|
||||||
&mut state,
|
|
||||||
GcReportBuilder::default(),
|
GcReportBuilder::default(),
|
||||||
|
&mut state,
|
||||||
|
HidReaderWriter::<_, 5, 37>::new,
|
||||||
);
|
);
|
||||||
|
|
||||||
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||||
}
|
}
|
||||||
ControllerMode::Procon => {
|
ControllerMode::Procon => {
|
||||||
|
let mut state = State::new();
|
||||||
|
|
||||||
let mut request_handler = ProconRequestHandler;
|
let mut request_handler = ProconRequestHandler;
|
||||||
|
|
||||||
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures::<_, _, _, 64, 64>(
|
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures(
|
||||||
input_consistency_mode,
|
input_consistency_mode,
|
||||||
|
controller_mode,
|
||||||
&config,
|
&config,
|
||||||
&mut request_handler,
|
&mut request_handler,
|
||||||
builder,
|
builder,
|
||||||
&mut state,
|
|
||||||
ProconReportBuilder::default(),
|
ProconReportBuilder::default(),
|
||||||
|
&mut state,
|
||||||
|
HidReaderWriter::<_, 64, 64>::new,
|
||||||
|
);
|
||||||
|
|
||||||
|
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||||
|
}
|
||||||
|
ControllerMode::XInput => {
|
||||||
|
let mut state = XInputState::new();
|
||||||
|
|
||||||
|
let mut request_handler = XInputRequestHandler;
|
||||||
|
|
||||||
|
let (usb_fut_wrapped, in_fut, out_fut) = mk_usb_transfer_futures(
|
||||||
|
input_consistency_mode,
|
||||||
|
controller_mode,
|
||||||
|
&config,
|
||||||
|
&mut request_handler,
|
||||||
|
builder,
|
||||||
|
XInputReportBuilder,
|
||||||
|
&mut state,
|
||||||
|
XInputReaderWriter::<_, 32, 32>::new,
|
||||||
);
|
);
|
||||||
|
|
||||||
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
join(usb_fut_wrapped, join(in_fut, out_fut)).await;
|
||||||
|
|
Loading…
Reference in a new issue