Bridges a real Bluetooth DualSense controller to a virtual USB DualSense device on Windows, enabling native DualSense support in games without a wired connection.
┌──────────────┐ BT HID ┌──────────────┐ IOCTL ┌──────────────────┐
│ DualSense │──────────────▶│ DualProxySvc │───────────▶│ VirtualDualSense │
│ (real, BT) │◀──────────────│ (service) │◀───────────│ (VHF kernel drv) │
└──────────────┘ └──────────────┘ └──────────────────┘
│ │
┌────┴────┐ Windows sees it as
│ Tray │ USB DualSense (054C:0CE6)
│ (GUI) │
└─────────┘
- VirtualDualSense.sys — VHF (Virtual HID Framework) kernel driver. Creates a virtual USB DualSense controller.
- DualProxySvc.exe — SYSTEM-level service. Connects to the real BT DualSense, forwards input to the virtual device, and forwards output (rumble/LED) back to the real controller.
- DualProxyTray.exe — System tray UI. Start/stop the bridge, toggle BT passthrough.
- Windows 10/11 x64
- Visual Studio 2022 with Desktop development with C++ workload
- Windows 10/11 WDK (10.0.28000.0+)
- Windows 10/11 SDK
- A DualSense Controller
- ViGEmBus — required for HidHide compatibility layer
- HidHide — hides the real controller from games so they only see the virtual one
- Test signing enabled:
bcdedit /set testsigning on(then reboot)
Open src\VirtualDualSense.sln in Visual Studio 2022 and build for x64 (Debug or Release).
Or from command line:
MSBuild src\VirtualDualSense.sln /p:Configuration=Debug /p:Platform=x64 /t:Build- Install ViGEmBus and HidHide (see Prerequisites), then reboot
- Enable test signing:
bcdedit /set testsigning on(requires reboot) - Run
scripts\install.ps1as Administrator - Connect your DualSense controller via Bluetooth
- The tray app
DualProxyTray.exewill start automatically
- Tray icon — right-click to start/stop the bridge, toggle BT read
- Service — runs as
DualProxySvc, starts automatically on boot - Logs —
%ProgramData%\DualProxy\logs\DualProxySvc.log
src/
├── VirtualDualSense/ # VHF kernel driver (WDF/KMDF)
│ ├── VirtualDualSense.inf
│ └── ioctl.c, hiddescriptor.h, ...
├── DualProxySvc/ # Bridge service (C++)
│ ├── bridge.cpp/.h # Core bridge logic
│ ├── main.cpp # Service entry, named pipe IPC
│ ├── hidapi.cpp/.h # HID device enumeration/communication
│ └── report.cpp/.h # Report format conversion (USB ↔ BT)
└── DualProxyTray/ # System tray GUI (C++)
scripts/
├── install.ps1 # Driver + service install
└── uninstall.ps1 # Driver + service removal
The service opens a HID connection to the real BT DualSense and creates a virtual USB DualSense via the VHF driver. It continuously:
- Reads 78-byte BT HID input reports from the real controller
- Converts them to 64-byte USB HID input reports and submits to the virtual device
- Reads 48-byte USB output reports from the virtual device (rumble/LED)
- Converts them to 78-byte BT output reports and sends to the real controller
The tray app communicates with the service over a named pipe for start/stop/status commands.
BSD 3-Clause
- Audio haptics — BT audio-to-haptics (speaker/headphone jack → haptic feedback via HID report 0x32)
- Mic LED — the microphone mute/LED state is not forwarded to the virtual device
- Firmware/battery passthrough — calibration data and battery status from the real controller are exposed but not fully interpreted by the virtual device