# Xbox Wireless Dongle: macOS Port User-space macOS app for the Xbox Wireless Dongle (MediaTek MT76xx). Ports the Linux kernel driver [`xone/`](../xone/) to macOS. See [PLAN.md](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: ```sh cmake -S . -B build -GNinja -DBUILD_TESTING=ON ninja -C build ./build/xone_app ninja -C build check # build and run the test suite ``` Layout (mirrors `../refix/`): - `deps/`: CMake modules: `Platform.cmake`, `Flags.cmake`, `Sanitizers.cmake`, `FindUnity.cmake` - `src/{usb,mt76,gip,auth,hid}`: C++ protocol stack (ported from `medusalix/xone`) - `src/app/`: Swift app entry point + C ABI bridge (`include/app/xone_api.h`) - `src/cli/`: C++ CLI (`xone_cli`) for manual protocol sequences - `scripts/`: `download-firmware.sh` (port of the upstream firmware fetcher) - `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 - [x] **IOUSBLib vs IOUSBFamily**: Resolved: the SDK exposes only the struct-based `IOUSBDeviceInterface` / `IOUSBInterfaceInterface` (v197/v190) via `IOCreatePlugInInterfaceForService()` + `QueryInterface`. Implemented in `src/usb/usb_transport.cpp`. - [x] **USB device matching**: Class match on `IOUSBDevice` plus a user-space VID/PID filter on the `idVendor`/`idProduct` properties (numeric registry matching proved unreliable). Verified with a physical dongle (PID 0x02E6). - [x] **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. - [x] **Async transfer latency**: Resolved: the `ReadPipeAsync` pump (4 outstanding reads per IN pipe, resubmission in the completion handler) streams live controller input with no drops at game-report rates. - [x] **Device reconnect handling**: Resolved: `kIOTerminatedNotification` with a PID filter fires on unplug and chip re-enumeration. Verified across firmware-load resets and repeated unplug/replug cycles. ### 2. Firmware Loading - [x] **Firmware binary availability**: Resolved: `scripts/download-firmware.sh` (port of `../xone/install/firmware.sh`) fetches the Windows Update CAB images and the hashes match the Linux driver. Images land in `firmware/`. - [x] **Firmware load sequence**: Resolved: ported to `src/mt76/mt76.cpp` (control request to firmware mode, bulk transfer in 0x3800-byte chunks, MCU completion poll, chip reset). Verified on hardware: FCE shows the firmware running after load. - [x] **Post-firmware reconnect**: Resolved: the chip re-enumerates with the same VID/PID; the app and CLI close, wait, and reopen the device. The full sequence runs end-to-end. ### 3. MT76 Register Access - [x] **Vendor request format**: Resolved: the Linux values work unchanged via `IOUSBDeviceInterface->DeviceRequest()` (bRequest 0x84/0x86 for register R/W). All radio init traffic uses this path. - [x] **Register timing**: Resolved: `usleep()` / `clock_nanosleep()` in user-space are sufficient; radio init and channel evaluation complete reliably. - [x] **EFUSE read**: Resolved: the EFUSE sequence returns valid MAC address, chip ID, and TX power calibration data on macOS (`xone_cli info`). ### 4. 802.11 Frame Handling - [x] **Beacon construction**: Resolved: beacons with the Microsoft OUI (00:50:f2) information element transmit correctly, including the chip header before the 802.11 frame. Controllers hear them and associate. - [x] **Frame encapsulation**: Resolved: TX/RX chip headers handled per the Linux driver (`send_wlan` / `process_wlan`). Association, pairing, and encryption-enable management frames verified against hardware. - [x] **QoS data frames**: RX path verified: controller input QoS frames are decrypted, fed to the GIP layer, and shown live in the app. Host to controller TX (rumble) verified on hardware via the app's Rumble button. ### 5. AES-CCMP Encryption - [x] **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`. - [x] **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`). - [x] **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](https://developer.apple.com/documentation/coreaudio/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 `IOHIDManager` and 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` / `AUOutputUnit` for headset playback and `AURecordingCallback` / `AUInputUnit` for 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 - [x] **CMake vs Xcode**: Resolved: CMake with the Ninja generator compiles both the C++ stack and the Swift app (see Building). An Xcode project is only needed later for a DriverKit extension, if Phase 4 goes that route. - [ ] **Minimum macOS version**: Target 12.0 (Monterey) for modern IOKit/DriverKit. Verify all APIs are available. - [ ] **Code signing**: Development builds run unsigned on the local machine with no entitlements needed for IOKit USB access. DriverKit notarization and distribution signing deferred to the HID phase. ### 9. Regulatory / Legal - [ ] **5GHz channel restrictions**: Channel evaluation picks a working channel and pairing is verified on hardware; no regulatory failures seen so far. Regional edge cases (blocked channels) remain untested. - [ ] **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 - [x] **Dongle**: Xbox Wireless Dongle (PID 0x02FE preferred, 0x02E6 also works). In hand: PID 0x02E6 verified end-to-end. - [x] **Controller**: Xbox One or Series X|S controller (for pairing and input testing). In hand: pairing and live input verified. - [ ] **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()` | ✅ Done (radio init, channel eval) | | `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 | --- ## Current Status Working end-to-end on hardware: - Dongle probe/open, async read pump, vendor register R/W - Firmware download (`scripts/download-firmware.sh`) and load - EFUSE read, radio init, channel evaluation, beacon TX - Pairing mode, controller association, GIP handshake + auth - Live controller input (buttons, sticks, triggers) in the Swift app - Rumble test button (host to controller TX over the GIP data path) Not done yet: virtual HID gamepad (Phase 4), headset audio (Phase 5). `xone_cli` exercises the stack manually in a single dongle session: ```sh ./build/xone_cli info firmware firmware/xow_dongle.bin radio-init pair ```