4f5f16e43f167cdc5ebb7ee05ab6934b4e48bf77
Add an opaque xone_dongle session handle to the C ABI bridge: xone_open() probes, resets, and loads the firmware (path optional), and xone_pid/xone_chip_id/xone_mac_address/xone_firmware_build return debug info for the open session. chip captures the firmware build string from the image header during load. The Swift app opens a session when the dongle appears and shows PID, chip ID, MAC address, and firmware build string in a debug section. Co-Authored-By: qwen3.8-27b@q2_k_xl: added C API session handle and debug UI
Xbox Wireless Dongle — macOS Port
User-space macOS app for the Xbox Wireless Dongle (MediaTek MT76xx).
Ports the Linux kernel driver xone/ to macOS.
See PLAN.md for full architecture and implementation phases.
Building
Requires CMake ≥ 3.21, Xcode (or Command Line Tools), and Ninja. Swift is only supported by the Ninja and Xcode generators in CMake, so configure with one of them explicitly:
cmake -S . -B build -G Ninja -DBUILD_TESTING=ON
cmake --build build
./build/xone_app
cmake --build build --target check # run the test suite
Layout (mirrors ../refix/):
deps/— CMake modules:Platform.cmake,Flags.cmake,Sanitizers.cmake,FindUnity.cmakesrc/{usb,mt76,gip,auth,hid}— C++ protocol stack (ported frommedusalix/xone)src/app/— Swift app entry point + C ABI bridge (include/app/xone_api.h)tests/— Unity test suite (BUILD_TESTING=ON)
Investigation Checklist
These items need research on macOS before implementation can begin. Strike through or check off as each is resolved.
1. IOKit USB Access
- IOUSBLib vs IOUSBFamily — Resolved: the SDK exposes only the
struct-based
IOUSBDeviceInterface/IOUSBInterfaceInterface(v197/v190) viaIOCreatePlugInInterfaceForService()+QueryInterface. Implemented insrc/usb/usb_transport.cpp. - USB device matching — Class match on
IOUSBDeviceplus a user-space VID/PID filter on theidVendor/idProductproperties (numeric registry matching proved unreliable). Verified with a physical dongle (PID 0x02E6). - Interface claiming — All interfaces are opened and pipes mapped by endpoint number + direction (EP 0x04 IN/OUT, EP 0x05 IN). Verified: opens cleanly with no driver conflict.
- Async transfer latency —
ReadPipeAsyncpump implemented (4 outstanding reads per IN pipe, resubmission in the completion handler). Latency tuning deferred to Phase 3 firmware load. - Device reconnect handling —
kIOTerminatedNotificationwith a PID filter fires on unplug and chip re-enumeration. Reliability pending hardware.
2. Firmware Loading
- Firmware binary availability — Run
../xone/install/firmware.sh(or portedscripts/download-firmware.sh) to confirm the Windows Update CAB URLs still work and firmware hashes match. - Firmware load sequence — Trace the Linux
xone_mt76_load_firmware()flow: control request to enter firmware mode → bulk transfer in 0x3800-byte chunks → MCU completion poll → chip reset. Map each step to IOKit equivalents. - Post-firmware reconnect — After firmware loads, the chip resets and re-enumerates. Verify the USB device reappears with the same VID/PID and can be re-opened.
3. MT76 Register Access
- Vendor request format — The Linux driver uses
usb_control_msg()with vendor requests (bRequest 0x84/0x86 for register R/W). Confirm the exactbmRequestType,bRequest,wValue,wIndex,wLengthvalues work viaIOUSBDeviceInterface->DeviceRequest(). - Register timing — Some register writes require delays between them.
Test if
usleep()in user-space provides sufficient precision, or ifclock_nanosleep()is needed. - EFUSE read — Verify the EFUSE read sequence returns valid MAC address, chip ID, and TX power calibration data on macOS.
4. 802.11 Frame Handling
- Beacon construction — The Linux driver builds raw 802.11 beacon frames with a Microsoft OUI (00:50:f2) information element. Verify the frame format matches what the MT76 chip expects (may include chip-specific headers before the 802.11 frame).
- Frame encapsulation — The MT76 chip wraps 802.11 frames in a
proprietary header. Reverse-engineer or confirm the header format from Linux
driver source (
xone_mt76_tx()/xone_mt76_rx()). - QoS data frames — Controller input/output uses 802.11 QoS data frames. Verify the frame construction and AES-CCMP encryption/decryption flow.
5. AES-CCMP Encryption
- Crypto backend choice — CommonCrypto (SHA-256/HMAC), Security.framework
(RSA PKCS#1), and a self-contained P-256 ECDH (no public macOS C API for EC
key agreement). Implemented in
src/auth/crypto.cpp. - CCMP mode implementation — Not needed on the host: AES-CCMP runs on the
MT76 chip; the host only installs keys via WCID registers
(
xone_mt76_set_client_key). - ECDH key exchange — P-256 implemented and validated against
OpenSSL-derived test vectors (
tests/test_crypto.cpp).
6. Virtual HID Gamepad
- HID Proxy Driver feasibility — Research Apple's HID Proxy Driver (DriverKit). Can we create a virtual Xbox controller that games recognize natively?
- IOHIDDevice user-space alternative — Can we create a virtual HID
device entirely in user-space? Test with
IOHIDManagerand see if games (Steam, Game Center) recognize it. - HID report descriptor — Write an Xbox 360/One-compatible HID report descriptor. Test with existing Xbox controller (via Bluetooth) to capture the exact report format macOS expects.
- Force feedback — Can the virtual HID device receive rumble commands from games and relay them to the controller via GIP?
7. Core Audio (Headset Support)
- Audio Unit setup — Test creating an
AURenderCallback/AUOutputUnitfor headset playback andAURecordingCallback/AUInputUnitfor mic input. - Latency requirements — The GIP protocol sends audio in 8ms intervals. Can Core Audio maintain this latency without glitches?
- Format negotiation — The headset negotiates audio format (sample rate,
channels) via GIP. Map GIP audio formats to Core Audio
AudioStreamBasicDescription.
8. Build System
- CMake vs Xcode — CMake is simpler for the C library portions, but Xcode is needed for the macOS app bundle and any DriverKit extension. Decide on primary build system.
- Minimum macOS version — Target 12.0 (Monterey) for modern IOKit/DriverKit. Verify all APIs are available.
- Code signing — IOKit USB access may require specific entitlements
(
com.apple.kpi.iokit,com.apple.security.device.usb). DriverKit requires notarization. Plan for development vs distribution signing.
9. Regulatory / Legal
- 5GHz channel restrictions — macOS enforces regulatory domain for 5GHz. The dongle may try to use channels blocked in the current region. May need to limit to 2.4GHz only or find a way to override.
- Firmware license — The firmware binaries are from Microsoft Windows Update. Confirm they can be redistributed with the macOS port (the Linux driver includes them with a disclaimer).
10. Testing Hardware
- Dongle — Xbox Wireless Dongle (PID 0x02FE preferred, 0x02E6 also works)
- Controller — Xbox One or Series X|S controller (for pairing and input testing)
- Headset — Xbox Wireless Headset (optional, for audio testing)
- macOS machine — Intel or Apple Silicon (test both if possible, IOKit may differ)
Quick Reference
| Linux API | macOS Replacement | Status |
|---|---|---|
usb_control_msg() |
IOUSBDeviceInterface->DeviceRequest() |
✅ Done (send_vendor_request) |
usb_bulk_msg() |
WritePipe() / ReadPipeAsync() |
✅ Done (bulk_write, reader thread) |
usb_submit_urb() |
ReadPipeAsync() + CFRunLoopSource |
✅ Done (reader thread) |
kzalloc / kfree |
malloc / free |
✅ Straightforward |
spin_lock_irqsave |
std::mutex / std::condition_variable |
✅ Done (usb_transport.cpp) |
msleep / mdelay |
usleep() / clock_nanosleep() |
☐ Test timing |
crypto_shash_* |
CommonCrypto / Security.framework | ✅ Done (auth/crypto.cpp) |
input_register_device() |
HID Proxy Driver / IOHIDSystem | ☐ Investigate |
snd_pcm_* |
Core Audio (Audio Units) | ☐ Investigate |
request_firmware() |
File I/O (fopen/fread) |
✅ Straightforward |
cfg80211_* |
Nothing (no regulatory reporting) | ✅ Remove |
bus_register() |
Custom client management | ✅ Redesign |
device_create() / sysfs |
Nothing (no sysfs) | ✅ Remove |
First Steps on macOS
- Plug in the dongle, run
system_profiler SPUSBDataType— confirm it's detected - Check
log show --predicate 'subsystem == "com.apple.iokit"'— see if macOS loads any driver - Open the device with
transport::probe()and confirm endpoint enumeration (EP 0x04 IN/OUT, EP 0x05 IN) - Try a vendor control request (register read) to verify USB communication works
- Attempt firmware load with the binary from
firmware/directory
Description
Languages
C++
86.7%
Swift
8.3%
CMake
2.9%
C
1.5%
Shell
0.6%