Add stock-USB native Joy-Con R/L hub bridge
This commit is contained in:
parent
9f6dddb790
commit
1748910316
41 changed files with 7101 additions and 909 deletions
|
|
@ -1,21 +1,71 @@
|
|||
#include "bootsel.h"
|
||||
|
||||
#include "model.h"
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#include <string.h>
|
||||
#include "pico/bootrom.h"
|
||||
#include "usb/native_hub/native_hub.h"
|
||||
#else
|
||||
#include "adapter/adapter_mode_controller.h"
|
||||
#endif
|
||||
#include "usb/usb_configuration_management.h"
|
||||
|
||||
namespace {
|
||||
bool bootsel_accepted;
|
||||
#if SWITCH2_PROBE_HUB
|
||||
constexpr uint32_t kBootselRebootDelayMs = 50;
|
||||
struct BootselTransfer {
|
||||
uint8_t envelope[UsbConfigurationManagement::kRequestHeaderSize];
|
||||
bool pending;
|
||||
bool validated;
|
||||
};
|
||||
// Control state is independent even when the two children enumerate together.
|
||||
BootselTransfer bootsel_transfers[PROBE_CONTROLLER_COUNT + 1];
|
||||
bool bootsel_delay_started;
|
||||
uint32_t bootsel_deadline_ms;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool probe_bootsel_vendor_control(uint8_t rhport, uint8_t stage,
|
||||
const tusb_control_request_t* request) {
|
||||
using namespace UsbConfigurationManagement;
|
||||
#if SWITCH2_PROBE_HUB
|
||||
if (rhport > PROBE_CONTROLLER_COUNT) return false;
|
||||
BootselTransfer& transfer = bootsel_transfers[rhport];
|
||||
if (stage == CONTROL_STAGE_SETUP) {
|
||||
transfer.pending = false;
|
||||
transfer.validated = false;
|
||||
}
|
||||
#endif
|
||||
if (request == nullptr || request->bmRequestType != 0x40 ||
|
||||
request->bRequest != static_cast<uint8_t>(Operation::kBootselReboot) ||
|
||||
request->wValue != kRequestValue || request->wIndex != kRequestIndex ||
|
||||
request->wLength != kRequestHeaderSize) {
|
||||
return false;
|
||||
}
|
||||
#if SWITCH2_PROBE_HUB
|
||||
if (stage == CONTROL_STAGE_SETUP) {
|
||||
// Any short OUT leaves nonzero reserved/CRC bytes and fails decoding.
|
||||
memset(transfer.envelope, 0xff, sizeof(transfer.envelope));
|
||||
transfer.pending = native_hub_control_xfer(
|
||||
rhport, request, transfer.envelope, sizeof(transfer.envelope));
|
||||
return transfer.pending;
|
||||
}
|
||||
if (stage == CONTROL_STAGE_DATA) {
|
||||
DecodedRequest decoded{};
|
||||
transfer.validated = transfer.pending &&
|
||||
decode_request(Operation::kBootselReboot, transfer.envelope,
|
||||
sizeof(transfer.envelope), &decoded) &&
|
||||
decoded.payload_size == 0;
|
||||
return transfer.validated;
|
||||
}
|
||||
if (stage == CONTROL_STAGE_ACK && transfer.pending && transfer.validated) {
|
||||
transfer.pending = false;
|
||||
bootsel_accepted = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
#else
|
||||
// The shared handler receives the envelope at SETUP and validates and
|
||||
// dispatches it only at ACK, after the host's control transfer completes.
|
||||
const bool accepted =
|
||||
|
|
@ -24,12 +74,24 @@ bool probe_bootsel_vendor_control(uint8_t rhport, uint8_t stage,
|
|||
bootsel_accepted = true;
|
||||
}
|
||||
return accepted;
|
||||
#endif
|
||||
}
|
||||
|
||||
void probe_bootsel_task(uint32_t now_ms) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
if (!bootsel_accepted) return;
|
||||
if (!bootsel_delay_started) {
|
||||
bootsel_delay_started = true;
|
||||
bootsel_deadline_ms = now_ms + kBootselRebootDelayMs;
|
||||
} else if (static_cast<int32_t>(now_ms - bootsel_deadline_ms) >= 0) {
|
||||
bootsel_accepted = false;
|
||||
reset_usb_boot(0, 0);
|
||||
}
|
||||
#else
|
||||
// The native bridge does not initialize ordinary adapter-mode selection.
|
||||
// A successful BOOTSEL dispatch guarantees the task takes its reboot path.
|
||||
if (bootsel_accepted) {
|
||||
adapter_mode_controller_task(now_ms);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "controller_input.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -8,6 +9,10 @@
|
|||
#include "platform/pico/system_clock.h"
|
||||
#include "profile/controller_profile_runtime.h"
|
||||
#include "pico/stdlib.h"
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#include <inttypes.h>
|
||||
extern "C" int probe_debug_printf(const char* format, ...);
|
||||
#endif
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
#include <math.h>
|
||||
#include "input/wii_ir_pointer.h"
|
||||
|
|
@ -25,17 +30,25 @@ extern "C" int probe_debug_printf(const char* format, ...);
|
|||
namespace {
|
||||
constexpr uint8_t kSourceAddress[] = {SWITCH2_BRIDGE_SOURCE_ADDRESS_BYTES};
|
||||
static_assert(sizeof(kSourceAddress) == 6, "Select one physical Bluetooth address");
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
constexpr uint8_t kSecondSourceAddress[] = {SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS_BYTES};
|
||||
static_assert(sizeof(kSecondSourceAddress) == 6, "Select the second physical Bluetooth address");
|
||||
#endif
|
||||
constexpr uint32_t kInputDeadlineMs = 500;
|
||||
#if !SWITCH2_PROBE_HUB
|
||||
constexpr uint32_t kFlashCoordinationTimeoutMs = 1000;
|
||||
// The backend publishes stage 2 only after Core 1's flash-safe registration;
|
||||
// reaching Core 1 already required successful Core 0 registration in start().
|
||||
#endif
|
||||
// Stage 2 publishes flash safety: both cores registered in dedicated-radio
|
||||
// modes, or Core 0 registered with an SRAM-only/IRQ-disabled Core 1 in hub mode.
|
||||
constexpr uint32_t kFlashCoordinationStage = 2;
|
||||
bool g_initialized;
|
||||
bool g_start_attempted;
|
||||
bool g_flash_ready;
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
probe_controller_input g_input;
|
||||
#if !SWITCH2_BRIDGE_WII_INPUT
|
||||
uint32_t g_received_ms;
|
||||
#else
|
||||
probe_controller_input g_inputs[PROBE_CONTROLLER_COUNT];
|
||||
uint32_t g_received_times[PROBE_CONTROLLER_COUNT];
|
||||
#endif
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
#ifndef SWITCH2_WII_IR_SCREEN_CONFIG
|
||||
|
|
@ -368,8 +381,13 @@ extern "C" void probe_controller_input_init(void) {
|
|||
if (!g_screen_configured) probe_debug_printf("[PROBE] Invalid native IR viewport configuration\n");
|
||||
wii_ir_mouse_set_output_enabled(false);
|
||||
#else
|
||||
static_assert(SWITCH2_MOUSE_CAPTURE_SOURCE_COUNT == PROBE_CONTROLLER_COUNT,
|
||||
"Each native controller requires an independent capture channel");
|
||||
switch2_mouse_capture_init();
|
||||
switch2_mouse_capture_select_input(kSourceAddress);
|
||||
switch2_mouse_capture_select_input(0, kSourceAddress, probe_model_pid(0));
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
switch2_mouse_capture_select_input(1, kSecondSourceAddress, probe_model_pid(1));
|
||||
#endif
|
||||
bluepad32_input_backend_init();
|
||||
#endif
|
||||
controller_profile_runtime_reset();
|
||||
|
|
@ -384,6 +402,14 @@ extern "C" bool probe_controller_input_start(void) {
|
|||
#endif
|
||||
g_start_attempted = true;
|
||||
bluepad32_input_backend_start();
|
||||
#if SWITCH2_PROBE_HUB
|
||||
// Initialization is synchronous on Core 0; there is no radio Core 1 to
|
||||
// wait for. The SDK async context advances radio startup in task().
|
||||
Bluepad32BackendDiagnostics diagnostics;
|
||||
bluepad32_input_backend_diagnostics(&diagnostics);
|
||||
g_flash_ready = diagnostics.initialization_stage >= kFlashCoordinationStage;
|
||||
return g_flash_ready;
|
||||
#else
|
||||
const absolute_time_t deadline = make_timeout_time_ms(kFlashCoordinationTimeoutMs);
|
||||
do {
|
||||
Bluepad32BackendDiagnostics diagnostics;
|
||||
|
|
@ -397,6 +423,25 @@ extern "C" bool probe_controller_input_start(void) {
|
|||
// Do not reset Core 1 or retry a partially launched backend. It may still
|
||||
// be running; a false return keeps USB and its flash writes fail-closed.
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" void probe_controller_input_task(void) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
if (!g_flash_ready) return;
|
||||
bluepad32_input_backend_poll();
|
||||
static uint32_t last_diagnostics;
|
||||
const uint32_t now = to_ms_since_boot(get_absolute_time());
|
||||
if ((uint32_t)(now - last_diagnostics) >= 1000u) {
|
||||
last_diagnostics = now;
|
||||
Bluepad32BackendDiagnostics diagnostics;
|
||||
bluepad32_input_backend_diagnostics(&diagnostics);
|
||||
probe_debug_printf("[HUB_RADIO] stage=%" PRIu32 " timers=%" PRIu32 "/%" PRIu32
|
||||
" reports=%" PRIu32 "\n", diagnostics.initialization_stage,
|
||||
diagnostics.rumble_timer_ticks, diagnostics.configuration_timer_ticks,
|
||||
diagnostics.controller_reports);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" bool probe_controller_input_pairing_task(void) {
|
||||
|
|
@ -426,30 +471,31 @@ extern "C" void probe_controller_input_set_native_features(uint8_t features) {
|
|||
}
|
||||
#endif
|
||||
|
||||
extern "C" void probe_controller_input_set_native_stream(bool enabled) {
|
||||
extern "C" void probe_controller_input_set_native_stream(uint8_t instance, bool enabled) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) return;
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
enabled = enabled && g_flash_ready;
|
||||
if (g_native_stream != enabled || !enabled) discard_wii_output();
|
||||
g_native_stream = enabled;
|
||||
update_wii_ir_gate(time_us_32());
|
||||
#else
|
||||
switch2_mouse_capture_set_native_stream(g_flash_ready && enabled);
|
||||
switch2_mouse_capture_set_native_stream(instance, g_flash_ready && enabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" uint32_t probe_controller_input_peek_native_report(
|
||||
uint32_t now_ms, uint8_t report[63]) {
|
||||
if (!g_flash_ready) return 0;
|
||||
uint8_t instance, uint32_t now_ms, uint8_t report[63]) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) return 0;
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
(void)now_ms;
|
||||
return prepare_wii_report(report);
|
||||
#else
|
||||
return switch2_mouse_capture_peek_native_report(now_ms, report);
|
||||
return switch2_mouse_capture_peek_native_report(instance, now_ms, report);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" bool probe_controller_input_commit_native_report(uint32_t serial) {
|
||||
if (!g_flash_ready) return false;
|
||||
extern "C" bool probe_controller_input_commit_native_report(uint8_t instance, uint32_t serial) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) return false;
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
if (!g_native_stream || !serial || serial != g_pending_serial ||
|
||||
g_pending_generation != g_wii_generation || !g_wii_active) return false;
|
||||
|
|
@ -462,12 +508,12 @@ extern "C" bool probe_controller_input_commit_native_report(uint32_t serial) {
|
|||
++g_report_counter;
|
||||
return true;
|
||||
#else
|
||||
return switch2_mouse_capture_commit_native_report(serial);
|
||||
return switch2_mouse_capture_commit_native_report(instance, serial);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" bool probe_controller_input_play_sample(uint8_t sample_id, uint64_t* token) {
|
||||
if (!g_flash_ready) {
|
||||
extern "C" bool probe_controller_input_play_sample(uint8_t instance, uint8_t sample_id, uint64_t* token) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) {
|
||||
if (token != nullptr) *token = 0;
|
||||
return false;
|
||||
}
|
||||
|
|
@ -475,40 +521,43 @@ extern "C" bool probe_controller_input_play_sample(uint8_t sample_id, uint64_t*
|
|||
return bluepad32_input_backend_wii_sample_request(sample_id, token);
|
||||
#else
|
||||
return switch2_mouse_capture_request_sample(
|
||||
sample_id, to_ms_since_boot(get_absolute_time()), token);
|
||||
instance, sample_id, to_ms_since_boot(get_absolute_time()), token);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" int probe_controller_input_sample_result(uint64_t token, uint32_t now_ms) {
|
||||
if (!g_flash_ready) return -1;
|
||||
extern "C" int probe_controller_input_sample_result(uint8_t instance, uint64_t token, uint32_t now_ms) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) return -1;
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
(void)now_ms;
|
||||
return bluepad32_input_backend_wii_sample_result(token);
|
||||
#else
|
||||
return switch2_mouse_capture_sample_result(token, now_ms);
|
||||
return switch2_mouse_capture_sample_result(instance, token, now_ms);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" void probe_controller_input_cancel_sample(void) {
|
||||
extern "C" void probe_controller_input_cancel_sample(uint8_t instance) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) return;
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
bluepad32_input_backend_wii_sample_cancel();
|
||||
#else
|
||||
switch2_mouse_capture_cancel_sample();
|
||||
switch2_mouse_capture_cancel_sample(instance);
|
||||
#endif
|
||||
}
|
||||
|
||||
extern "C" void probe_controller_input_poll(uint32_t now_ms,
|
||||
extern "C" void probe_controller_input_poll(uint8_t instance, uint32_t now_ms,
|
||||
probe_controller_input* out) {
|
||||
if (out == nullptr) return;
|
||||
if (!g_flash_ready) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !g_flash_ready) {
|
||||
*out = {};
|
||||
return;
|
||||
}
|
||||
#if SWITCH2_BRIDGE_WII_INPUT
|
||||
poll_wii_source(now_ms);
|
||||
#else
|
||||
probe_controller_input& g_input = g_inputs[instance];
|
||||
uint32_t& g_received_ms = g_received_times[instance];
|
||||
Switch2MouseCaptureInput sample;
|
||||
if (switch2_mouse_capture_latest_input(g_input.serial, &sample)) {
|
||||
if (switch2_mouse_capture_latest_input(instance, g_input.serial, &sample)) {
|
||||
g_input.serial = sample.serial;
|
||||
g_input.active = sample.active;
|
||||
g_received_ms = sample.received_ms;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ typedef struct {
|
|||
uint32_t serial;
|
||||
uint8_t buttons[2];
|
||||
uint8_t stick[3];
|
||||
// Latest opaque native 08 byte 8.
|
||||
// Latest opaque native 07/08 byte 8.
|
||||
uint8_t native_status;
|
||||
// Cumulative signed relative totals within mouse_epoch, not per-poll
|
||||
// deltas. Cached polls repeat these totals without consuming motion.
|
||||
|
|
@ -20,7 +20,7 @@ typedef struct {
|
|||
uint32_t mouse_epoch;
|
||||
int64_t mouse_total_x;
|
||||
int64_t mouse_total_y;
|
||||
// Latest opaque native 08 byte 13.
|
||||
// Latest opaque native 07/08 byte 13.
|
||||
uint8_t mouse_surface;
|
||||
} probe_controller_input;
|
||||
|
||||
|
|
@ -28,10 +28,15 @@ typedef struct {
|
|||
void probe_controller_input_clock_init(void);
|
||||
// Core 0, after stdio and before protocol reset or USB startup.
|
||||
void probe_controller_input_init(void);
|
||||
// True means both cores are registered for flash coordination, not that the
|
||||
// radio is ready or a controller is connected. Failure is latched: keep USB
|
||||
// and flash-writing protocol operations disabled rather than retrying startup.
|
||||
// True means flash coordination is ready, not that the radio is ready or a
|
||||
// controller is connected. Hub mode initializes on Core 0 with Core 1 reserved
|
||||
// for SRAM-only USB; other modes register both cores and launch the radio there.
|
||||
// Failure is latched: keep USB and flash-writing protocol operations disabled.
|
||||
bool probe_controller_input_start(void);
|
||||
// Core 0 main loop before USB tasks, outside IRQs and application state locks.
|
||||
// Hub mode cooperatively services CYW43/BTstack, including storage and haptics;
|
||||
// a no-op before successful start and in dedicated-radio modes.
|
||||
void probe_controller_input_task(void);
|
||||
// Core 0 after start(): polls the existing two-second BOOTSEL hold gesture.
|
||||
// True means a Bluetooth pairing-window request was queued. Long holds NEVER
|
||||
// clear pairings in this bridge, and this does not inject USB controller input.
|
||||
|
|
@ -42,29 +47,30 @@ void probe_controller_input_set_stick_calibration(const uint8_t calibration[9]);
|
|||
// Native feature changes are output barriers, not Bluetooth/IMU resets.
|
||||
void probe_controller_input_set_native_features(uint8_t features);
|
||||
#endif
|
||||
// Core0 native08 output. Disable discards queued/prepared data; repeated enable
|
||||
// preserves it. Joy-Con mode relays its bounded FIFO; Wii mode synthesizes from
|
||||
// fresh calibrated sensors and the selected IR pointer. No pairing changes.
|
||||
void probe_controller_input_set_native_stream(bool enabled);
|
||||
// Core0 native07/08 output. Disable discards queued/prepared data; repeated
|
||||
// enable preserves it. Joy-Con mode relays its bounded FIFO; right-only Wii
|
||||
// mode synthesizes fresh calibrated sensors and the selected IR pointer.
|
||||
// No pairing changes.
|
||||
void probe_controller_input_set_native_stream(uint8_t instance, bool enabled);
|
||||
// Copy one63-byte payload without report ID. Returns a boot-unique token, or0
|
||||
// without changing output. Nondestructive until successful HID submission and
|
||||
// commit. now_ms uses the Pico boot-ms clock; unavailable/stale input is rejected.
|
||||
uint32_t probe_controller_input_peek_native_report(uint32_t now_ms, uint8_t report[63]);
|
||||
uint32_t probe_controller_input_peek_native_report(uint8_t instance, uint32_t now_ms, uint8_t report[63]);
|
||||
// Remove only the exact current head once. A stale/replaced token cannot pop a
|
||||
// new stream's packet. Before flash-ready startup peek/commit return 0/false.
|
||||
bool probe_controller_input_commit_native_report(uint32_t serial);
|
||||
bool probe_controller_input_commit_native_report(uint8_t instance, uint32_t serial);
|
||||
// Built-in vibration samples only; raw HD-rumble output is not forwarded.
|
||||
// A nonzero token means queued, not completed. Result:0 pending,1 completion,
|
||||
// -1 failed/stale. Joy-Con completion is its application ACK; Wii completion is
|
||||
// actual bounded rumble-driver dispatch (not an HD-waveform fidelity claim).
|
||||
// Reset cancels the request, never stored pairing.
|
||||
bool probe_controller_input_play_sample(uint8_t sample_id, uint64_t* token);
|
||||
int probe_controller_input_sample_result(uint64_t token, uint32_t now_ms);
|
||||
void probe_controller_input_cancel_sample(void);
|
||||
bool probe_controller_input_play_sample(uint8_t instance, uint8_t sample_id, uint64_t* token);
|
||||
int probe_controller_input_sample_result(uint8_t instance, uint64_t token, uint32_t now_ms);
|
||||
void probe_controller_input_cancel_sample(uint8_t instance);
|
||||
// Core0 at250Hz; now_ms uses Pico boot milliseconds. Supplies current mapped
|
||||
// controls for diagnostic reports; the native sender owns motion consumption.
|
||||
// Inactive controls are zero except serial; USB supplies its calibrated center.
|
||||
void probe_controller_input_poll(uint32_t now_ms, probe_controller_input* out);
|
||||
void probe_controller_input_poll(uint8_t instance, uint32_t now_ms, probe_controller_input* out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,66 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "model.h"
|
||||
|
||||
// Published Joy-Con 2 (R) USB descriptors, reproduced for enumeration capture.
|
||||
// Published Joy-Con 2 USB descriptors, reproduced for the selected model.
|
||||
// https://github.com/ndeadly/switch2_controller_research/blob/master/descriptors.md
|
||||
|
||||
static const uint8_t probe_device_descriptor[] = {
|
||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x7e, 0x05, 0x66, 0x20,
|
||||
0x00, 0x01, 0x01, 0x02, 0x03, 0x01,
|
||||
};
|
||||
// Composite retains the primary right PID (0x2066): USB has one device identity,
|
||||
// not a separate device PID for each left/right function.
|
||||
#define PROBE_DEVICE_DESCRIPTOR(pid) { \
|
||||
0x12, 0x01, 0x00, 0x02, 0xef, 0x02, 0x01, 0x40, 0x7e, 0x05, \
|
||||
(pid) & 0xff, (pid) >> 8, \
|
||||
0x00, 0x01, 0x01, 0x02, 0x03, 0x01, \
|
||||
}
|
||||
static const uint8_t probe_device_descriptor[] = PROBE_DEVICE_DESCRIPTOR(PROBE_JOYCON_PID);
|
||||
#if SWITCH2_PROBE_HUB
|
||||
static const uint8_t probe_left_device_descriptor[] = PROBE_DEVICE_DESCRIPTOR(0x2067u);
|
||||
#endif
|
||||
#undef PROBE_DEVICE_DESCRIPTOR
|
||||
|
||||
static const uint8_t probe_configuration_descriptor[] = {
|
||||
0x09, 0x02, 0x50, 0x00, 0x02, 0x01, 0x04, 0xc0, 0xfa, 0x08, 0x0b, 0x00,
|
||||
#if SWITCH2_PROBE_COMPOSITE
|
||||
0x09, 0x02, 0x97, 0x00, 0x04, 0x01, 0x04, 0xc0, 0xfa,
|
||||
#else
|
||||
0x09, 0x02, 0x50, 0x00, 0x02, 0x01, 0x04, 0xc0, 0xfa,
|
||||
#endif
|
||||
0x08, 0x0b, 0x00,
|
||||
0x01, 0x03, 0x00, 0x00, 0x00, 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00,
|
||||
0x00, 0x05, 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x64, 0x00, 0x07,
|
||||
0x05, 0x81, 0x03, 0x40, 0x00, 0x04, 0x07, 0x05, 0x01, 0x03, 0x40, 0x00,
|
||||
0x04, 0x08, 0x0b, 0x01, 0x01, 0xff, 0x00, 0x00, 0x00, 0x09, 0x04, 0x01,
|
||||
0x00, 0x02, 0xff, 0x00, 0x00, 0x06, 0x07, 0x05, 0x02, 0x02, 0x40, 0x00,
|
||||
0x00, 0x07, 0x05, 0x82, 0x02, 0x40, 0x00, 0x00,
|
||||
#if SWITCH2_PROBE_COMPOSITE
|
||||
0x08, 0x0b, 0x02, 0x01, 0x03, 0x00, 0x00, 0x00,
|
||||
0x09, 0x04, 0x02, 0x00, 0x02, 0x03, 0x00, 0x00, 0x07,
|
||||
0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, 0x64, 0x00,
|
||||
0x07, 0x05, 0x83, 0x03, 0x40, 0x00, 0x04,
|
||||
0x07, 0x05, 0x03, 0x03, 0x40, 0x00, 0x04,
|
||||
0x08, 0x0b, 0x03, 0x01, 0xff, 0x00, 0x00, 0x00,
|
||||
0x09, 0x04, 0x03, 0x00, 0x02, 0xff, 0x00, 0x00, 0x08,
|
||||
0x07, 0x05, 0x04, 0x02, 0x40, 0x00, 0x00,
|
||||
0x07, 0x05, 0x84, 0x02, 0x40, 0x00, 0x00,
|
||||
#endif
|
||||
};
|
||||
|
||||
static const uint8_t probe_hid_report_descriptor[] = {
|
||||
0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x05, 0x05, 0xff, 0x09, 0x01,
|
||||
0x15, 0x00, 0x26, 0xff, 0x00, 0x95, 0x3f, 0x75, 0x08, 0x81, 0x02, 0x85,
|
||||
0x08, 0x09, 0x01, 0x95, 0x02, 0x81, 0x02, 0x05, 0x09, 0x19, 0x01, 0x29,
|
||||
0x10, 0x25, 0x01, 0x95, 0x10, 0x75, 0x01, 0x81, 0x02, 0x05, 0xff, 0x09,
|
||||
0x01, 0x26, 0xff, 0x00, 0x95, 0x01, 0x75, 0x08, 0x81, 0x02, 0x05, 0x01,
|
||||
0x09, 0x01, 0xa1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x26, 0xff, 0x0f, 0x95,
|
||||
0x02, 0x75, 0x0c, 0x81, 0x02, 0xc0, 0x05, 0xff, 0x09, 0x02, 0x26, 0xff,
|
||||
0x00, 0x95, 0x37, 0x75, 0x08, 0x81, 0x02, 0x85, 0x01, 0x09, 0x01, 0x95,
|
||||
0x3f, 0x91, 0x02, 0xc0,
|
||||
#define PROBE_HID_DESCRIPTOR(report_id) { \
|
||||
0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x05, 0x05, 0xff, 0x09, 0x01, \
|
||||
0x15, 0x00, 0x26, 0xff, 0x00, 0x95, 0x3f, 0x75, 0x08, 0x81, 0x02, 0x85, \
|
||||
report_id, 0x09, 0x01, 0x95, 0x02, 0x81, 0x02, 0x05, 0x09, 0x19, 0x01, 0x29, \
|
||||
0x10, 0x25, 0x01, 0x95, 0x10, 0x75, 0x01, 0x81, 0x02, 0x05, 0xff, 0x09, \
|
||||
0x01, 0x26, 0xff, 0x00, 0x95, 0x01, 0x75, 0x08, 0x81, 0x02, 0x05, 0x01, \
|
||||
0x09, 0x01, 0xa1, 0x00, 0x09, 0x30, 0x09, 0x31, 0x26, 0xff, 0x0f, 0x95, \
|
||||
0x02, 0x75, 0x0c, 0x81, 0x02, 0xc0, 0x05, 0xff, 0x09, 0x02, 0x26, 0xff, \
|
||||
0x00, 0x95, 0x37, 0x75, 0x08, 0x81, 0x02, 0x85, 0x01, 0x09, 0x01, 0x95, \
|
||||
0x3f, 0x91, 0x02, 0xc0, \
|
||||
}
|
||||
|
||||
static const uint8_t probe_hid_report_descriptors[PROBE_CONTROLLER_COUNT][100] = {
|
||||
PROBE_HID_DESCRIPTOR(PROBE_NATIVE_REPORT_ID),
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
PROBE_HID_DESCRIPTOR(0x07u),
|
||||
#endif
|
||||
};
|
||||
#undef PROBE_HID_DESCRIPTOR
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -2,21 +2,23 @@
|
|||
#include "probe_memory_data.h"
|
||||
#include <string.h>
|
||||
|
||||
_Static_assert(sizeof(probe_factory_memory) == 8192, "factory capture size");
|
||||
_Static_assert(sizeof(probe_user_calibration) == 4096, "user calibration capture size");
|
||||
_Static_assert(sizeof(probe_factory_memories) == PROBE_CONTROLLER_COUNT * 8192u,
|
||||
"factory capture sizes");
|
||||
_Static_assert(sizeof(probe_user_calibrations) == PROBE_CONTROLLER_COUNT * 4096u,
|
||||
"user calibration capture sizes");
|
||||
|
||||
bool probe_memory_read(uint32_t address, uint8_t* output, size_t length) {
|
||||
if (!output) return false;
|
||||
bool probe_memory_read(uint8_t instance, uint32_t address, uint8_t* output, size_t length) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !output) return false;
|
||||
const uint8_t* source;
|
||||
size_t offset, available;
|
||||
if (address >= 0x13000 && address < 0x15000) {
|
||||
offset = address - 0x13000;
|
||||
source = probe_factory_memory;
|
||||
available = sizeof(probe_factory_memory) - offset;
|
||||
source = probe_factory_memories[instance];
|
||||
available = sizeof(probe_factory_memories[instance]) - offset;
|
||||
} else if (address >= 0x1fc000 && address < 0x1fd000) {
|
||||
offset = address - 0x1fc000;
|
||||
source = probe_user_calibration;
|
||||
available = sizeof(probe_user_calibration) - offset;
|
||||
source = probe_user_calibrations[instance];
|
||||
available = sizeof(probe_user_calibrations[instance]) - offset;
|
||||
} else {
|
||||
return false; // No fabricated erased bytes, pairing keys, or firmware reads.
|
||||
}
|
||||
|
|
@ -43,12 +45,12 @@ static bool valid_calibration(const uint8_t* data) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool probe_memory_right_stick_calibration(uint8_t output[9]) {
|
||||
if (!output) return false;
|
||||
// A solo Joy-Con uses the primary calibration record, even for the right
|
||||
// controller. User magic precedes its 9-byte record; factory has no magic.
|
||||
const uint8_t* selected = probe_factory_memory + 0xa8;
|
||||
const uint8_t* user = probe_user_calibration + 0x40;
|
||||
bool probe_memory_stick_calibration(uint8_t instance, uint8_t output[9]) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT || !output) return false;
|
||||
// Each Joy-Con's own capture uses the primary calibration record.
|
||||
// User magic precedes its 9-byte record; factory has no magic.
|
||||
const uint8_t* selected = probe_factory_memories[instance] + 0xa8;
|
||||
const uint8_t* user = probe_user_calibrations[instance] + 0x40;
|
||||
if (user[0] == 0xb2 && user[1] == 0xa1 && valid_calibration(user + 2))
|
||||
selected = user + 2;
|
||||
if (!valid_calibration(selected)) return false;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
bool probe_memory_read(uint32_t address, uint8_t* output, size_t length);
|
||||
// Invalid instances and unavailable ranges leave output unchanged.
|
||||
bool probe_memory_read(uint8_t instance, uint32_t address, uint8_t* output, size_t length);
|
||||
// Packed center, positive travel, negative travel (two12-bit axes each).
|
||||
bool probe_memory_right_stick_calibration(uint8_t output[9]);
|
||||
bool probe_memory_stick_calibration(uint8_t instance, uint8_t output[9]);
|
||||
|
|
|
|||
94
tools/switch2_usb_probe/model.h
Normal file
94
tools/switch2_usb_probe/model.h
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef SWITCH2_PROBE_HUB
|
||||
#define SWITCH2_PROBE_HUB 0
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_HUB != 0 && SWITCH2_PROBE_HUB != 1
|
||||
#error "SWITCH2_PROBE_HUB must be 0 or 1"
|
||||
#endif
|
||||
|
||||
#ifndef SWITCH2_PROBE_COMPOSITE
|
||||
#define SWITCH2_PROBE_COMPOSITE 0
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_COMPOSITE != 0 && SWITCH2_PROBE_COMPOSITE != 1
|
||||
#error "SWITCH2_PROBE_COMPOSITE must be 0 or 1"
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_HUB && SWITCH2_PROBE_COMPOSITE
|
||||
#error "Native hub and composite USB backends are mutually exclusive"
|
||||
#endif
|
||||
|
||||
#ifndef SWITCH2_PROBE_JOYCON_LEFT
|
||||
#define SWITCH2_PROBE_JOYCON_LEFT 0
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_JOYCON_LEFT != 0 && SWITCH2_PROBE_JOYCON_LEFT != 1
|
||||
#error "SWITCH2_PROBE_JOYCON_LEFT must be 0 or 1"
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
#if SWITCH2_PROBE_JOYCON_LEFT
|
||||
#error "Dual-controller primary must be Joy-Con 2 (R)"
|
||||
#endif
|
||||
#ifndef PROBE_CONTROLLER_COUNT
|
||||
#define PROBE_CONTROLLER_COUNT 2
|
||||
#endif
|
||||
#if PROBE_CONTROLLER_COUNT != 2
|
||||
#error "Dual-controller output requires two controller instances"
|
||||
#endif
|
||||
#else
|
||||
#ifndef PROBE_CONTROLLER_COUNT
|
||||
#define PROBE_CONTROLLER_COUNT 1
|
||||
#endif
|
||||
#if PROBE_CONTROLLER_COUNT != 1
|
||||
#error "Standalone requires one controller instance"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if SWITCH2_PROBE_JOYCON_LEFT
|
||||
#define PROBE_JOYCON_PID 0x2067u
|
||||
#define PROBE_JOYCON_PRODUCT "Joy-Con 2 (L)"
|
||||
#define PROBE_JOYCON_SIDE "left"
|
||||
#define PROBE_NATIVE_REPORT_ID 0x07u
|
||||
#define PROBE_IMU_LENGTH_OFFSET 14u
|
||||
#define PROBE_IMU_DATA_OFFSET 15u
|
||||
#else
|
||||
#define PROBE_JOYCON_PID 0x2066u
|
||||
#define PROBE_JOYCON_PRODUCT "Joy-Con 2 (R)"
|
||||
#define PROBE_JOYCON_SIDE "right"
|
||||
#define PROBE_NATIVE_REPORT_ID 0x08u
|
||||
#define PROBE_IMU_LENGTH_OFFSET 15u
|
||||
#define PROBE_IMU_DATA_OFFSET 16u
|
||||
#endif
|
||||
|
||||
// Instance zero is the standalone model or the dual-controller right function.
|
||||
// In composite and native hub modes, instance one is the independent left side.
|
||||
static inline bool probe_model_is_left(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_COMPOSITE || SWITCH2_PROBE_HUB
|
||||
return instance == 1;
|
||||
#else
|
||||
(void)instance;
|
||||
return SWITCH2_PROBE_JOYCON_LEFT != 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint16_t probe_model_pid(uint8_t instance) {
|
||||
return probe_model_is_left(instance) ? 0x2067u : 0x2066u;
|
||||
}
|
||||
|
||||
static inline uint8_t probe_model_report_id(uint8_t instance) {
|
||||
return probe_model_is_left(instance) ? 0x07u : 0x08u;
|
||||
}
|
||||
|
||||
static inline uint8_t probe_model_imu_length_offset(uint8_t instance) {
|
||||
return probe_model_is_left(instance) ? 14u : 15u;
|
||||
}
|
||||
|
||||
static inline uint8_t probe_model_imu_data_offset(uint8_t instance) {
|
||||
return probe_model_is_left(instance) ? 15u : 16u;
|
||||
}
|
||||
|
|
@ -3,6 +3,78 @@
|
|||
set(SWITCH2_USB_PROBE_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
||||
set(PICO_MBEDTLS_CONFIG_FILE "${SWITCH2_USB_PROBE_DIR}/mbedtls_config.h")
|
||||
|
||||
option(SWITCH2_PROBE_COMPOSITE
|
||||
"Experiment: independent right and left Joy-Con 2 functions on one USB port" OFF)
|
||||
option(SWITCH2_PROBE_HUB "Native R/L devices on the built-in SIO USB hub" OFF)
|
||||
if(SWITCH2_PROBE_HUB AND SWITCH2_PROBE_COMPOSITE)
|
||||
message(FATAL_ERROR "Select native hub or composite, not both")
|
||||
endif()
|
||||
option(SWITCH2_PROBE_JOIN_CHORD_GATE
|
||||
"Experiment: pass L/R shoulder presses only while both physical halves hold them" OFF)
|
||||
set(SWITCH2_PROBE_SIDE "RIGHT" CACHE STRING "Primary Joy-Con 2 model: LEFT or RIGHT")
|
||||
set_property(CACHE SWITCH2_PROBE_SIDE PROPERTY STRINGS LEFT RIGHT)
|
||||
if(SWITCH2_PROBE_SIDE STREQUAL "LEFT")
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB OR SWITCH2_BRIDGE_WII_INPUT)
|
||||
message(FATAL_ERROR "SWITCH2_PROBE_SIDE=LEFT cannot be combined with composite or Wii input; select RIGHT")
|
||||
endif()
|
||||
set(probe_joycon_left 1)
|
||||
elseif(SWITCH2_PROBE_SIDE STREQUAL "RIGHT")
|
||||
set(probe_joycon_left 0)
|
||||
else()
|
||||
message(FATAL_ERROR "SWITCH2_PROBE_SIDE must be LEFT or RIGHT")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
if(NOT SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_BRIDGE_WII_INPUT
|
||||
OR NOT SWITCH2_BRIDGE_INPUT STREQUAL "JOYCON2")
|
||||
message(FATAL_ERROR "Composite Joy-Con 2 requires SWITCH_PICO_SWITCH2_USB_BRIDGE=ON and SWITCH2_BRIDGE_INPUT=JOYCON2")
|
||||
endif()
|
||||
set(probe_composite 0)
|
||||
if(SWITCH2_PROBE_COMPOSITE)
|
||||
set(probe_composite 1)
|
||||
endif()
|
||||
set(probe_controller_count 2)
|
||||
else()
|
||||
set(probe_composite 0)
|
||||
set(probe_controller_count 1)
|
||||
endif()
|
||||
if(SWITCH2_PROBE_JOIN_CHORD_GATE AND NOT SWITCH2_PROBE_COMPOSITE)
|
||||
message(FATAL_ERROR "The L+R shoulder gate requires the composite Joy-Con bridge")
|
||||
endif()
|
||||
# Capture, the Bluetooth backend, and TinyUSB must agree before their targets exist.
|
||||
add_compile_definitions(
|
||||
SWITCH2_PROBE_JOYCON_LEFT=${probe_joycon_left}
|
||||
SWITCH2_PROBE_COMPOSITE=${probe_composite}
|
||||
SWITCH2_PROBE_HUB=$<BOOL:${SWITCH2_PROBE_HUB}>
|
||||
PROBE_CONTROLLER_COUNT=${probe_controller_count})
|
||||
|
||||
set(SWITCH2_BRIDGE_SOURCE_ADDRESS "" CACHE STRING
|
||||
"Primary physical Bluetooth source address (xx:xx:xx:xx:xx:xx)")
|
||||
set(SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS "" CACHE STRING
|
||||
"Secondary left physical Bluetooth source address (xx:xx:xx:xx:xx:xx)")
|
||||
if(SWITCH_PICO_SWITCH2_USB_BRIDGE OR SWITCH2_PROBE_COMPOSITE)
|
||||
set(probe_source_fields SWITCH2_BRIDGE_SOURCE_ADDRESS)
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
list(APPEND probe_source_fields SWITCH2_BRIDGE_SECOND_SOURCE_ADDRESS)
|
||||
endif()
|
||||
set(probe_source_addresses "")
|
||||
foreach(field IN LISTS probe_source_fields)
|
||||
string(TOLOWER "${${field}}" source_address)
|
||||
string(LENGTH "${source_address}" source_address_length)
|
||||
if(NOT source_address_length EQUAL 17
|
||||
OR NOT source_address MATCHES "^([0-9a-f][0-9a-f]:)+[0-9a-f][0-9a-f]$"
|
||||
OR source_address STREQUAL "00:00:00:00:00:00"
|
||||
OR source_address STREQUAL "ff:ff:ff:ff:ff")
|
||||
message(FATAL_ERROR "Provide ${field} as a physical six-byte Bluetooth address")
|
||||
endif()
|
||||
if(source_address IN_LIST probe_source_addresses)
|
||||
message(FATAL_ERROR "Composite physical source addresses must be distinct")
|
||||
endif()
|
||||
list(APPEND probe_source_addresses "${source_address}")
|
||||
string(REPLACE ":" ",0x" ${field}_BYTES "${source_address}")
|
||||
string(PREPEND ${field}_BYTES "0x")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
function(switch2_usb_probe_configure target)
|
||||
set(probe_sources
|
||||
${SWITCH2_USB_PROBE_DIR}/main.c
|
||||
|
|
@ -10,6 +82,9 @@ function(switch2_usb_probe_configure target)
|
|||
${SWITCH2_USB_PROBE_DIR}/storage.cpp
|
||||
${SWITCH2_USB_PROBE_DIR}/button_test.c)
|
||||
target_compile_features(${target} PRIVATE c_std_11 cxx_std_17)
|
||||
if(SWITCH2_PROBE_JOIN_CHORD_GATE)
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_JOIN_CHORD_GATE=1)
|
||||
endif()
|
||||
target_include_directories(${target} PRIVATE
|
||||
${SWITCH2_USB_PROBE_DIR}
|
||||
${SWITCH2_USB_PROBE_DIR}/../../src/firmware
|
||||
|
|
@ -81,64 +156,13 @@ function(switch2_usb_probe_configure target)
|
|||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_ZERO_NATIVE_IMU_PAYLOAD=1)
|
||||
endif()
|
||||
|
||||
set(SWITCH2_PROBE_IDENTITY_FILE "" CACHE FILEPATH "64-byte Joy-Con 2 (R) factory-format identity block")
|
||||
if(SWITCH2_PROBE_IDENTITY_FILE)
|
||||
file(READ "${SWITCH2_PROBE_IDENTITY_FILE}" identity_hex LIMIT 65 HEX)
|
||||
string(LENGTH "${identity_hex}" identity_length)
|
||||
if(NOT identity_length EQUAL 128)
|
||||
message(FATAL_ERROR "Identity capture must contain exactly 64 bytes")
|
||||
endif()
|
||||
string(TOLOWER "${identity_hex}" identity_hex)
|
||||
string(SUBSTRING "${identity_hex}" 36 8 identity_vid_pid)
|
||||
if(NOT identity_vid_pid STREQUAL "7e056620")
|
||||
message(FATAL_ERROR "Identity capture must match Joy-Con 2 (R), 057e:2066")
|
||||
endif()
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," identity_bytes "${identity_hex}")
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/probe_identity.h"
|
||||
"// Generated from a private, read-only controller capture; do not commit.\n#include <stdint.h>\nstatic const uint8_t probe_identity_reply[64] = {${identity_bytes}};\n")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${SWITCH2_PROBE_IDENTITY_FILE}")
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_IDENTITY_REPLY=1)
|
||||
endif()
|
||||
set(SWITCH2_PROBE_VERSION_FILE "" CACHE FILEPATH "Captured 12-byte Joy-Con 2 (R) firmware-version reply")
|
||||
set(SWITCH2_PROBE_CONTROLLER_ADDRESS "" CACHE STRING "Advertised controller address (captured or distinct virtual identity)")
|
||||
if(SWITCH2_PROBE_VERSION_FILE)
|
||||
if(NOT SWITCH2_PROBE_IDENTITY_FILE)
|
||||
message(FATAL_ERROR "Version response requires the matching identity capture")
|
||||
endif()
|
||||
file(READ "${SWITCH2_PROBE_VERSION_FILE}" version_hex LIMIT 13 HEX)
|
||||
string(LENGTH "${version_hex}" version_length)
|
||||
if(NOT version_length EQUAL 24)
|
||||
message(FATAL_ERROR "Firmware version capture must contain exactly 12 bytes")
|
||||
endif()
|
||||
string(TOLOWER "${version_hex}" version_hex)
|
||||
string(SUBSTRING "${version_hex}" 6 2 firmware_type)
|
||||
if(NOT firmware_type STREQUAL "01")
|
||||
message(FATAL_ERROR "Firmware version capture must describe Joy-Con 2 (R)")
|
||||
endif()
|
||||
string(REPLACE ":" "" address_hex "${SWITCH2_PROBE_CONTROLLER_ADDRESS}")
|
||||
string(TOLOWER "${address_hex}" address_hex)
|
||||
string(LENGTH "${address_hex}" address_length)
|
||||
if(NOT address_length EQUAL 12 OR NOT address_hex MATCHES "^[0-9a-f]+$")
|
||||
message(FATAL_ERROR "Provide a six-byte advertised controller Bluetooth address")
|
||||
endif()
|
||||
set(address_reversed "")
|
||||
foreach(byte RANGE 0 5)
|
||||
math(EXPR position "10 - 2 * ${byte}")
|
||||
string(SUBSTRING "${address_hex}" ${position} 2 octet)
|
||||
string(APPEND address_reversed "${octet}")
|
||||
endforeach()
|
||||
string(SUBSTRING "${version_hex}" 0 6 main_version)
|
||||
string(SUBSTRING "${version_hex}" 8 6 bluetooth_version)
|
||||
# Layout corroborated against two genuine USB vendor-02 responses and their
|
||||
# matching command-10 version and command-15 address responses.
|
||||
set(status_hex "${main_version}000000${bluetooth_version}00${address_reversed}")
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," status_bytes "${status_hex}")
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," firmware_bytes "${version_hex}")
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/probe_version.h"
|
||||
"// Generated from private controller captures; do not commit.\n#include <stdint.h>\nstatic const uint8_t probe_version_reply[16] = {${status_bytes}};\nstatic const uint8_t probe_firmware_version[12] = {${firmware_bytes}};\n")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${SWITCH2_PROBE_VERSION_FILE}")
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_VERSION_REPLY=1)
|
||||
endif()
|
||||
foreach(prefix IN ITEMS SWITCH2_PROBE SWITCH2_PROBE_SECOND)
|
||||
set(${prefix}_IDENTITY_FILE "" CACHE FILEPATH "64-byte matching Joy-Con 2 factory-format identity block")
|
||||
set(${prefix}_VERSION_FILE "" CACHE FILEPATH "Captured 12-byte matching Joy-Con 2 firmware-version reply")
|
||||
set(${prefix}_CONTROLLER_ADDRESS "" CACHE STRING "Advertised controller address (captured or distinct virtual identity)")
|
||||
set(${prefix}_FACTORY_FILE "" CACHE FILEPATH "8192-byte captured factory region with configured virtual identity")
|
||||
set(${prefix}_USER_CALIBRATION_FILE "" CACHE FILEPATH "4096-byte captured user calibration region")
|
||||
endforeach()
|
||||
option(SWITCH2_PROBE_ACK_SETUP04 "Acknowledge the observed vendor-04 setup transaction" OFF)
|
||||
if(SWITCH2_PROBE_ACK_SETUP04)
|
||||
if(NOT SWITCH2_PROBE_IDENTITY_FILE OR NOT SWITCH2_PROBE_VERSION_FILE)
|
||||
|
|
@ -153,29 +177,130 @@ function(switch2_usb_probe_configure target)
|
|||
endif()
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_USB_INIT=1)
|
||||
endif()
|
||||
set(SWITCH2_PROBE_FACTORY_FILE "" CACHE FILEPATH "8192-byte captured factory region with configured virtual identity")
|
||||
set(SWITCH2_PROBE_USER_CALIBRATION_FILE "" CACHE FILEPATH "4096-byte captured user calibration region")
|
||||
if(SWITCH2_PROBE_FACTORY_FILE OR SWITCH2_PROBE_USER_CALIBRATION_FILE)
|
||||
if(NOT SWITCH2_PROBE_USB_INIT OR NOT SWITCH2_PROBE_FACTORY_FILE OR NOT SWITCH2_PROBE_USER_CALIBRATION_FILE)
|
||||
message(FATAL_ERROR "Memory replies require initialized USB and both calibration captures")
|
||||
|
||||
set(probe_capture_prefixes SWITCH2_PROBE)
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
list(APPEND probe_capture_prefixes SWITCH2_PROBE_SECOND)
|
||||
endif()
|
||||
set(identity_rows "")
|
||||
set(status_rows "")
|
||||
set(firmware_rows "")
|
||||
set(factory_rows "")
|
||||
set(user_calibration_rows "")
|
||||
set(controller_addresses "")
|
||||
foreach(prefix IN LISTS probe_capture_prefixes)
|
||||
if(probe_joycon_left OR prefix STREQUAL "SWITCH2_PROBE_SECOND")
|
||||
set(probe_model "Joy-Con 2 (L)")
|
||||
set(probe_vid_pid "7e056720")
|
||||
set(probe_firmware_type "00")
|
||||
else()
|
||||
set(probe_model "Joy-Con 2 (R)")
|
||||
set(probe_vid_pid "7e056620")
|
||||
set(probe_firmware_type "01")
|
||||
endif()
|
||||
file(READ "${SWITCH2_PROBE_FACTORY_FILE}" factory_hex LIMIT 8193 HEX)
|
||||
file(READ "${SWITCH2_PROBE_USER_CALIBRATION_FILE}" user_calibration_hex LIMIT 4097 HEX)
|
||||
string(LENGTH "${factory_hex}" factory_length)
|
||||
string(LENGTH "${user_calibration_hex}" user_calibration_length)
|
||||
if(NOT factory_length EQUAL 16384 OR NOT user_calibration_length EQUAL 8192)
|
||||
message(FATAL_ERROR "Factory/user captures must contain exactly 8192/4096 bytes")
|
||||
if(SWITCH2_PROBE_COMPOSITE OR SWITCH2_PROBE_HUB)
|
||||
foreach(field IDENTITY_FILE VERSION_FILE FACTORY_FILE USER_CALIBRATION_FILE CONTROLLER_ADDRESS)
|
||||
if(NOT ${prefix}_${field})
|
||||
message(FATAL_ERROR "Composite ${probe_model} requires ${prefix}_${field}")
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
string(SUBSTRING "${factory_hex}" 0 128 factory_identity_hex)
|
||||
if(NOT factory_identity_hex STREQUAL identity_hex)
|
||||
message(FATAL_ERROR "Factory memory identity must match the vendor-control identity")
|
||||
if(${prefix}_IDENTITY_FILE)
|
||||
file(READ "${${prefix}_IDENTITY_FILE}" identity_hex LIMIT 65 HEX)
|
||||
string(LENGTH "${identity_hex}" identity_length)
|
||||
if(NOT identity_length EQUAL 128)
|
||||
message(FATAL_ERROR "${prefix}_IDENTITY_FILE must contain exactly 64 bytes")
|
||||
endif()
|
||||
string(TOLOWER "${identity_hex}" identity_hex)
|
||||
string(SUBSTRING "${identity_hex}" 36 8 identity_vid_pid)
|
||||
if(NOT identity_vid_pid STREQUAL probe_vid_pid)
|
||||
message(FATAL_ERROR "${prefix}_IDENTITY_FILE must match selected model ${probe_model}")
|
||||
endif()
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," identity_bytes "${identity_hex}")
|
||||
string(APPEND identity_rows " {${identity_bytes}},\n")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${${prefix}_IDENTITY_FILE}")
|
||||
endif()
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," factory_bytes "${factory_hex}")
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," user_calibration_bytes "${user_calibration_hex}")
|
||||
if(${prefix}_VERSION_FILE)
|
||||
if(NOT ${prefix}_IDENTITY_FILE)
|
||||
message(FATAL_ERROR "${prefix}_VERSION_FILE requires the matching identity capture")
|
||||
endif()
|
||||
file(READ "${${prefix}_VERSION_FILE}" version_hex LIMIT 13 HEX)
|
||||
string(LENGTH "${version_hex}" version_length)
|
||||
if(NOT version_length EQUAL 24)
|
||||
message(FATAL_ERROR "${prefix}_VERSION_FILE must contain exactly 12 bytes")
|
||||
endif()
|
||||
string(TOLOWER "${version_hex}" version_hex)
|
||||
string(SUBSTRING "${version_hex}" 6 2 firmware_type)
|
||||
if(NOT firmware_type STREQUAL probe_firmware_type)
|
||||
message(FATAL_ERROR "${prefix}_VERSION_FILE must describe selected model ${probe_model}")
|
||||
endif()
|
||||
string(REPLACE ":" "" address_hex "${${prefix}_CONTROLLER_ADDRESS}")
|
||||
string(TOLOWER "${address_hex}" address_hex)
|
||||
string(LENGTH "${address_hex}" address_length)
|
||||
if(NOT address_length EQUAL 12 OR NOT address_hex MATCHES "^[0-9a-f]+$"
|
||||
OR address_hex STREQUAL "000000000000" OR address_hex STREQUAL "ffffffffffff")
|
||||
message(FATAL_ERROR "Provide ${prefix}_CONTROLLER_ADDRESS as a six-byte advertised Bluetooth address")
|
||||
endif()
|
||||
if(address_hex IN_LIST controller_addresses)
|
||||
message(FATAL_ERROR "Composite advertised controller addresses must be distinct")
|
||||
endif()
|
||||
list(APPEND controller_addresses "${address_hex}")
|
||||
set(address_reversed "")
|
||||
foreach(byte RANGE 0 5)
|
||||
math(EXPR position "10 - 2 * ${byte}")
|
||||
string(SUBSTRING "${address_hex}" ${position} 2 octet)
|
||||
string(APPEND address_reversed "${octet}")
|
||||
endforeach()
|
||||
string(SUBSTRING "${version_hex}" 0 6 main_version)
|
||||
string(SUBSTRING "${version_hex}" 8 6 bluetooth_version)
|
||||
# Layout corroborated against two genuine USB vendor-02 responses and their
|
||||
# matching command-10 version and command-15 address responses.
|
||||
set(status_hex "${main_version}000000${bluetooth_version}00${address_reversed}")
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," status_bytes "${status_hex}")
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," firmware_bytes "${version_hex}")
|
||||
string(APPEND status_rows " {${status_bytes}},\n")
|
||||
string(APPEND firmware_rows " {${firmware_bytes}},\n")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${${prefix}_VERSION_FILE}")
|
||||
endif()
|
||||
if(${prefix}_FACTORY_FILE OR ${prefix}_USER_CALIBRATION_FILE)
|
||||
if(NOT SWITCH2_PROBE_USB_INIT OR NOT ${prefix}_FACTORY_FILE OR NOT ${prefix}_USER_CALIBRATION_FILE)
|
||||
message(FATAL_ERROR "${prefix} memory replies require initialized USB and both calibration captures")
|
||||
endif()
|
||||
file(READ "${${prefix}_FACTORY_FILE}" factory_hex LIMIT 8193 HEX)
|
||||
file(READ "${${prefix}_USER_CALIBRATION_FILE}" user_calibration_hex LIMIT 4097 HEX)
|
||||
string(LENGTH "${factory_hex}" factory_length)
|
||||
string(LENGTH "${user_calibration_hex}" user_calibration_length)
|
||||
if(NOT factory_length EQUAL 16384 OR NOT user_calibration_length EQUAL 8192)
|
||||
message(FATAL_ERROR "${prefix} factory/user captures must contain exactly 8192/4096 bytes")
|
||||
endif()
|
||||
string(TOLOWER "${factory_hex}" factory_hex)
|
||||
string(TOLOWER "${user_calibration_hex}" user_calibration_hex)
|
||||
string(SUBSTRING "${factory_hex}" 0 128 factory_identity_hex)
|
||||
if(NOT factory_identity_hex STREQUAL identity_hex)
|
||||
message(FATAL_ERROR "${prefix} factory memory identity must match the vendor-control identity")
|
||||
endif()
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," factory_bytes "${factory_hex}")
|
||||
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," user_calibration_bytes "${user_calibration_hex}")
|
||||
string(APPEND factory_rows " {${factory_bytes}},\n")
|
||||
string(APPEND user_calibration_rows " {${user_calibration_bytes}},\n")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${${prefix}_FACTORY_FILE}" "${${prefix}_USER_CALIBRATION_FILE}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(capture_header "// Generated from private, read-only controller captures; do not commit.\n#include <stdint.h>\n#include \"model.h\"\n")
|
||||
if(SWITCH2_PROBE_IDENTITY_FILE)
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/probe_identity.h"
|
||||
"${capture_header}static const uint8_t probe_identity_replies[PROBE_CONTROLLER_COUNT][64] = {\n${identity_rows}};\n")
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_IDENTITY_REPLY=1)
|
||||
endif()
|
||||
if(SWITCH2_PROBE_VERSION_FILE)
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/probe_version.h"
|
||||
"${capture_header}static const uint8_t probe_version_replies[PROBE_CONTROLLER_COUNT][16] = {\n${status_rows}};\nstatic const uint8_t probe_firmware_versions[PROBE_CONTROLLER_COUNT][12] = {\n${firmware_rows}};\n")
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_VERSION_REPLY=1)
|
||||
endif()
|
||||
if(SWITCH2_PROBE_FACTORY_FILE)
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/probe_memory_data.h"
|
||||
"// Generated from private calibration captures; do not commit.\n#include <stdint.h>\nstatic const uint8_t probe_factory_memory[8192] = {${factory_bytes}};\nstatic const uint8_t probe_user_calibration[4096] = {${user_calibration_bytes}};\n")
|
||||
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS
|
||||
"${SWITCH2_PROBE_FACTORY_FILE}" "${SWITCH2_PROBE_USER_CALIBRATION_FILE}")
|
||||
"${capture_header}static const uint8_t probe_factory_memories[PROBE_CONTROLLER_COUNT][8192] = {\n${factory_rows}};\nstatic const uint8_t probe_user_calibrations[PROBE_CONTROLLER_COUNT][4096] = {\n${user_calibration_rows}};\n")
|
||||
list(APPEND probe_sources ${SWITCH2_USB_PROBE_DIR}/memory.c)
|
||||
target_compile_definitions(${target} PRIVATE SWITCH2_PROBE_MEMORY=1)
|
||||
endif()
|
||||
|
|
@ -189,17 +314,69 @@ function(switch2_usb_probe_configure target)
|
|||
target_compile_options(${target} PRIVATE ${probe_compile_options})
|
||||
endif()
|
||||
target_link_libraries(${target} PRIVATE pico_stdlib hardware_uart hardware_sync
|
||||
hardware_flash pico_flash pico_mbedtls_crypto pico_mbedtls_headers tinyusb_device)
|
||||
hardware_flash pico_flash pico_mbedtls_crypto pico_mbedtls_headers)
|
||||
if(SWITCH2_PROBE_HUB)
|
||||
target_sources(${target} PRIVATE
|
||||
${SWITCH2_USB_PROBE_DIR}/../pico_usb_address_probe/router.c
|
||||
${SWITCH2_USB_PROBE_DIR}/../../src/firmware/usb/native_hub/native_hub.c)
|
||||
target_include_directories(${target} PRIVATE
|
||||
${SWITCH2_USB_PROBE_DIR}/../pico_usb_address_probe
|
||||
${PICO_SDK_PATH}/lib/tinyusb/src)
|
||||
target_compile_definitions(${target} PRIVATE CFG_TUSB_MCU=OPT_MCU_RP2040)
|
||||
target_link_libraries(${target} PRIVATE pico_multicore pico_unique_id hardware_irq hardware_resets)
|
||||
set_source_files_properties(
|
||||
${SWITCH2_USB_PROBE_DIR}/../pico_usb_address_probe/router.c
|
||||
${SWITCH2_USB_PROBE_DIR}/../../src/firmware/usb/native_hub/native_hub.c
|
||||
PROPERTIES COMPILE_OPTIONS "-O3;-fno-jump-tables;-Wall;-Wextra;-Werror")
|
||||
# Core0 now owns the Bluetooth call stack. Reserve16KiB from main
|
||||
# SRAM instead of overflowing the SDK's4KiB scratch stack region.
|
||||
set(default_linker "${PICO_SDK_PATH}/src/rp2_common/pico_crt0/rp2350/memmap_default.ld")
|
||||
file(READ "${default_linker}" hub_linker)
|
||||
string(REPLACE "RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 512k"
|
||||
"RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 496k\n MAIN_STACK(rwx) : ORIGIN = 0x2007c000, LENGTH = 16k"
|
||||
hub_linker "${hub_linker}")
|
||||
string(REPLACE "KEEP(*(.stack*))\n } > SCRATCH_Y"
|
||||
"KEEP(*(.stack*))\n } > MAIN_STACK" hub_linker "${hub_linker}")
|
||||
string(REPLACE "__StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y);"
|
||||
"__StackTop = ORIGIN(MAIN_STACK) + LENGTH(MAIN_STACK);" hub_linker "${hub_linker}")
|
||||
if(NOT hub_linker MATCHES "MAIN_STACK")
|
||||
message(FATAL_ERROR "SDK linker stack layout changed")
|
||||
endif()
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/native_hub_stack.ld" "${hub_linker}")
|
||||
pico_set_linker_script(${target} "${CMAKE_CURRENT_BINARY_DIR}/native_hub_stack.ld")
|
||||
else()
|
||||
target_link_libraries(${target} PRIVATE tinyusb_device)
|
||||
endif()
|
||||
pico_enable_stdio_usb(${target} 0)
|
||||
pico_enable_stdio_uart(${target} 1)
|
||||
if(SWITCH2_BRIDGE_WII_INPUT)
|
||||
if(SWITCH2_PROBE_HUB)
|
||||
pico_set_program_name(${target} "Native Joy-Con 2 R and L stock USB hub bridge")
|
||||
elseif(SWITCH2_PROBE_COMPOSITE)
|
||||
pico_set_program_name(${target} "Switch 2 right and left Joy-Con composite bridge")
|
||||
elseif(SWITCH2_BRIDGE_WII_INPUT)
|
||||
pico_set_program_name(${target} "Switch 2 Wii IR and native motion bridge")
|
||||
elseif(SWITCH_PICO_SWITCH2_USB_BRIDGE AND probe_joycon_left)
|
||||
pico_set_program_name(${target} "Switch 2 left Joy-Con Bluetooth bridge")
|
||||
elseif(SWITCH_PICO_SWITCH2_USB_BRIDGE)
|
||||
pico_set_program_name(${target} "Switch 2 right Joy-Con Bluetooth bridge")
|
||||
else()
|
||||
pico_set_program_name(${target} "Switch 2 USB initialization capture")
|
||||
endif()
|
||||
if(SWITCH2_PROBE_OMIT_NATIVE_IMU)
|
||||
if(SWITCH2_PROBE_HUB)
|
||||
pico_set_program_version(${target} "0.66-native-hub-input")
|
||||
elseif(SWITCH2_PROBE_JOIN_CHORD_GATE)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.37-pair-chord-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.37-pair-chord")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_COMPOSITE)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.35-pair-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.35-pair")
|
||||
endif()
|
||||
elseif(SWITCH2_PROBE_OMIT_NATIVE_IMU)
|
||||
pico_set_program_version(${target} "0.24-no-imu")
|
||||
elseif(SWITCH2_PROBE_ZERO_NATIVE_IMU_PAYLOAD)
|
||||
pico_set_program_version(${target} "0.24-zero-imu-payload")
|
||||
|
|
@ -209,6 +386,12 @@ function(switch2_usb_probe_configure target)
|
|||
else()
|
||||
pico_set_program_version(${target} "0.33-wii")
|
||||
endif()
|
||||
elseif(SWITCH_PICO_SWITCH2_USB_BRIDGE AND probe_joycon_left)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.34-left-trace")
|
||||
else()
|
||||
pico_set_program_version(${target} "0.34-left")
|
||||
endif()
|
||||
elseif(SWITCH_PICO_SWITCH2_USB_BRIDGE)
|
||||
if(SWITCH2_PROBE_TRACE_NATIVE_INPUT)
|
||||
pico_set_program_version(${target} "0.25-trace")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#include <string.h>
|
||||
|
||||
// Wire contracts: ndeadly/switch2_controller_research commands.md (03/0D,
|
||||
// 03/0A, 07/01, 09/01-08, 16/01, 15/01-04) and hid_reports.md (05/08). USB reply headers
|
||||
// 03/0A, 07/01, 09/01-08, 16/01, 15/01-04) and hid_reports.md (05/07/08). USB reply headers
|
||||
// and status payloads match captures/usb/rumble-procon-gccon.pcapng.gz.
|
||||
// This public component is not a pairing key. The host supplies the other half.
|
||||
static const uint8_t device_key_component[16] = {
|
||||
|
|
@ -71,7 +71,7 @@ static bool finalize_pairing(probe_protocol_state* state, const uint8_t* key) {
|
|||
memcpy(blob + 7, state->pending_host_addresses, 6u * state->pending_host_count);
|
||||
memcpy(blob + sizeof(blob) - 16u, key, 16);
|
||||
// Preserve both the old committed key and pending retry on any save failure.
|
||||
if (!state->save_pairing(blob, sizeof(blob))) return false;
|
||||
if (!state->save_pairing(state->context, blob, sizeof(blob))) return false;
|
||||
state->committed_host_count = blob[6];
|
||||
memcpy(state->committed_host_addresses, blob + 7, sizeof(state->committed_host_addresses));
|
||||
memcpy(state->committed_key, blob + sizeof(blob) - 16u, sizeof(state->committed_key));
|
||||
|
|
@ -81,11 +81,12 @@ static bool finalize_pairing(probe_protocol_state* state, const uint8_t* key) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void probe_protocol_reset(probe_protocol_state* state) {
|
||||
void probe_protocol_reset(probe_protocol_state* state, bool is_left) {
|
||||
memset(state, 0, sizeof(*state));
|
||||
state->report_id = 0x08;
|
||||
state->right_stick_center[1] = 0x08;
|
||||
state->right_stick_center[2] = 0x80;
|
||||
state->is_left = is_left;
|
||||
state->report_id = is_left ? 0x07 : 0x08;
|
||||
state->stick_center[1] = 0x08;
|
||||
state->stick_center[2] = 0x80;
|
||||
}
|
||||
|
||||
bool probe_protocol_restore_pairing(probe_protocol_state* state,
|
||||
|
|
@ -226,14 +227,14 @@ size_t probe_protocol_command(probe_protocol_state* state, const uint8_t* comman
|
|||
if (capacity < reply_length) return 0;
|
||||
if (vibration_sample) {
|
||||
uint64_t token = 0;
|
||||
if (!state->play_sample(command[8], &token) || !token) return 0;
|
||||
if (!state->play_sample(state->context, command[8], &token) || !token) return 0;
|
||||
*deferred_token = token;
|
||||
}
|
||||
uint8_t encrypted_challenge[16];
|
||||
if (confirm_key && !challenge_response(pairing_key, command + 9, encrypted_challenge)) return 0;
|
||||
if (finalize && !finalize_pairing(state, pairing_key)) return 0;
|
||||
if (memory_read &&
|
||||
!state->read_memory(memory_address, reply + 16, memory_length)) return 0;
|
||||
!state->read_memory(state->context, memory_address, reply + 16, memory_length)) return 0;
|
||||
const uint8_t header[] = {command[0], 0x01, 0, command[3], 0, 0xf8, 0, 0};
|
||||
memcpy(reply, header, sizeof(header));
|
||||
if (info11_03) {
|
||||
|
|
@ -255,7 +256,8 @@ size_t probe_protocol_command(probe_protocol_state* state, const uint8_t* comman
|
|||
reply[8] = 1;
|
||||
} else if (select_report) {
|
||||
// The real controller acknowledges but ignores unsupported report IDs.
|
||||
if (command[8] == 0x05 || command[8] == 0x08) state->report_id = command[8];
|
||||
if (command[8] == 0x05 || command[8] == (state->is_left ? 0x07 : 0x08))
|
||||
state->report_id = command[8];
|
||||
} else if (exchange_addresses) {
|
||||
if (length != 8) {
|
||||
clear_pending_pairing(state);
|
||||
|
|
@ -337,34 +339,48 @@ size_t probe_protocol_command(probe_protocol_state* state, const uint8_t* comman
|
|||
size_t probe_protocol_report(const probe_protocol_state* state, uint8_t report_id,
|
||||
uint8_t* output, size_t capacity) {
|
||||
if (!state || !state->initialized || !output || capacity < PROBE_INPUT_SIZE ||
|
||||
(report_id != 0x05 && report_id != 0x08)) return 0;
|
||||
(report_id != 0x05 && report_id != (state->is_left ? 0x07 : 0x08))) return 0;
|
||||
memset(output, 0, PROBE_INPUT_SIZE);
|
||||
const bool buttons_enabled = (state->enabled_features & 1) != 0;
|
||||
const uint8_t buttons0 = state->controller_active && buttons_enabled ? state->controller_buttons[0] : 0;
|
||||
const uint8_t buttons1 = state->controller_active && buttons_enabled ? state->controller_buttons[1] & 0xd1 : 0;
|
||||
const uint8_t buttons1 = state->controller_active && buttons_enabled ?
|
||||
state->controller_buttons[1] & (state->is_left ? 0xc1 : 0xd1) : 0;
|
||||
const uint8_t* stick = state->controller_active && (state->enabled_features & 2) ?
|
||||
state->controller_stick : state->right_stick_center;
|
||||
if (report_id == 0x08) {
|
||||
state->controller_stick : state->stick_center;
|
||||
if (report_id != 0x05) {
|
||||
output[0] = (uint8_t)state->report_counter;
|
||||
output[1] = 0x25; // Virtual full battery, external USB power.
|
||||
output[2] = buttons0;
|
||||
output[3] = buttons1;
|
||||
if (state->test_rail_buttons && (state->enabled_features & 1))
|
||||
output[3] |= 0xc0; // Joy-Con R native SL + SR.
|
||||
output[3] |= 0xc0; // Both models' native SL + SR.
|
||||
output[4] = 0x07;
|
||||
memcpy(output + 5, stick, 3);
|
||||
// Diagnostic snapshot only; complete live native packets bypass this generator.
|
||||
} else {
|
||||
for (unsigned i = 0; i < 4; ++i) output[i] = (uint8_t)(state->report_counter >> (8 * i));
|
||||
output[4] = (uint8_t)(((buttons0 & 0x03) << 2) | ((buttons0 & 0x0c) >> 2) |
|
||||
((buttons0 & 0x30) << 2) | ((buttons1 & 0xc0) >> 2));
|
||||
output[5] = (uint8_t)(((buttons0 & 0xc0) >> 5) | ((buttons1 & 0x01) << 4) |
|
||||
((buttons1 & 0x10) << 2));
|
||||
if (state->test_rail_buttons && (state->enabled_features & 1))
|
||||
output[4] |= 0x30; // Common report: right SL + SR.
|
||||
output[11] = 0x08;
|
||||
output[12] = 0x80;
|
||||
memcpy(output + 13, stick, 3);
|
||||
if (state->is_left) {
|
||||
output[5] = (uint8_t)(((buttons0 & 0x40) >> 6) | ((buttons0 & 0x80) >> 4) |
|
||||
((buttons1 & 0x01) << 5));
|
||||
output[6] = (uint8_t)((buttons0 & 0x01) | ((buttons0 & 0x06) << 1) |
|
||||
((buttons0 & 0x08) >> 2) | ((buttons0 & 0x30) << 2) |
|
||||
((buttons1 & 0xc0) >> 2));
|
||||
if (state->test_rail_buttons && buttons_enabled)
|
||||
output[6] |= 0x30; // Common report: left SL + SR.
|
||||
memcpy(output + 10, stick, 3);
|
||||
output[14] = 0x08;
|
||||
output[15] = 0x80;
|
||||
} else {
|
||||
output[4] = (uint8_t)(((buttons0 & 0x03) << 2) | ((buttons0 & 0x0c) >> 2) |
|
||||
((buttons0 & 0x30) << 2) | ((buttons1 & 0xc0) >> 2));
|
||||
output[5] = (uint8_t)(((buttons0 & 0xc0) >> 5) | ((buttons1 & 0x01) << 4) |
|
||||
((buttons1 & 0x10) << 2));
|
||||
if (state->test_rail_buttons && buttons_enabled)
|
||||
output[4] |= 0x30; // Common report: right SL + SR.
|
||||
output[11] = 0x08;
|
||||
output[12] = 0x80;
|
||||
memcpy(output + 13, stick, 3);
|
||||
}
|
||||
output[31] = 0xa0;
|
||||
output[32] = 0x0f; // Virtual battery voltage 4000mV.
|
||||
output[33] = 0x20;
|
||||
|
|
@ -372,3 +388,22 @@ size_t probe_protocol_report(const probe_protocol_state* state, uint8_t report_i
|
|||
}
|
||||
return PROBE_INPUT_SIZE;
|
||||
}
|
||||
|
||||
void probe_protocol_gate_native_report(const probe_protocol_state* state,
|
||||
uint8_t input[PROBE_INPUT_SIZE]) {
|
||||
const uint8_t imu_length_offset = state->is_left ? 14u : 15u;
|
||||
if (!(state->enabled_features & 1)) memset(input + 2, 0, 2);
|
||||
if (!(state->enabled_features & 2))
|
||||
memcpy(input + 5, state->stick_center, sizeof(state->stick_center));
|
||||
if (!(state->enabled_features & 0x10)) memset(input + 9, 0, 5);
|
||||
#ifdef SWITCH2_PROBE_OMIT_NATIVE_IMU
|
||||
// Deliberate A/B fault injection: leave every other field and feature bit intact.
|
||||
memset(input + imu_length_offset, 0, 41);
|
||||
#elif defined(SWITCH2_PROBE_ZERO_NATIVE_IMU_PAYLOAD)
|
||||
if (!(state->enabled_features & 4)) input[imu_length_offset] = 0;
|
||||
memset(input + imu_length_offset + 1u, 0, 40); // Preserve enabled genuine length.
|
||||
#else
|
||||
if (!(state->enabled_features & 4))
|
||||
memset(input + imu_length_offset, 0, 41);
|
||||
#endif
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "model.h"
|
||||
|
||||
#define PROBE_COMMAND_MAX_SIZE 263u
|
||||
#define PROBE_REPLY_MAX_SIZE 96u
|
||||
|
|
@ -10,13 +11,15 @@
|
|||
#define PROBE_INPUT_SIZE 63u
|
||||
|
||||
typedef struct {
|
||||
bool is_left;
|
||||
void* context; // Caller-owned context shared by this state's callbacks.
|
||||
bool initialized;
|
||||
uint8_t report_id;
|
||||
bool test_rail_buttons;
|
||||
bool runtime03_0c; // Observed USB toggle; full semantics remain unknown.
|
||||
uint8_t right_stick_center[3];
|
||||
uint8_t stick_center[3];
|
||||
bool controller_active;
|
||||
uint8_t controller_buttons[2]; // Native right Joy-Con button ordering.
|
||||
uint8_t controller_buttons[2]; // Selected model's native Joy-Con button ordering.
|
||||
uint8_t controller_stick[3]; // Raw packed 12-bit axes from the selected donor.
|
||||
uint8_t player_leds; // Virtual four-LED mask, exposed through UART diagnostics.
|
||||
bool player_leds_flashing;
|
||||
|
|
@ -39,15 +42,15 @@ typedef struct {
|
|||
uint8_t committed_host_addresses[PROBE_HOST_MAX_ADDRESSES][6];
|
||||
uint8_t committed_key[16]; // Standard AES byte order.
|
||||
// Synchronous durable save; NULL disables successful finalization.
|
||||
bool (*save_pairing)(const uint8_t* blob, size_t length);
|
||||
bool (*read_memory)(uint32_t address, uint8_t* output, size_t length);
|
||||
bool (*save_pairing)(void* context, const uint8_t* blob, size_t length);
|
||||
bool (*read_memory)(void* context, uint32_t address, uint8_t* output, size_t length);
|
||||
// Queue a physical sample, returning true only with a nonzero completion token.
|
||||
// Acceptance is not a Bluetooth application ACK.
|
||||
bool (*play_sample)(uint8_t sample_id, uint64_t* token);
|
||||
bool (*play_sample)(void* context, uint8_t sample_id, uint64_t* token);
|
||||
uint32_t report_counter;
|
||||
} probe_protocol_state;
|
||||
|
||||
void probe_protocol_reset(probe_protocol_state* state);
|
||||
void probe_protocol_reset(probe_protocol_state* state, bool is_left);
|
||||
// Blob: own address[6], count[1], zero-padded host addresses[42][6], AES key[16].
|
||||
// Rejects other identities, invalid counts/padding/lengths without mutation.
|
||||
// A successful restore replaces the committed record and clears pending state.
|
||||
|
|
@ -66,3 +69,7 @@ size_t probe_protocol_command(probe_protocol_state* state, const uint8_t* comman
|
|||
// Button/stick snapshot without relative mouse events; safe for GET_REPORT.
|
||||
size_t probe_protocol_report(const probe_protocol_state* state, uint8_t report_id,
|
||||
uint8_t* output, size_t capacity);
|
||||
// Apply virtual feature gates to one complete native payload in place.
|
||||
// Enabled mouse/motion and all opaque bytes remain unchanged.
|
||||
void probe_protocol_gate_native_report(const probe_protocol_state* state,
|
||||
uint8_t input[PROBE_INPUT_SIZE]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
#include "storage.h"
|
||||
#include "model.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -16,13 +17,16 @@ namespace {
|
|||
constexpr size_t kSlotCount = 2;
|
||||
constexpr size_t kMaximumPayloadSize = 512;
|
||||
constexpr size_t kStorageSize = kSlotCount * FLASH_SECTOR_SIZE;
|
||||
constexpr size_t kReservedStorageSize = 2 * kStorageSize;
|
||||
constexpr size_t kConfigurationStorageSize =
|
||||
CONFIGURATION_STORAGE_COPY_COUNT * FLASH_SECTOR_SIZE;
|
||||
constexpr size_t kConfigurationStorageOffset =
|
||||
PICO_FLASH_BANK_STORAGE_OFFSET - kConfigurationStorageSize;
|
||||
constexpr size_t kProfileStorageOffset =
|
||||
kConfigurationStorageOffset - PROFILE_STORAGE_TOTAL_SIZE;
|
||||
constexpr uint32_t kStorageOffset = kProfileStorageOffset - kStorageSize;
|
||||
// Keep the original right bank adjacent to profiles; reserve the left bank below.
|
||||
constexpr uint32_t kRightStorageOffset = kProfileStorageOffset - kStorageSize;
|
||||
constexpr uint32_t kLeftStorageOffset = kRightStorageOffset - kStorageSize;
|
||||
constexpr uint32_t kFlashSafeTimeoutMs = 5000;
|
||||
constexpr uint32_t kFormatVersion = 1;
|
||||
|
||||
|
|
@ -66,9 +70,11 @@ static_assert(PROFILE_STORAGE_TOTAL_SIZE % FLASH_SECTOR_SIZE == 0);
|
|||
static_assert(PICO_FLASH_BANK_STORAGE_OFFSET % FLASH_SECTOR_SIZE == 0);
|
||||
static_assert(PICO_FLASH_BANK_STORAGE_OFFSET >=
|
||||
kConfigurationStorageSize + PROFILE_STORAGE_TOTAL_SIZE +
|
||||
kStorageSize,
|
||||
kReservedStorageSize,
|
||||
"pairing storage offset underflows flash");
|
||||
static_assert(kStorageOffset + kStorageSize == kProfileStorageOffset);
|
||||
static_assert(kLeftStorageOffset + kStorageSize == kRightStorageOffset);
|
||||
static_assert(kRightStorageOffset + kStorageSize == kProfileStorageOffset);
|
||||
static_assert(kLeftStorageOffset + kReservedStorageSize == kProfileStorageOffset);
|
||||
static_assert(kProfileStorageOffset + PROFILE_STORAGE_TOTAL_SIZE ==
|
||||
kConfigurationStorageOffset);
|
||||
static_assert(kConfigurationStorageOffset + kConfigurationStorageSize ==
|
||||
|
|
@ -119,22 +125,23 @@ bool is_erased(const uint8_t *bytes, size_t size) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool storage_region_available() {
|
||||
bool storage_region_available(uint32_t storage_offset) {
|
||||
const uintptr_t binary_end = reinterpret_cast<uintptr_t>(&__flash_binary_end);
|
||||
return binary_end >= XIP_BASE &&
|
||||
binary_end - XIP_BASE <= kStorageOffset &&
|
||||
kStorageOffset % FLASH_SECTOR_SIZE == 0 &&
|
||||
kStorageOffset <= PICO_FLASH_SIZE_BYTES &&
|
||||
kStorageSize <= PICO_FLASH_SIZE_BYTES - kStorageOffset &&
|
||||
kStorageOffset + kStorageSize == kProfileStorageOffset;
|
||||
binary_end - XIP_BASE <= kLeftStorageOffset &&
|
||||
storage_offset % FLASH_SECTOR_SIZE == 0 &&
|
||||
storage_offset <= PICO_FLASH_SIZE_BYTES &&
|
||||
kStorageSize <= PICO_FLASH_SIZE_BYTES - storage_offset &&
|
||||
storage_offset >= kLeftStorageOffset &&
|
||||
storage_offset + kStorageSize <= kProfileStorageOffset;
|
||||
}
|
||||
|
||||
uint32_t slot_offset(size_t slot) {
|
||||
return static_cast<uint32_t>(kStorageOffset + slot * FLASH_SECTOR_SIZE);
|
||||
uint32_t slot_offset(uint32_t storage_offset, size_t slot) {
|
||||
return static_cast<uint32_t>(storage_offset + slot * FLASH_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
const uint8_t *slot_bytes(size_t slot) {
|
||||
return reinterpret_cast<const uint8_t *>(XIP_BASE + slot_offset(slot));
|
||||
const uint8_t *slot_bytes(uint32_t storage_offset, size_t slot) {
|
||||
return reinterpret_cast<const uint8_t *>(XIP_BASE + slot_offset(storage_offset, slot));
|
||||
}
|
||||
|
||||
bool owner_valid(const uint8_t *bytes, uint32_t offset) {
|
||||
|
|
@ -180,10 +187,10 @@ bool commit_valid(const uint8_t *bytes, uint32_t offset) {
|
|||
FLASH_PAGE_SIZE - kDescriptorSize);
|
||||
}
|
||||
|
||||
Slot inspect_slot(size_t index) {
|
||||
const uint8_t *bytes = slot_bytes(index);
|
||||
Slot inspect_slot(uint32_t storage_offset, size_t index) {
|
||||
const uint8_t *bytes = slot_bytes(storage_offset, index);
|
||||
Slot slot{SlotKind::Unknown, bytes, 0, 0};
|
||||
if (!owner_valid(bytes, slot_offset(index))) {
|
||||
if (!owner_valid(bytes, slot_offset(storage_offset, index))) {
|
||||
if (is_erased(bytes, FLASH_SECTOR_SIZE)) {
|
||||
slot.kind = SlotKind::Erased;
|
||||
}
|
||||
|
|
@ -198,7 +205,7 @@ Slot inspect_slot(size_t index) {
|
|||
// A complete ownership page plus an erased tail proves ownership of the
|
||||
// bounded body/commit area, even if either subsequent write was interrupted.
|
||||
slot.kind = SlotKind::OwnedIncomplete;
|
||||
if (body_valid(bytes) && commit_valid(bytes, slot_offset(index))) {
|
||||
if (body_valid(bytes) && commit_valid(bytes, slot_offset(storage_offset, index))) {
|
||||
slot.kind = SlotKind::Committed;
|
||||
slot.generation = read_u32(bytes + kBodyOffset + 8);
|
||||
slot.size = read_u32(bytes + kBodyOffset + 16);
|
||||
|
|
@ -245,37 +252,37 @@ void perform_flash_mutation(void *context) {
|
|||
|
||||
// The caller has classified BOTH sectors before permitting any erase. Only
|
||||
// the inactive, explicitly owned sector is passed here; the active one survives.
|
||||
bool erase_slot(size_t index) {
|
||||
if (index >= kSlotCount || !storage_region_available()) {
|
||||
bool erase_slot(uint32_t storage_offset, size_t index) {
|
||||
if (index >= kSlotCount || !storage_region_available(storage_offset)) {
|
||||
return false;
|
||||
}
|
||||
FlashMutation mutation{slot_offset(index), nullptr};
|
||||
FlashMutation mutation{slot_offset(storage_offset, index), nullptr};
|
||||
return flash_safe_execute(perform_flash_mutation, &mutation,
|
||||
kFlashSafeTimeoutMs) == PICO_OK &&
|
||||
is_erased(slot_bytes(index), FLASH_SECTOR_SIZE);
|
||||
is_erased(slot_bytes(storage_offset, index), FLASH_SECTOR_SIZE);
|
||||
}
|
||||
|
||||
bool program_page(size_t index, size_t offset, const uint8_t *page) {
|
||||
bool program_page(uint32_t storage_offset, size_t index, size_t offset, const uint8_t *page) {
|
||||
if (index >= kSlotCount || offset % FLASH_PAGE_SIZE != 0 ||
|
||||
offset > kRecordFootprint - FLASH_PAGE_SIZE ||
|
||||
!storage_region_available() ||
|
||||
!is_erased(slot_bytes(index) + offset, FLASH_PAGE_SIZE)) {
|
||||
!storage_region_available(storage_offset) ||
|
||||
!is_erased(slot_bytes(storage_offset, index) + offset, FLASH_PAGE_SIZE)) {
|
||||
return false;
|
||||
}
|
||||
FlashMutation mutation{
|
||||
static_cast<uint32_t>(slot_offset(index) + offset), page,
|
||||
static_cast<uint32_t>(slot_offset(storage_offset, index) + offset), page,
|
||||
};
|
||||
return flash_safe_execute(perform_flash_mutation, &mutation,
|
||||
kFlashSafeTimeoutMs) == PICO_OK &&
|
||||
memcmp(slot_bytes(index) + offset, page, FLASH_PAGE_SIZE) == 0;
|
||||
memcmp(slot_bytes(storage_offset, index) + offset, page, FLASH_PAGE_SIZE) == 0;
|
||||
}
|
||||
|
||||
void prepare_record(size_t target, uint32_t generation,
|
||||
void prepare_record(uint32_t storage_offset, size_t target, uint32_t generation,
|
||||
const uint8_t *data, size_t size) {
|
||||
memset(staging, 0xff, sizeof(staging));
|
||||
memcpy(staging, kOwnerMagic, sizeof(kOwnerMagic));
|
||||
write_u32(staging + 16, kFormatVersion);
|
||||
write_u32(staging + 20, slot_offset(target));
|
||||
write_u32(staging + 20, slot_offset(storage_offset, target));
|
||||
write_u32(staging + 24, kMaximumPayloadSize);
|
||||
write_u32(staging + 28, FLASH_PAGE_SIZE);
|
||||
write_u32(staging + 32, FLASH_SECTOR_SIZE);
|
||||
|
|
@ -300,19 +307,23 @@ void prepare_record(size_t target, uint32_t generation,
|
|||
write_u32(commit + 20, header_crc);
|
||||
write_u32(commit + 24, payload_crc);
|
||||
write_u32(commit + 28, static_cast<uint32_t>(size));
|
||||
write_u32(commit + 32, slot_offset(target));
|
||||
write_u32(commit + 32, slot_offset(storage_offset, target));
|
||||
write_u32(commit + kDescriptorCrcOffset,
|
||||
configuration_crc32(commit, kDescriptorCrcOffset));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool probe_storage_load(uint8_t *output, size_t size) {
|
||||
bool probe_storage_load(uint8_t instance, uint8_t *output, size_t size) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) return false;
|
||||
const uint32_t storage_offset = probe_storage_offset(instance);
|
||||
if (output == nullptr || size == 0 || size > kMaximumPayloadSize ||
|
||||
!storage_region_available()) {
|
||||
!storage_region_available(storage_offset)) {
|
||||
return false;
|
||||
}
|
||||
const Slot slots[kSlotCount] = {inspect_slot(0), inspect_slot(1)};
|
||||
const Slot slots[kSlotCount] = {
|
||||
inspect_slot(storage_offset, 0), inspect_slot(storage_offset, 1),
|
||||
};
|
||||
int active;
|
||||
if (!newest_slot(slots, &active) || active < 0 || slots[active].size != size) {
|
||||
return false;
|
||||
|
|
@ -321,12 +332,16 @@ bool probe_storage_load(uint8_t *output, size_t size) {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool probe_storage_save(const uint8_t *data, size_t size) {
|
||||
bool probe_storage_save(uint8_t instance, const uint8_t *data, size_t size) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) return false;
|
||||
const uint32_t storage_offset = probe_storage_offset(instance);
|
||||
if (data == nullptr || size == 0 || size > kMaximumPayloadSize ||
|
||||
!storage_region_available()) {
|
||||
!storage_region_available(storage_offset)) {
|
||||
return false;
|
||||
}
|
||||
const Slot slots[kSlotCount] = {inspect_slot(0), inspect_slot(1)};
|
||||
const Slot slots[kSlotCount] = {
|
||||
inspect_slot(storage_offset, 0), inspect_slot(storage_offset, 1),
|
||||
};
|
||||
if (slots[0].kind == SlotKind::Unknown || slots[1].kind == SlotKind::Unknown) {
|
||||
return false; // Never erase through an unrecognized region.
|
||||
}
|
||||
|
|
@ -342,32 +357,33 @@ bool probe_storage_save(const uint8_t *data, size_t size) {
|
|||
? static_cast<size_t>(active) ^ 1u
|
||||
: (slots[0].kind == SlotKind::Erased ? 0u : 1u);
|
||||
const uint32_t generation = active >= 0 ? slots[active].generation + 1u : 1u;
|
||||
prepare_record(target, generation, data, size);
|
||||
prepare_record(storage_offset, target, generation, data, size);
|
||||
|
||||
if (slots[target].kind != SlotKind::Erased && !erase_slot(target)) {
|
||||
if (slots[target].kind != SlotKind::Erased && !erase_slot(storage_offset, target)) {
|
||||
return false;
|
||||
}
|
||||
if (!program_page(target, 0, staging)) {
|
||||
if (!program_page(storage_offset, target, 0, staging)) {
|
||||
return false;
|
||||
}
|
||||
for (size_t offset = kBodyOffset; offset < kCommitOffset;
|
||||
offset += FLASH_PAGE_SIZE) {
|
||||
if (!is_erased(staging + offset, FLASH_PAGE_SIZE) &&
|
||||
!program_page(target, offset, staging + offset)) {
|
||||
!program_page(storage_offset, target, offset, staging + offset)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!body_valid(slot_bytes(target)) ||
|
||||
!program_page(target, kCommitOffset, staging + kCommitOffset)) {
|
||||
if (!body_valid(slot_bytes(storage_offset, target)) ||
|
||||
!program_page(storage_offset, target, kCommitOffset, staging + kCommitOffset)) {
|
||||
return false;
|
||||
}
|
||||
const Slot committed = inspect_slot(target);
|
||||
const Slot committed = inspect_slot(storage_offset, target);
|
||||
return committed.kind == SlotKind::Committed &&
|
||||
committed.generation == generation && committed.size == size &&
|
||||
memcmp(committed.bytes + kPayloadOffset,
|
||||
staging + kPayloadOffset, size) == 0;
|
||||
}
|
||||
|
||||
uint32_t probe_storage_offset(void) {
|
||||
return kStorageOffset;
|
||||
uint32_t probe_storage_offset(uint8_t instance) {
|
||||
if (instance >= PROBE_CONTROLLER_COUNT) return UINT32_MAX;
|
||||
return probe_model_is_left(instance) ? kLeftStorageOffset : kRightStorageOffset;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,14 +11,18 @@ extern "C" {
|
|||
// Synchronous, main-loop-only API for the single-core probe. Serialize calls.
|
||||
// Blobs are opaque, nonempty, and at most 512 bytes. Load requires an exact
|
||||
// length match and leaves output unchanged on failure; it never writes flash.
|
||||
bool probe_storage_load(uint8_t *output, size_t size);
|
||||
// Invalid instances fail before reading a bank or writing output.
|
||||
bool probe_storage_load(uint8_t instance, uint8_t *output, size_t size);
|
||||
|
||||
// Success means an identical blob was already committed, or a replacement was
|
||||
// committed and read back. Failure never authorizes a protocol acknowledgement.
|
||||
bool probe_storage_save(const uint8_t *data, size_t size);
|
||||
bool probe_storage_save(uint8_t instance, const uint8_t *data, size_t size);
|
||||
|
||||
// Flash-relative offset of the two sectors immediately below profile storage.
|
||||
uint32_t probe_storage_offset(void);
|
||||
// Flash-relative offset of the instance's two-sector pairing bank, or UINT32_MAX
|
||||
// for an invalid instance. The right bank remains immediately below profile
|
||||
// storage; the left bank occupies the preceding two sectors. Both are reserved
|
||||
// in every build, and load/save inspect and mutate only the selected bank.
|
||||
uint32_t probe_storage_offset(uint8_t instance);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
|
|||
101
tools/switch2_usb_probe/transport.h
Normal file
101
tools/switch2_usb_probe/transport.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include "model.h"
|
||||
#include "tusb.h"
|
||||
#if SWITCH2_PROBE_HUB
|
||||
#include "usb/native_hub/native_hub.h"
|
||||
#endif
|
||||
|
||||
// Application instances are controllers, never native hub device slots.
|
||||
// Only control transfers retain the transport's rhport/device-slot argument.
|
||||
static inline bool probe_transport_mounted(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_mounted(instance);
|
||||
#else
|
||||
(void)instance;
|
||||
return tud_mounted();
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool probe_transport_suspended(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_suspended(instance);
|
||||
#else
|
||||
(void)instance;
|
||||
return tud_suspended();
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool probe_transport_hid_ready(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_hid_ready(instance);
|
||||
#else
|
||||
return tud_hid_n_ready(instance);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool probe_transport_hid_report(uint8_t instance, uint8_t report_id,
|
||||
const void* data, uint16_t length) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_hid_report(instance, report_id, data, length);
|
||||
#else
|
||||
return tud_hid_n_report(instance, report_id, data, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t probe_transport_vendor_write_available(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_vendor_write_available(instance);
|
||||
#else
|
||||
return tud_vendor_n_write_available(instance);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t probe_transport_vendor_write(uint8_t instance,
|
||||
const void* data, uint32_t length) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_vendor_write(instance, data, length);
|
||||
#else
|
||||
return tud_vendor_n_write(instance, data, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline uint32_t probe_transport_vendor_write_flush(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_vendor_write_flush(instance);
|
||||
#else
|
||||
return tud_vendor_n_write_flush(instance);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void probe_transport_vendor_discard_received(uint8_t instance) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
// Native RX supplies the actual packet once, with no second receive FIFO.
|
||||
(void)instance;
|
||||
#else
|
||||
// The application consumes the raw callback packet, not this duplicate.
|
||||
uint8_t discarded[64];
|
||||
while (tud_vendor_n_available(instance)) {
|
||||
if (!tud_vendor_n_read(instance, discarded, sizeof(discarded))) break;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool probe_transport_control_xfer(uint8_t rhport,
|
||||
const tusb_control_request_t* request,
|
||||
void* buffer, uint16_t length) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_control_xfer(rhport, request, buffer, length);
|
||||
#else
|
||||
return tud_control_xfer(rhport, request, buffer, length);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline bool probe_transport_control_status(uint8_t rhport,
|
||||
const tusb_control_request_t* request) {
|
||||
#if SWITCH2_PROBE_HUB
|
||||
return native_hub_control_status(rhport, request);
|
||||
#else
|
||||
return tud_control_status(rhport, request);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
#pragma once
|
||||
|
||||
#include "model.h"
|
||||
|
||||
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED)
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||
#define CFG_TUD_HID 1
|
||||
#define CFG_TUD_HID PROBE_CONTROLLER_COUNT
|
||||
#define CFG_TUD_HID_EP_BUFSIZE 64
|
||||
#define CFG_TUD_CDC 0
|
||||
#define CFG_TUD_MSC 0
|
||||
#define CFG_TUD_MIDI 0
|
||||
#define CFG_TUD_VENDOR 1
|
||||
#define CFG_TUD_VENDOR PROBE_CONTROLLER_COUNT
|
||||
#define CFG_TUD_VENDOR_EPSIZE 64
|
||||
#define CFG_TUD_VENDOR_RX_BUFSIZE 256
|
||||
#define CFG_TUD_VENDOR_TX_BUFSIZE 256
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue