Fix Windows adapter enumeration and rumble
This commit is contained in:
parent
a7fb6c5694
commit
db4a860cd6
10 changed files with 363 additions and 44 deletions
242
bluepad32_config/parser/uni_hid_parser_imu.h
Normal file
242
bluepad32_config/parser/uni_hid_parser_imu.h
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
#pragma once
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#define UNI_IMU_ACCEL_RES_PER_G 8192
|
||||
#define UNI_IMU_GYRO_RES_PER_DEG_S 1024
|
||||
#define UNI_PSMOVE_CALIBRATION_REPORT_SIZE 49
|
||||
#define UNI_PSMOVE_ZCM1_CALIBRATION_SIZE 143
|
||||
#define UNI_PSMOVE_ZCM2_CALIBRATION_SIZE 96
|
||||
|
||||
typedef enum {
|
||||
UNI_PSMOVE_IMU_MODEL_UNKNOWN = 0,
|
||||
UNI_PSMOVE_IMU_MODEL_ZCM1,
|
||||
UNI_PSMOVE_IMU_MODEL_ZCM2,
|
||||
} uni_psmove_imu_model_t;
|
||||
|
||||
typedef enum {
|
||||
UNI_PSMOVE_CALIBRATION_INVALID = 0,
|
||||
UNI_PSMOVE_CALIBRATION_INCOMPLETE,
|
||||
UNI_PSMOVE_CALIBRATION_COMPLETE,
|
||||
} uni_psmove_calibration_result_t;
|
||||
|
||||
typedef struct {
|
||||
int32_t accel[3];
|
||||
int32_t gyro[3];
|
||||
} uni_imu_fixed_sample_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t data[UNI_PSMOVE_ZCM1_CALIBRATION_SIZE];
|
||||
uni_psmove_imu_model_t model;
|
||||
uint8_t received_blocks;
|
||||
bool complete;
|
||||
} uni_psmove_imu_calibration_t;
|
||||
|
||||
static inline int32_t uni_imu_clamp_i64(int64_t value) {
|
||||
if (value > INT32_MAX) {
|
||||
return INT32_MAX;
|
||||
}
|
||||
if (value < INT32_MIN) {
|
||||
return INT32_MIN;
|
||||
}
|
||||
return (int32_t)value;
|
||||
}
|
||||
|
||||
static inline int32_t uni_imu_scale(int32_t value, int32_t span,
|
||||
int32_t full_scale) {
|
||||
if (span <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return uni_imu_clamp_i64((int64_t)value * full_scale / span);
|
||||
}
|
||||
|
||||
static inline int32_t uni_psmove_scale_gyro(int32_t raw, int32_t bias,
|
||||
int32_t span,
|
||||
int32_t full_scale) {
|
||||
if (span <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return uni_imu_clamp_i64(
|
||||
((int64_t)raw - bias) * full_scale / span);
|
||||
}
|
||||
|
||||
static inline int32_t uni_psmove_decode_value(
|
||||
uni_psmove_imu_model_t model, uint16_t value) {
|
||||
if (model == UNI_PSMOVE_IMU_MODEL_ZCM1) {
|
||||
return (int32_t)value - 0x8000;
|
||||
}
|
||||
return (int16_t)value;
|
||||
}
|
||||
|
||||
static inline int32_t uni_psmove_read_calibration_value(
|
||||
const uint8_t* data, uni_psmove_imu_model_t model, uint8_t offset) {
|
||||
const uint16_t value =
|
||||
(uint16_t)(data[offset] | ((uint16_t)data[offset + 1] << 8));
|
||||
return uni_psmove_decode_value(model, value);
|
||||
}
|
||||
|
||||
static inline uni_psmove_calibration_result_t
|
||||
uni_psmove_add_calibration_report(uni_psmove_imu_calibration_t* calibration,
|
||||
uni_psmove_imu_model_t model,
|
||||
const uint8_t* report, uint16_t length) {
|
||||
if (calibration == NULL || report == NULL ||
|
||||
length != UNI_PSMOVE_CALIBRATION_REPORT_SIZE || report[0] != 0x10 ||
|
||||
(model != UNI_PSMOVE_IMU_MODEL_ZCM1 &&
|
||||
model != UNI_PSMOVE_IMU_MODEL_ZCM2)) {
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
if (calibration->model != UNI_PSMOVE_IMU_MODEL_UNKNOWN &&
|
||||
calibration->model != model) {
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
calibration->model = model;
|
||||
|
||||
size_t offset;
|
||||
size_t source_offset;
|
||||
uint8_t block_mask;
|
||||
switch (report[1]) {
|
||||
case 0x00:
|
||||
offset = 0;
|
||||
source_offset = 0;
|
||||
block_mask = 0x01;
|
||||
break;
|
||||
case 0x01:
|
||||
if (model != UNI_PSMOVE_IMU_MODEL_ZCM1) {
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
offset = UNI_PSMOVE_CALIBRATION_REPORT_SIZE;
|
||||
source_offset = 2;
|
||||
block_mask = 0x02;
|
||||
break;
|
||||
case 0x81:
|
||||
if (model != UNI_PSMOVE_IMU_MODEL_ZCM2) {
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
offset = UNI_PSMOVE_CALIBRATION_REPORT_SIZE;
|
||||
source_offset = 2;
|
||||
block_mask = 0x02;
|
||||
break;
|
||||
case 0x82:
|
||||
if (model != UNI_PSMOVE_IMU_MODEL_ZCM1) {
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
offset = 2 * UNI_PSMOVE_CALIBRATION_REPORT_SIZE - 2;
|
||||
source_offset = 2;
|
||||
block_mask = 0x04;
|
||||
break;
|
||||
default:
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
|
||||
const size_t copy_size = length - source_offset;
|
||||
const size_t calibration_size =
|
||||
model == UNI_PSMOVE_IMU_MODEL_ZCM1
|
||||
? UNI_PSMOVE_ZCM1_CALIBRATION_SIZE
|
||||
: UNI_PSMOVE_ZCM2_CALIBRATION_SIZE;
|
||||
if (offset + copy_size > calibration_size) {
|
||||
return UNI_PSMOVE_CALIBRATION_INVALID;
|
||||
}
|
||||
memcpy(&calibration->data[offset], &report[source_offset], copy_size);
|
||||
calibration->received_blocks |= block_mask;
|
||||
|
||||
const uint8_t required_blocks =
|
||||
model == UNI_PSMOVE_IMU_MODEL_ZCM1 ? 0x07 : 0x03;
|
||||
calibration->complete =
|
||||
(calibration->received_blocks & required_blocks) == required_blocks;
|
||||
return calibration->complete ? UNI_PSMOVE_CALIBRATION_COMPLETE
|
||||
: UNI_PSMOVE_CALIBRATION_INCOMPLETE;
|
||||
}
|
||||
|
||||
static inline bool uni_psmove_normalize_imu(
|
||||
uni_psmove_imu_model_t model,
|
||||
const uni_psmove_imu_calibration_t* calibration,
|
||||
const uint16_t accel_first[3], const uint16_t accel_second[3],
|
||||
const uint16_t gyro_first[3], const uint16_t gyro_second[3],
|
||||
uni_imu_fixed_sample_t* output) {
|
||||
if (output == NULL) {
|
||||
return false;
|
||||
}
|
||||
memset(output, 0, sizeof(*output));
|
||||
if (calibration == NULL || !calibration->complete ||
|
||||
calibration->model != model || accel_first == NULL ||
|
||||
accel_second == NULL || gyro_first == NULL || gyro_second == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static const uint8_t zcm1_accel_low[] = {0x0a, 0x24, 0x14};
|
||||
static const uint8_t zcm1_accel_high[] = {0x16, 0x1e, 0x08};
|
||||
static const uint8_t zcm2_accel_low[] = {0x08, 0x16, 0x24};
|
||||
static const uint8_t zcm2_accel_high[] = {0x02, 0x10, 0x1e};
|
||||
static const uint8_t zcm1_gyro_bias[] = {0x2a, 0x2c, 0x2e};
|
||||
static const uint8_t zcm1_gyro_high[] = {0x46, 0x50, 0x5a};
|
||||
static const uint8_t zcm2_gyro_bias[] = {0x26, 0x28, 0x2a};
|
||||
static const uint8_t zcm2_gyro_low[] = {0x42, 0x4a, 0x52};
|
||||
static const uint8_t zcm2_gyro_high[] = {0x30, 0x38, 0x40};
|
||||
|
||||
const uint8_t* accel_low =
|
||||
model == UNI_PSMOVE_IMU_MODEL_ZCM1 ? zcm1_accel_low : zcm2_accel_low;
|
||||
const uint8_t* accel_high =
|
||||
model == UNI_PSMOVE_IMU_MODEL_ZCM1 ? zcm1_accel_high : zcm2_accel_high;
|
||||
const uint8_t* gyro_bias =
|
||||
model == UNI_PSMOVE_IMU_MODEL_ZCM1 ? zcm1_gyro_bias : zcm2_gyro_bias;
|
||||
const uint8_t* gyro_high =
|
||||
model == UNI_PSMOVE_IMU_MODEL_ZCM1 ? zcm1_gyro_high : zcm2_gyro_high;
|
||||
const int32_t gyro_full_scale =
|
||||
(model == UNI_PSMOVE_IMU_MODEL_ZCM1 ? 480 : 540) *
|
||||
UNI_IMU_GYRO_RES_PER_DEG_S;
|
||||
|
||||
for (uint8_t axis = 0; axis < 3; ++axis) {
|
||||
const int32_t accel_low_value = uni_psmove_read_calibration_value(
|
||||
calibration->data, model, accel_low[axis]);
|
||||
const int32_t accel_high_value = uni_psmove_read_calibration_value(
|
||||
calibration->data, model, accel_high[axis]);
|
||||
const int32_t accel_center =
|
||||
(accel_low_value + accel_high_value) / 2;
|
||||
const int32_t accel_raw =
|
||||
(uni_psmove_decode_value(model, accel_first[axis]) +
|
||||
uni_psmove_decode_value(model, accel_second[axis])) /
|
||||
2;
|
||||
const int32_t accel_delta = accel_raw - accel_center;
|
||||
const int32_t accel_span =
|
||||
accel_delta < 0 ? accel_center - accel_low_value
|
||||
: accel_high_value - accel_center;
|
||||
output->accel[axis] =
|
||||
uni_imu_scale(accel_delta, accel_span, UNI_IMU_ACCEL_RES_PER_G);
|
||||
|
||||
const int32_t gyro_bias_value = uni_psmove_read_calibration_value(
|
||||
calibration->data, model, gyro_bias[axis]);
|
||||
const int32_t gyro_raw =
|
||||
(uni_psmove_decode_value(model, gyro_first[axis]) +
|
||||
uni_psmove_decode_value(model, gyro_second[axis])) /
|
||||
2;
|
||||
int32_t gyro_span;
|
||||
if (model == UNI_PSMOVE_IMU_MODEL_ZCM1 ||
|
||||
gyro_raw >= gyro_bias_value) {
|
||||
gyro_span = uni_psmove_read_calibration_value(
|
||||
calibration->data, model, gyro_high[axis]) -
|
||||
gyro_bias_value;
|
||||
} else {
|
||||
gyro_span = gyro_bias_value - uni_psmove_read_calibration_value(
|
||||
calibration->data, model,
|
||||
zcm2_gyro_low[axis]);
|
||||
}
|
||||
output->gyro[axis] = uni_psmove_scale_gyro(
|
||||
gyro_raw, gyro_bias_value, gyro_span, gyro_full_scale);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline void uni_imu_normalize_wii_accel(int32_t x, int32_t y,
|
||||
int32_t z,
|
||||
int32_t output[3]) {
|
||||
if (output == NULL) {
|
||||
return;
|
||||
}
|
||||
output[0] = uni_imu_scale(-x, 100, UNI_IMU_ACCEL_RES_PER_G);
|
||||
output[1] = uni_imu_scale(z, 100, UNI_IMU_ACCEL_RES_PER_G);
|
||||
output[2] = uni_imu_scale(y, 100, UNI_IMU_ACCEL_RES_PER_G);
|
||||
}
|
||||
|
|
@ -20,7 +20,14 @@ constexpr int32_t kAxisMinimum = -512;
|
|||
constexpr int32_t kAxisMaximum = 511;
|
||||
constexpr int32_t kTriggerMaximum = 1023;
|
||||
constexpr int32_t kTriggerThreshold = (kTriggerMaximum * 35) / 100;
|
||||
#ifdef SWITCH_PICO_ADAPTER_FEASIBILITY
|
||||
// XInput vibration is stateful: it remains active until XInputSetState sends
|
||||
// a new magnitude. Use the longest Bluepad32 duration and stop explicitly on
|
||||
// the zero-magnitude packet.
|
||||
constexpr uint16_t kRumbleDurationMs = UINT16_MAX;
|
||||
#else
|
||||
constexpr uint16_t kRumbleDurationMs = 50;
|
||||
#endif
|
||||
constexpr uint32_t kRumblePollIntervalMs = 5;
|
||||
constexpr uint8_t kSlotCount = BLUEPAD32_INPUT_BACKEND_SLOT_COUNT;
|
||||
constexpr uint32_t kPairingWindowDurationMs = 60000;
|
||||
|
|
@ -725,8 +732,11 @@ void process_rumble_timer(btstack_timer_source_t* timer) {
|
|||
feedback.weak_magnitude, feedback.strong_magnitude);
|
||||
} else if (host_dispatch &&
|
||||
device->report_parser.play_dual_rumble != nullptr) {
|
||||
const bool stop =
|
||||
envelope.rumble.low_frequency_magnitude == 0 &&
|
||||
envelope.rumble.high_frequency_magnitude == 0;
|
||||
device->report_parser.play_dual_rumble(
|
||||
device, 0, kRumbleDurationMs,
|
||||
device, 0, stop ? 0 : kRumbleDurationMs,
|
||||
envelope.rumble.high_frequency_magnitude,
|
||||
envelope.rumble.low_frequency_magnitude);
|
||||
}
|
||||
|
|
|
|||
BIN
firmware/switch-pico-adapter-feasibility.uf2
Normal file
BIN
firmware/switch-pico-adapter-feasibility.uf2
Normal file
Binary file not shown.
|
|
@ -1090,8 +1090,10 @@ uint8_t const* tud_descriptor_device_cb(void) {
|
|||
if (adapter_host_probe_mode() == AdapterUsbMode::kXInput) {
|
||||
return XInputFeasibility::kDeviceDescriptor;
|
||||
}
|
||||
#endif
|
||||
return XInputFeasibility::kSwitchProbeDeviceDescriptor;
|
||||
#else
|
||||
return switch_pro_device_descriptor;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
|
||||
|
|
|
|||
|
|
@ -575,10 +575,18 @@ void test_independent_lifecycle() {
|
|||
for (int slot = 0; slot < kSlotCount; ++slot) {
|
||||
require(devices[slot].rumble_calls == 1 &&
|
||||
devices[slot].last_low == 11 + slot &&
|
||||
devices[slot].last_high == 21 + slot,
|
||||
devices[slot].last_high == 21 + slot &&
|
||||
devices[slot].last_rumble_duration_ms ==
|
||||
kRumbleDurationMs,
|
||||
"each slot rumble must reach only its indexed controller");
|
||||
}
|
||||
|
||||
bluepad32_input_backend_queue_rumble(0, SwitchRumbleOutput{0, 0});
|
||||
process_rumble_timer(&g_rumble_timer);
|
||||
require(devices[0].rumble_calls == 2 &&
|
||||
devices[0].last_rumble_duration_ms == 0,
|
||||
"zero XInput magnitude must stop rumble immediately");
|
||||
|
||||
bluepad32_input_backend_queue_rumble(3, SwitchRumbleOutput{55, 66});
|
||||
const uint32_t disconnected_generation =
|
||||
g_slots[3].connection_generation;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
|||
compiler = shutil.which("c++") or shutil.which("g++")
|
||||
assert compiler is not None, "a host C++ compiler is required"
|
||||
|
||||
executable = tmp_path / "bluepad32_backend_lifecycle_test"
|
||||
subprocess.run(
|
||||
[
|
||||
for adapter_feasibility in (False, True):
|
||||
suffix = "_adapter" if adapter_feasibility else ""
|
||||
executable = tmp_path / f"bluepad32_backend_lifecycle_test{suffix}"
|
||||
command = [
|
||||
compiler,
|
||||
"-std=c++17",
|
||||
"-Wall",
|
||||
|
|
@ -20,27 +21,31 @@ def test_bluepad32_backend_lifecycle_native(tmp_path: Path) -> None:
|
|||
"-Werror",
|
||||
"-pedantic",
|
||||
"-DSWITCH_PICO_HID_INSTANCE_COUNT=4",
|
||||
f"-I{root / 'tests' / 'bluepad32_native_stubs'}",
|
||||
f"-I{root}",
|
||||
str(root / "tests" / "bluepad32_backend_lifecycle_test.cpp"),
|
||||
"-o",
|
||||
str(executable),
|
||||
],
|
||||
check=True,
|
||||
cwd=root,
|
||||
)
|
||||
]
|
||||
if adapter_feasibility:
|
||||
command.append("-DSWITCH_PICO_ADAPTER_FEASIBILITY=1")
|
||||
command.extend(
|
||||
[
|
||||
f"-I{root / 'tests' / 'bluepad32_native_stubs'}",
|
||||
f"-I{root}",
|
||||
str(root / "tests" / "bluepad32_backend_lifecycle_test.cpp"),
|
||||
"-o",
|
||||
str(executable),
|
||||
]
|
||||
)
|
||||
subprocess.run(command, check=True, cwd=root)
|
||||
|
||||
for scenario in (
|
||||
"ready-forward",
|
||||
"ready-reverse",
|
||||
"rejections",
|
||||
"lifecycle",
|
||||
"pairing-policy",
|
||||
"slot-lighting",
|
||||
"abxy-hotkey",
|
||||
"motion-hotkey",
|
||||
"clear-pairings",
|
||||
"flash-core-start",
|
||||
"flash-core-failure",
|
||||
):
|
||||
subprocess.run([str(executable), scenario], check=True, cwd=root)
|
||||
for scenario in (
|
||||
"ready-forward",
|
||||
"ready-reverse",
|
||||
"rejections",
|
||||
"lifecycle",
|
||||
"pairing-policy",
|
||||
"slot-lighting",
|
||||
"abxy-hotkey",
|
||||
"motion-hotkey",
|
||||
"clear-pairings",
|
||||
"flash-core-start",
|
||||
"flash-core-failure",
|
||||
):
|
||||
subprocess.run([str(executable), scenario], check=True, cwd=root)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ def test_bluepad32_imu_normalization_native(tmp_path: Path) -> None:
|
|||
"-Wextra",
|
||||
"-Werror",
|
||||
"-pedantic",
|
||||
f"-I{root / 'bluepad32_config'}",
|
||||
f"-I{root / 'external' / 'bluepad32' / 'src' / 'components' / 'bluepad32' / 'include'}",
|
||||
str(root / "tests" / "bluepad32_imu_normalization_test.cpp"),
|
||||
"-o",
|
||||
|
|
|
|||
|
|
@ -32,10 +32,27 @@ uint32_t read_le32(const uint8_t *data) {
|
|||
|
||||
void test_device_and_configuration_descriptors() {
|
||||
using namespace XInputFeasibility;
|
||||
expect(read_le16(&kSwitchProbeDeviceDescriptor[8]) ==
|
||||
kSwitchProbeVendorId,
|
||||
"Switch probe VID mismatch");
|
||||
expect(read_le16(&kSwitchProbeDeviceDescriptor[10]) ==
|
||||
kSwitchProbeProductId,
|
||||
"Switch probe PID mismatch");
|
||||
expect(read_le16(&kSwitchProbeDeviceDescriptor[12]) ==
|
||||
kSwitchProbeDeviceRevision,
|
||||
"Switch probe revision mismatch");
|
||||
expect(kSwitchProbeDeviceRevision != 0x0210,
|
||||
"Switch probe reuses the genuine controller cache identity");
|
||||
expect(read_le16(&kDeviceDescriptor[8]) == kPrototypeVendorId,
|
||||
"prototype VID mismatch");
|
||||
expect(read_le16(&kDeviceDescriptor[10]) == kPrototypeProductId,
|
||||
"prototype PID mismatch");
|
||||
expect(read_le16(&kDeviceDescriptor[12]) ==
|
||||
kPrototypeDeviceRevision,
|
||||
"prototype revision mismatch");
|
||||
expect(kDeviceDescriptor[4] == 0 && kDeviceDescriptor[5] == 0 &&
|
||||
kDeviceDescriptor[6] == 0,
|
||||
"multi-interface prototype is not a composite USB device");
|
||||
expect(kPrototypeVendorId != 0x045e,
|
||||
"prototype must not impersonate Microsoft's VID");
|
||||
expect(read_le16(&kConfigurationDescriptor[2]) ==
|
||||
|
|
|
|||
|
|
@ -87,10 +87,10 @@ function Get-UsbIdentityDevices {
|
|||
[Parameter(Mandatory = $true)]
|
||||
[string]$Vid,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Pid
|
||||
[string]$ProductId
|
||||
)
|
||||
|
||||
$pattern = "VID_{0}&PID_{1}" -f $Vid.ToUpperInvariant(), $Pid.ToUpperInvariant()
|
||||
$pattern = "VID_{0}&PID_{1}" -f $Vid.ToUpperInvariant(), $ProductId.ToUpperInvariant()
|
||||
if (Get-Command Get-PnpDevice -ErrorAction SilentlyContinue) {
|
||||
return @(Get-PnpDevice -PresentOnly -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.InstanceId -like "*$pattern*" })
|
||||
|
|
@ -165,7 +165,7 @@ Write-Host "Switch Pico Windows XInput feasibility test" -ForegroundColor Cyan
|
|||
Write-Host "Prototype identities: Switch probe 057E:2009 -> XInput CAFE:4010"
|
||||
Write-Host "Disconnect other XInput controllers before continuing."
|
||||
|
||||
$baseline = Get-ConnectedXInputStates
|
||||
$baseline = @(Get-ConnectedXInputStates)
|
||||
Add-Result -Name "Clean XInput baseline" -Passed ($baseline.Count -eq 0) `
|
||||
-Detail ("{0} XInput controller(s) connected before the Pico" -f $baseline.Count)
|
||||
|
||||
|
|
@ -176,8 +176,8 @@ if (-not $SkipInteractive) {
|
|||
|
||||
$disconnectDeadline = (Get-Date).AddSeconds(10)
|
||||
do {
|
||||
$switchPresent = (Get-UsbIdentityDevices -Vid "057E" -Pid "2009").Count -gt 0
|
||||
$xinputPresent = (Get-UsbIdentityDevices -Vid "CAFE" -Pid "4010").Count -gt 0
|
||||
$switchPresent = @(Get-UsbIdentityDevices -Vid "057E" -ProductId "2009").Count -gt 0
|
||||
$xinputPresent = @(Get-UsbIdentityDevices -Vid "CAFE" -ProductId "4010").Count -gt 0
|
||||
if (-not $switchPresent -and -not $xinputPresent) { break }
|
||||
Start-Sleep -Milliseconds 100
|
||||
} while ((Get-Date) -lt $disconnectDeadline)
|
||||
|
|
@ -199,13 +199,13 @@ $switchSeenAt = $null
|
|||
$xinputSeenAt = $null
|
||||
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
if (-not $seenSwitch -and (Get-UsbIdentityDevices -Vid "057E" -Pid "2009").Count -gt 0) {
|
||||
if (-not $seenSwitch -and @(Get-UsbIdentityDevices -Vid "057E" -ProductId "2009").Count -gt 0) {
|
||||
$seenSwitch = $true
|
||||
$switchSeenAt = (Get-Date)
|
||||
Write-Host "Observed Switch probe identity 057E:2009" -ForegroundColor DarkCyan
|
||||
}
|
||||
|
||||
if ((Get-UsbIdentityDevices -Vid "CAFE" -Pid "4010").Count -gt 0) {
|
||||
if (@(Get-UsbIdentityDevices -Vid "CAFE" -ProductId "4010").Count -gt 0) {
|
||||
$seenXInput = $true
|
||||
$xinputSeenAt = (Get-Date)
|
||||
Write-Host "Observed XInput identity CAFE:4010" -ForegroundColor DarkCyan
|
||||
|
|
@ -226,7 +226,7 @@ if ($SkipInteractive -and $seenXInput) {
|
|||
-Detail ("SwitchSeen={0}, XInputSeen={1}, transition={2} ms" -f $seenSwitch, $seenXInput, $transitionMs)
|
||||
}
|
||||
|
||||
$xinputPnp = @(Get-UsbIdentityDevices -Vid "CAFE" -Pid "4010")
|
||||
$xinputPnp = @(Get-UsbIdentityDevices -Vid "CAFE" -ProductId "4010")
|
||||
Add-Result -Name "XInput PnP identity" -Passed ($xinputPnp.Count -gt 0) `
|
||||
-Detail ("Found {0} present PnP node(s) for CAFE:4010" -f $xinputPnp.Count)
|
||||
|
||||
|
|
@ -237,7 +237,7 @@ Add-Result -Name "PnP device health" -Passed ($problemDevices.Count -eq 0) `
|
|||
-Detail ("{0} unhealthy PnP node(s)" -f $problemDevices.Count)
|
||||
|
||||
Start-Sleep -Seconds 2
|
||||
$connectedStates = Get-ConnectedXInputStates
|
||||
$connectedStates = @(Get-ConnectedXInputStates)
|
||||
Add-Result -Name "Four XInput API slots" -Passed ($connectedStates.Count -eq 4) `
|
||||
-Detail ("XInputGetState reports {0}/4 connected slots" -f $connectedStates.Count)
|
||||
|
||||
|
|
@ -344,7 +344,11 @@ $report = [ordered]@{
|
|||
Results = @($script:Results)
|
||||
}
|
||||
|
||||
$report | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $OutputPath -Encoding UTF8
|
||||
$reportJson = $report | ConvertTo-Json -Depth 8
|
||||
[System.IO.File]::WriteAllText(
|
||||
$OutputPath,
|
||||
$reportJson,
|
||||
[System.Text.UTF8Encoding]::new($false))
|
||||
Write-Host ""
|
||||
Write-Host ("Wrote report: {0}" -f (Resolve-Path $OutputPath)) -ForegroundColor Cyan
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace XInputFeasibility {
|
|||
|
||||
constexpr uint16_t kPrototypeVendorId = 0xcafe;
|
||||
constexpr uint16_t kPrototypeProductId = 0x4010;
|
||||
constexpr uint16_t kPrototypeDeviceRevision = 0x0101;
|
||||
constexpr uint8_t kInterfaceDescriptorSize = 39;
|
||||
constexpr uint16_t kConfigurationDescriptorSize =
|
||||
9 + SWITCH_PICO_HID_INSTANCE_COUNT * kInterfaceDescriptorSize;
|
||||
|
|
@ -23,21 +24,49 @@ constexpr uint16_t kMsCompatIdDescriptorSize =
|
|||
constexpr uint8_t kMsVendorRequest = 0x20;
|
||||
constexpr uint16_t kMsCompatIdIndex = 0x0004;
|
||||
|
||||
constexpr uint16_t kSwitchProbeVendorId = 0x057e;
|
||||
constexpr uint16_t kSwitchProbeProductId = 0x2009;
|
||||
// Windows caches Microsoft OS descriptor support by VID, PID, and bcdDevice.
|
||||
// Use a revision distinct from genuine Pro Controllers so their cached
|
||||
// "unsupported" result cannot suppress this probe's 0xEE request.
|
||||
constexpr uint16_t kSwitchProbeDeviceRevision = 0x0211;
|
||||
|
||||
static const uint8_t kSwitchProbeDeviceDescriptor[] = {
|
||||
0x12,
|
||||
0x01, // Device descriptor
|
||||
0x00,
|
||||
0x02, // USB 2.0
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // Class information comes from the interface descriptor
|
||||
0x40, // Endpoint zero packet size
|
||||
static_cast<uint8_t>(kSwitchProbeVendorId & 0xff),
|
||||
static_cast<uint8_t>(kSwitchProbeVendorId >> 8),
|
||||
static_cast<uint8_t>(kSwitchProbeProductId & 0xff),
|
||||
static_cast<uint8_t>(kSwitchProbeProductId >> 8),
|
||||
static_cast<uint8_t>(kSwitchProbeDeviceRevision & 0xff),
|
||||
static_cast<uint8_t>(kSwitchProbeDeviceRevision >> 8),
|
||||
0x01,
|
||||
0x02,
|
||||
0x03, // Manufacturer, product, serial strings
|
||||
0x01, // One configuration
|
||||
};
|
||||
|
||||
static const uint8_t kDeviceDescriptor[] = {
|
||||
0x12,
|
||||
0x01, // Device descriptor
|
||||
0x00,
|
||||
0x02, // USB 2.0
|
||||
0xff,
|
||||
0xff,
|
||||
0xff, // Vendor-specific device
|
||||
0x00,
|
||||
0x00,
|
||||
0x00, // Composite device; each interface binds to the XUSB driver
|
||||
0x40, // Endpoint zero packet size
|
||||
static_cast<uint8_t>(kPrototypeVendorId & 0xff),
|
||||
static_cast<uint8_t>(kPrototypeVendorId >> 8),
|
||||
static_cast<uint8_t>(kPrototypeProductId & 0xff),
|
||||
static_cast<uint8_t>(kPrototypeProductId >> 8),
|
||||
0x00,
|
||||
0x01, // Prototype revision 1.00
|
||||
static_cast<uint8_t>(kPrototypeDeviceRevision & 0xff),
|
||||
static_cast<uint8_t>(kPrototypeDeviceRevision >> 8),
|
||||
0x01,
|
||||
0x02,
|
||||
0x03, // Manufacturer, product, serial strings
|
||||
|
|
@ -116,6 +145,7 @@ static const uint8_t kProbeMsCompatIdDescriptor[] = {
|
|||
};
|
||||
|
||||
static_assert(sizeof(kDeviceDescriptor) == 18);
|
||||
static_assert(sizeof(kSwitchProbeDeviceDescriptor) == 18);
|
||||
static_assert(sizeof(kConfigurationDescriptor) == kConfigurationDescriptorSize);
|
||||
static_assert(sizeof(kMsCompatIdDescriptor) == kMsCompatIdDescriptorSize);
|
||||
static_assert(sizeof(kProbeMsCompatIdDescriptor) == 16);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue