mirror of
https://github.com/ddeverill/SteamlessController.git
synced 2026-10-18 20:59:45 -05:00
Initial release
This commit is contained in:
commit
589ed11e4a
45 files changed
+4557
No files matched your search
+25
@@ -0,0 +1,25 @@
|
||||
# Build output
|
||||
build/
|
||||
|
||||
# Loose binaries committed by mistake
|
||||
*.exe
|
||||
*.dll
|
||||
*.lib
|
||||
*.pdb
|
||||
|
||||
# Claude Code local settings
|
||||
.claude/
|
||||
|
||||
# Reverse engineering session data
|
||||
Results/
|
||||
HIDInfo
|
||||
HIDReportDescriptor.rtf
|
||||
|
||||
# CMake user files
|
||||
CMakeSettings.json
|
||||
CMakeUserPresets.json
|
||||
|
||||
# Visual Studio
|
||||
.vs/
|
||||
*.user
|
||||
*.suo
|
||||
@@ -0,0 +1,62 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(SteamlessController CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Shared HID + Steam Controller library code
|
||||
add_library(SteamController STATIC
|
||||
src/hid/HidDevice.cpp
|
||||
src/steam/SteamController.cpp
|
||||
)
|
||||
target_include_directories(SteamController PUBLIC src)
|
||||
target_link_libraries(SteamController PUBLIC hid setupapi)
|
||||
if (MSVC)
|
||||
target_compile_options(SteamController PRIVATE /W4 /WX)
|
||||
endif()
|
||||
|
||||
# Diagnostic probe tool (console)
|
||||
add_executable(SteamProbe
|
||||
src/main.cpp
|
||||
)
|
||||
target_link_libraries(SteamProbe PRIVATE SteamController)
|
||||
if (MSVC)
|
||||
target_compile_options(SteamProbe PRIVATE /W4 /WX)
|
||||
set_target_properties(SteamProbe PROPERTIES LINK_FLAGS_DEBUG "/SUBSYSTEM:CONSOLE")
|
||||
endif()
|
||||
|
||||
# ViGEmClient (built from source, static)
|
||||
add_library(ViGEmClient STATIC
|
||||
third_party/ViGEmClient-1.16.18.0/src/ViGEmClient.cpp
|
||||
)
|
||||
target_include_directories(ViGEmClient PUBLIC
|
||||
third_party/ViGEmClient-1.16.18.0/include
|
||||
)
|
||||
target_link_libraries(ViGEmClient PUBLIC setupapi)
|
||||
if (MSVC)
|
||||
target_compile_options(ViGEmClient PRIVATE /W0)
|
||||
endif()
|
||||
|
||||
# RawGameController diagnostic probe (WinRT)
|
||||
add_executable(RawControllerProbe
|
||||
src/probe/RawControllerProbe.cpp
|
||||
)
|
||||
target_link_libraries(RawControllerProbe PRIVATE windowsapp)
|
||||
if (MSVC)
|
||||
target_compile_options(RawControllerProbe PRIVATE /W4)
|
||||
endif()
|
||||
|
||||
# System tray app (no console window)
|
||||
add_executable(SteamlessController WIN32
|
||||
src/app/main.cpp
|
||||
src/app/TrayApp.cpp
|
||||
src/app/ControllerManager.cpp
|
||||
src/app/VirtualController.cpp
|
||||
src/app/TrackpadMouse.cpp
|
||||
resources/app.rc
|
||||
)
|
||||
target_include_directories(SteamlessController PRIVATE resources)
|
||||
target_link_libraries(SteamlessController PRIVATE SteamController ViGEmClient)
|
||||
if (MSVC)
|
||||
target_compile_options(SteamlessController PRIVATE /W4 /WX)
|
||||
endif()
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Dylan Deverill
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,67 @@
|
||||
# SteamlessController
|
||||
|
||||
A lightweight Windows system tray app that lets you use a **Steam Controller** as a standard gamepad — without Steam running.
|
||||
|
||||
When **Steamless Mode** is active, the app disables the controller's built-in keyboard/mouse emulation (lizard mode) and exposes it as a virtual Xbox 360 controller via [ViGEmBus](https://github.com/nefarius/ViGEmBus), making it compatible with any game that supports XInput or the Xbox controller.
|
||||
|
||||
## Features
|
||||
|
||||
- System tray icon shows connection and mode status
|
||||
- **Steamless Mode** — disables lizard mode and exposes controller as Xbox 360 gamepad
|
||||
- **Trackpad Mouse** — use the right (or left) trackpad as a mouse cursor
|
||||
- **Back Buttons for Clicking** — map R4/R5 (or L4/L5) to left/right mouse click
|
||||
- **Use Left Trackpad Instead** — mirror all trackpad/back-button functionality to the left side for left-handed users
|
||||
- **Start with Windows** — launch automatically at login
|
||||
- Settings persist across restarts
|
||||
- Single-instance guard — safe to leave running
|
||||
|
||||
## Requirements
|
||||
|
||||
### To run
|
||||
- Windows 10 or later (64-bit)
|
||||
- [ViGEmBus](https://github.com/nefarius/ViGEmBus/releases/latest) driver installed
|
||||
- Steam Controller (VID `0x28DE` / PID `0x1302`)
|
||||
- Steam **closed** (Steam claims the controller when running)
|
||||
|
||||
### To build
|
||||
- [Visual Studio 2022](https://visualstudio.microsoft.com/) with the **Desktop development with C++** workload
|
||||
- [CMake](https://cmake.org/download/) 3.20 or later (included with Visual Studio, or install separately)
|
||||
- Windows SDK 10.0.22000 or later (installed via Visual Studio Installer)
|
||||
|
||||
## Building
|
||||
|
||||
```bat
|
||||
git clone https://github.com/your-username/SteamlessController.git
|
||||
cd SteamlessController
|
||||
cmake -B build
|
||||
cmake --build build --target SteamlessController
|
||||
```
|
||||
|
||||
The executable will be at `build\Debug\SteamlessController.exe`.
|
||||
|
||||
For a release build:
|
||||
|
||||
```bat
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build build --config Release --target SteamlessController
|
||||
```
|
||||
|
||||
## CMake Targets
|
||||
|
||||
| Target | Description |
|
||||
|---|---|
|
||||
| `SteamlessController` | Main system tray application |
|
||||
| `SteamProbe` | Console diagnostic tool — dumps raw HID report bytes as you interact with the controller. Useful for protocol research. |
|
||||
| `RawControllerProbe` | Checks whether `Windows.Gaming.Input.RawGameController` can enumerate the Steam Controller (requires WinRT). |
|
||||
|
||||
## How it works
|
||||
|
||||
The Steam Controller exposes a vendor HID collection (usage page `0xFF00`) that carries all game input in a 54-byte report (ID `0x42`) at ~60 Hz. By default the firmware runs in **lizard mode**, emulating a keyboard and mouse so the controller works without drivers.
|
||||
|
||||
SteamlessController sends HID feature reports to disable lizard mode, then reads the raw input reports and translates them into a virtual Xbox 360 controller via ViGEmBus. A background heartbeat re-sends the disable command every 800 ms to keep lizard mode off.
|
||||
|
||||
The full input report layout is documented in [`src/steam/SteamController.h`](src/steam/SteamController.h).
|
||||
|
||||
## Third-party
|
||||
|
||||
- [ViGEmClient](https://github.com/nefarius/ViGEmClient) — MIT License, built from source as a static library
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1,4 @@
|
||||
#include "resource.h"
|
||||
|
||||
IDI_ICON_OFF ICON "SteamControllerOFF.ico"
|
||||
IDI_ICON_ON ICON "SteamControllerON.ico"
|
||||
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
#define IDI_ICON_OFF 101
|
||||
#define IDI_ICON_ON 102
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "ControllerManager.h"
|
||||
#include "VirtualController.h"
|
||||
#include "steam/SteamController.h"
|
||||
#include <memory>
|
||||
|
||||
static std::unique_ptr<SteamController> g_ctrl;
|
||||
|
||||
ControllerManager::ControllerManager(StateChangedFn onStateChanged)
|
||||
: m_onStateChanged(std::move(onStateChanged))
|
||||
{
|
||||
TryOpen();
|
||||
}
|
||||
|
||||
ControllerManager::~ControllerManager() {
|
||||
Close(/*restoreLizard=*/true);
|
||||
}
|
||||
|
||||
void ControllerManager::OnDeviceChange() {
|
||||
if (!m_connected)
|
||||
TryOpen();
|
||||
else if (g_ctrl && !g_ctrl->IsOpen())
|
||||
Close(/*restoreLizard=*/false);
|
||||
}
|
||||
|
||||
void ControllerManager::EnableGameMode() {
|
||||
if (!m_connected || m_gameModeActive) return;
|
||||
if (!g_ctrl->DisableLizardMode()) return;
|
||||
|
||||
m_virtual = std::make_unique<VirtualController>();
|
||||
if (!m_virtual->IsValid()) {
|
||||
bool missing = m_virtual->IsDriverMissing();
|
||||
m_virtual.reset();
|
||||
g_ctrl->EnableLizardMode();
|
||||
if (missing) m_onStateChanged(m_connected, m_gameModeActive, /*vigemMissing=*/true);
|
||||
return;
|
||||
}
|
||||
|
||||
m_gameModeActive = true;
|
||||
m_trackpad.Reset();
|
||||
m_trackpad.SetTrackpadEnabled(m_trackpadMouseEnabled);
|
||||
m_trackpad.SetBackButtonsEnabled(m_backButtonsEnabled);
|
||||
m_trackpad.SetUseLeftTrackpad(m_useLeftTrackpad);
|
||||
StartReadLoop();
|
||||
m_onStateChanged(m_connected, m_gameModeActive, false);
|
||||
}
|
||||
|
||||
void ControllerManager::DisableGameMode() {
|
||||
if (!m_gameModeActive) return;
|
||||
StopReadLoop();
|
||||
m_trackpad.Reset();
|
||||
m_virtual.reset();
|
||||
g_ctrl->EnableLizardMode();
|
||||
m_gameModeActive = false;
|
||||
m_onStateChanged(m_connected, m_gameModeActive, false);
|
||||
}
|
||||
|
||||
void ControllerManager::SetTrackpadMouseEnabled(bool enabled) {
|
||||
m_trackpadMouseEnabled = enabled;
|
||||
m_trackpad.SetTrackpadEnabled(enabled);
|
||||
}
|
||||
|
||||
void ControllerManager::SetBackButtonsEnabled(bool enabled) {
|
||||
m_backButtonsEnabled = enabled;
|
||||
m_trackpad.SetBackButtonsEnabled(enabled);
|
||||
}
|
||||
|
||||
void ControllerManager::SetUseLeftTrackpad(bool enabled) {
|
||||
m_useLeftTrackpad = enabled;
|
||||
m_trackpad.SetUseLeftTrackpad(enabled);
|
||||
}
|
||||
|
||||
void ControllerManager::TryOpen() {
|
||||
if (!g_ctrl) g_ctrl = std::make_unique<SteamController>();
|
||||
if (g_ctrl->Open()) {
|
||||
m_connected = true;
|
||||
m_onStateChanged(m_connected, m_gameModeActive, false);
|
||||
}
|
||||
}
|
||||
|
||||
void ControllerManager::Close(bool restoreLizard) {
|
||||
StopReadLoop();
|
||||
m_virtual.reset();
|
||||
if (g_ctrl) {
|
||||
if (restoreLizard && m_gameModeActive)
|
||||
g_ctrl->EnableLizardMode();
|
||||
g_ctrl->Close();
|
||||
}
|
||||
m_connected = false;
|
||||
m_gameModeActive = false;
|
||||
m_onStateChanged(m_connected, m_gameModeActive, false);
|
||||
}
|
||||
|
||||
void ControllerManager::StartReadLoop() {
|
||||
m_readRunning = true;
|
||||
m_readThread = std::thread(&ControllerManager::ReadLoop, this);
|
||||
}
|
||||
|
||||
void ControllerManager::StopReadLoop() {
|
||||
m_readRunning = false;
|
||||
if (m_readThread.joinable())
|
||||
m_readThread.join();
|
||||
}
|
||||
|
||||
void ControllerManager::ReadLoop() {
|
||||
uint8_t buf[64];
|
||||
while (m_readRunning) {
|
||||
size_t n = g_ctrl->ReadReport(buf, sizeof(buf), /*timeoutMs=*/32);
|
||||
if (n == 0) continue;
|
||||
if (buf[0] != SteamController::REPORT_STATE) continue;
|
||||
if (m_virtual) m_virtual->Update(buf, n);
|
||||
m_trackpad.Update(buf, n);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
#include "TrackpadMouse.h"
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
class VirtualController;
|
||||
|
||||
// Manages the Steam Controller lifecycle: device discovery, lizard mode
|
||||
// disable/enable, and the heartbeat that keeps lizard mode off.
|
||||
// All public methods are safe to call from the UI thread.
|
||||
class ControllerManager {
|
||||
public:
|
||||
using StateChangedFn = std::function<void(bool connected, bool gameModeActive, bool vigemMissing)>;
|
||||
|
||||
explicit ControllerManager(StateChangedFn onStateChanged);
|
||||
~ControllerManager();
|
||||
ControllerManager(const ControllerManager&) = delete;
|
||||
ControllerManager& operator=(const ControllerManager&) = delete;
|
||||
|
||||
// Called when Windows reports a device arrival or removal (WM_DEVICECHANGE).
|
||||
void OnDeviceChange();
|
||||
|
||||
// Toggle game mode on/off. No-op if controller is not connected.
|
||||
void EnableGameMode();
|
||||
void DisableGameMode();
|
||||
|
||||
void SetTrackpadMouseEnabled(bool enabled);
|
||||
void SetBackButtonsEnabled(bool enabled);
|
||||
void SetUseLeftTrackpad(bool enabled);
|
||||
|
||||
bool IsConnected() const { return m_connected; }
|
||||
bool IsGameModeActive() const { return m_gameModeActive; }
|
||||
bool IsTrackpadMouseEnabled() const { return m_trackpadMouseEnabled; }
|
||||
bool IsBackButtonsEnabled() const { return m_backButtonsEnabled; }
|
||||
bool IsUseLeftTrackpad() const { return m_useLeftTrackpad; }
|
||||
|
||||
private:
|
||||
void TryOpen();
|
||||
void Close(bool restoreLizard);
|
||||
void StartReadLoop();
|
||||
void StopReadLoop();
|
||||
void ReadLoop();
|
||||
|
||||
StateChangedFn m_onStateChanged;
|
||||
bool m_connected = false;
|
||||
bool m_gameModeActive = false;
|
||||
bool m_trackpadMouseEnabled = false;
|
||||
bool m_backButtonsEnabled = false;
|
||||
bool m_useLeftTrackpad = false;
|
||||
std::unique_ptr<VirtualController> m_virtual;
|
||||
TrackpadMouse m_trackpad;
|
||||
std::thread m_readThread;
|
||||
std::atomic<bool> m_readRunning{false};
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
#include "TrackpadMouse.h"
|
||||
#include "steam/SteamController.h"
|
||||
#include <Windows.h>
|
||||
#include <cstring>
|
||||
|
||||
static constexpr uint8_t BTN_TP_RT_CLICK = 0x40; // buf[4] bit 6 — hard press
|
||||
|
||||
static void SendMouseButton(DWORD flags) {
|
||||
INPUT input{};
|
||||
input.type = INPUT_MOUSE;
|
||||
input.mi.dwFlags = flags;
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
}
|
||||
|
||||
void TrackpadMouse::Reset() {
|
||||
if (m_prevClick) SendMouseButton(MOUSEEVENTF_LEFTUP);
|
||||
if (m_prevR4) SendMouseButton(MOUSEEVENTF_LEFTUP);
|
||||
if (m_prevR5) SendMouseButton(MOUSEEVENTF_RIGHTUP);
|
||||
m_touching = false;
|
||||
m_prevClick = false;
|
||||
m_prevR4 = false;
|
||||
m_prevR5 = false;
|
||||
m_prevX = 0;
|
||||
m_prevY = 0;
|
||||
}
|
||||
|
||||
void TrackpadMouse::Update(const uint8_t* buf, size_t n) {
|
||||
if (n < 30) return;
|
||||
|
||||
const uint8_t b0 = buf[2];
|
||||
const uint8_t b1 = buf[3];
|
||||
const uint8_t b2 = buf[4];
|
||||
const uint8_t b3 = buf[5];
|
||||
|
||||
// --- Trackpad mouse movement and click ---
|
||||
if (m_trackpadEnabled) {
|
||||
const bool touching = m_useLeftTrackpad
|
||||
? (b3 & SteamController::BTN_TP_LT) != 0
|
||||
: (b2 & SteamController::BTN_TP_RT) != 0;
|
||||
const bool clicking = m_useLeftTrackpad
|
||||
? (b3 & SteamController::BTN_TP_LT_CLICK) != 0
|
||||
: (b2 & BTN_TP_RT_CLICK) != 0;
|
||||
|
||||
int16_t x = 0, y = 0;
|
||||
if (m_useLeftTrackpad) {
|
||||
memcpy(&x, buf + 18, 2);
|
||||
memcpy(&y, buf + 20, 2);
|
||||
} else {
|
||||
memcpy(&x, buf + 24, 2);
|
||||
memcpy(&y, buf + 26, 2);
|
||||
}
|
||||
|
||||
if (touching && m_touching) {
|
||||
const int dx = static_cast<int>(x - m_prevX);
|
||||
const int dy = -static_cast<int>(y - m_prevY); // negate: up = up
|
||||
if (dx != 0 || dy != 0) {
|
||||
INPUT input{};
|
||||
input.type = INPUT_MOUSE;
|
||||
input.mi.dwFlags = MOUSEEVENTF_MOVE;
|
||||
input.mi.dx = static_cast<LONG>(dx * SENSITIVITY);
|
||||
input.mi.dy = static_cast<LONG>(dy * SENSITIVITY);
|
||||
SendInput(1, &input, sizeof(INPUT));
|
||||
}
|
||||
}
|
||||
|
||||
if (touching) { m_prevX = x; m_prevY = y; }
|
||||
m_touching = touching;
|
||||
|
||||
if (clicking != m_prevClick) {
|
||||
SendMouseButton(clicking ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP);
|
||||
m_prevClick = clicking;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Back buttons: left side uses L4/L5, right side uses R4/R5 ---
|
||||
if (m_backButtonsEnabled) {
|
||||
const bool btn1 = m_useLeftTrackpad
|
||||
? (b2 & SteamController::BTN_L4) != 0 // L4 = left click
|
||||
: (b0 & SteamController::BTN_R4) != 0; // R4 = left click
|
||||
const bool btn2 = m_useLeftTrackpad
|
||||
? (b2 & SteamController::BTN_L5) != 0 // L5 = right click
|
||||
: (b1 & SteamController::BTN_R5) != 0; // R5 = right click
|
||||
|
||||
if (btn1 != m_prevR4) {
|
||||
SendMouseButton(btn1 ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP);
|
||||
m_prevR4 = btn1;
|
||||
}
|
||||
if (btn2 != m_prevR5) {
|
||||
SendMouseButton(btn2 ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP);
|
||||
m_prevR5 = btn2;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
class TrackpadMouse {
|
||||
public:
|
||||
void SetTrackpadEnabled(bool enabled) { m_trackpadEnabled = enabled; }
|
||||
void SetBackButtonsEnabled(bool enabled) { m_backButtonsEnabled = enabled; }
|
||||
void SetUseLeftTrackpad(bool enabled) { m_useLeftTrackpad = enabled; }
|
||||
|
||||
void Update(const uint8_t* buf, size_t n);
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
bool m_trackpadEnabled = false;
|
||||
bool m_backButtonsEnabled = false;
|
||||
bool m_useLeftTrackpad = false;
|
||||
|
||||
bool m_touching = false;
|
||||
bool m_prevClick = false;
|
||||
bool m_prevR4 = false;
|
||||
bool m_prevR5 = false;
|
||||
int16_t m_prevX = 0;
|
||||
int16_t m_prevY = 0;
|
||||
|
||||
static constexpr float SENSITIVITY = 0.015f;
|
||||
};
|
||||
@@ -0,0 +1,257 @@
|
||||
#include "TrayApp.h"
|
||||
#include "ControllerManager.h"
|
||||
#include "resource.h"
|
||||
#include <shellapi.h>
|
||||
#include <dbt.h>
|
||||
#include <winreg.h>
|
||||
|
||||
static TrayApp* g_app = nullptr;
|
||||
|
||||
static constexpr wchar_t WNDCLASS_NAME[] = L"SteamlessControllerTray";
|
||||
|
||||
TrayApp::TrayApp() {
|
||||
g_app = this;
|
||||
}
|
||||
|
||||
TrayApp::~TrayApp() {
|
||||
RemoveTrayIcon();
|
||||
g_app = nullptr;
|
||||
}
|
||||
|
||||
bool TrayApp::Init(HINSTANCE hInstance) {
|
||||
m_hInstance = hInstance;
|
||||
m_iconOff = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ICON_OFF));
|
||||
m_iconOn = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ICON_ON));
|
||||
m_wmTaskbar = RegisterWindowMessageW(L"TaskbarCreated");
|
||||
|
||||
WNDCLASSEXW wc{};
|
||||
wc.cbSize = sizeof(wc);
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = hInstance;
|
||||
wc.lpszClassName = WNDCLASS_NAME;
|
||||
if (!RegisterClassExW(&wc)) return false;
|
||||
|
||||
// Message-only window — invisible, never shown.
|
||||
m_hwnd = CreateWindowExW(0, WNDCLASS_NAME, L"SteamlessController",
|
||||
0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, hInstance, nullptr);
|
||||
if (!m_hwnd) return false;
|
||||
|
||||
// Register for HID device arrival/removal notifications.
|
||||
DEV_BROADCAST_DEVICEINTERFACE_W filter{};
|
||||
filter.dbcc_size = sizeof(filter);
|
||||
filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
|
||||
// HID device interface GUID
|
||||
filter.dbcc_classguid = {0x4D1E55B2, 0xF16F, 0x11CF,
|
||||
{0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30}};
|
||||
RegisterDeviceNotificationW(m_hwnd, &filter, DEVICE_NOTIFY_WINDOW_HANDLE);
|
||||
|
||||
m_controller = std::make_unique<ControllerManager>(
|
||||
[this](bool connected, bool gameModeActive, bool vigemMissing) {
|
||||
UpdateTrayIcon(connected, gameModeActive, vigemMissing);
|
||||
});
|
||||
|
||||
LoadSettings();
|
||||
AddTrayIcon();
|
||||
return true;
|
||||
}
|
||||
|
||||
int TrayApp::Run() {
|
||||
MSG msg;
|
||||
BOOL ret;
|
||||
while ((ret = GetMessageW(&msg, nullptr, 0, 0)) != 0) {
|
||||
if (ret == -1) return -1;
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
return static_cast<int>(msg.wParam);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK TrayApp::WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
if (g_app) return g_app->HandleMessage(hwnd, msg, wp, lp);
|
||||
return DefWindowProcW(hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
LRESULT TrayApp::HandleMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
if (msg == m_wmTaskbar) {
|
||||
AddTrayIcon();
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (msg) {
|
||||
case WM_TRAY:
|
||||
if (LOWORD(lp) == NIN_BALLOONUSERCLICK)
|
||||
ShellExecuteW(nullptr, L"open", L"https://github.com/nefarius/ViGEmBus/releases/latest",
|
||||
nullptr, nullptr, SW_SHOWNORMAL);
|
||||
else if (LOWORD(lp) == WM_RBUTTONUP || LOWORD(lp) == WM_LBUTTONUP)
|
||||
ShowContextMenu();
|
||||
return 0;
|
||||
|
||||
case WM_COMMAND:
|
||||
switch (LOWORD(wp)) {
|
||||
case IDM_TOGGLE:
|
||||
if (m_controller->IsGameModeActive())
|
||||
m_controller->DisableGameMode();
|
||||
else
|
||||
m_controller->EnableGameMode();
|
||||
break;
|
||||
case IDM_TRACKPAD:
|
||||
m_controller->SetTrackpadMouseEnabled(!m_controller->IsTrackpadMouseEnabled());
|
||||
SaveSettings();
|
||||
break;
|
||||
case IDM_BACKBUTTONS:
|
||||
m_controller->SetBackButtonsEnabled(!m_controller->IsBackButtonsEnabled());
|
||||
SaveSettings();
|
||||
break;
|
||||
case IDM_LEFT_TRACKPAD:
|
||||
m_controller->SetUseLeftTrackpad(!m_controller->IsUseLeftTrackpad());
|
||||
SaveSettings();
|
||||
break;
|
||||
case IDM_EXIT:
|
||||
m_controller->DisableGameMode();
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DEVICECHANGE:
|
||||
if (wp == DBT_DEVICEARRIVAL || wp == DBT_DEVICEREMOVECOMPLETE)
|
||||
m_controller->OnDeviceChange();
|
||||
return TRUE;
|
||||
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
}
|
||||
return DefWindowProcW(hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
void TrayApp::AddTrayIcon() {
|
||||
NOTIFYICONDATAW nid{};
|
||||
nid.cbSize = sizeof(nid);
|
||||
nid.hWnd = m_hwnd;
|
||||
nid.uID = TRAY_UID;
|
||||
nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
|
||||
nid.uCallbackMessage = WM_TRAY;
|
||||
nid.hIcon = m_iconOff;
|
||||
wcscpy_s(nid.szTip, L"Steamless Controller");
|
||||
Shell_NotifyIconW(NIM_ADD, &nid);
|
||||
nid.uVersion = NOTIFYICON_VERSION_4;
|
||||
Shell_NotifyIconW(NIM_SETVERSION, &nid);
|
||||
}
|
||||
|
||||
void TrayApp::RemoveTrayIcon() {
|
||||
NOTIFYICONDATAW nid{};
|
||||
nid.cbSize = sizeof(nid);
|
||||
nid.hWnd = m_hwnd;
|
||||
nid.uID = TRAY_UID;
|
||||
Shell_NotifyIconW(NIM_DELETE, &nid);
|
||||
}
|
||||
|
||||
void TrayApp::UpdateTrayIcon(bool connected, bool gameModeActive, bool vigemMissing) {
|
||||
if (vigemMissing) { ShowViGEmBalloon(); return; }
|
||||
bool gameModeOn = gameModeActive;
|
||||
|
||||
const wchar_t* tip = gameModeOn ? L"Steamless Controller — Steamless Mode ON"
|
||||
: connected ? L"Steamless Controller — Connected (Steamless Mode OFF)"
|
||||
: L"Steamless Controller — No controller found";
|
||||
|
||||
NOTIFYICONDATAW nid{};
|
||||
nid.cbSize = sizeof(nid);
|
||||
nid.hWnd = m_hwnd;
|
||||
nid.uID = TRAY_UID;
|
||||
nid.uFlags = NIF_TIP | NIF_ICON;
|
||||
nid.hIcon = gameModeOn ? m_iconOn : m_iconOff;
|
||||
wcscpy_s(nid.szTip, tip);
|
||||
Shell_NotifyIconW(NIM_MODIFY, &nid);
|
||||
}
|
||||
|
||||
void TrayApp::ShowViGEmBalloon() {
|
||||
NOTIFYICONDATAW nid{};
|
||||
nid.cbSize = sizeof(nid);
|
||||
nid.hWnd = m_hwnd;
|
||||
nid.uID = TRAY_UID;
|
||||
nid.uFlags = NIF_INFO;
|
||||
nid.dwInfoFlags = NIIF_WARNING;
|
||||
wcscpy_s(nid.szInfoTitle, L"Driver required");
|
||||
wcscpy_s(nid.szInfo, L"ViGEmBus is not installed. Click here to download it.");
|
||||
Shell_NotifyIconW(NIM_MODIFY, &nid);
|
||||
}
|
||||
|
||||
static constexpr wchar_t REG_KEY[] = L"Software\\SteamlessController";
|
||||
|
||||
void TrayApp::LoadSettings() {
|
||||
HKEY key;
|
||||
if (RegOpenKeyExW(HKEY_CURRENT_USER, REG_KEY, 0, KEY_READ, &key) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
auto readBool = [&](const wchar_t* name, bool def) -> bool {
|
||||
DWORD val = 0, size = sizeof(val);
|
||||
if (RegQueryValueExW(key, name, nullptr, nullptr,
|
||||
reinterpret_cast<LPBYTE>(&val), &size) == ERROR_SUCCESS)
|
||||
return val != 0;
|
||||
return def;
|
||||
};
|
||||
|
||||
m_controller->SetTrackpadMouseEnabled(readBool(L"TrackpadMouse", false));
|
||||
m_controller->SetBackButtonsEnabled (readBool(L"BackButtons", false));
|
||||
m_controller->SetUseLeftTrackpad (readBool(L"UseLeftTrackpad", false));
|
||||
|
||||
RegCloseKey(key);
|
||||
}
|
||||
|
||||
void TrayApp::SaveSettings() {
|
||||
HKEY key;
|
||||
if (RegCreateKeyExW(HKEY_CURRENT_USER, REG_KEY, 0, nullptr,
|
||||
REG_OPTION_NON_VOLATILE, KEY_WRITE, nullptr,
|
||||
&key, nullptr) != ERROR_SUCCESS)
|
||||
return;
|
||||
|
||||
auto writeBool = [&](const wchar_t* name, bool val) {
|
||||
DWORD dw = val ? 1 : 0;
|
||||
RegSetValueExW(key, name, 0, REG_DWORD,
|
||||
reinterpret_cast<const BYTE*>(&dw), sizeof(dw));
|
||||
};
|
||||
|
||||
writeBool(L"TrackpadMouse", m_controller->IsTrackpadMouseEnabled());
|
||||
writeBool(L"BackButtons", m_controller->IsBackButtonsEnabled());
|
||||
writeBool(L"UseLeftTrackpad", m_controller->IsUseLeftTrackpad());
|
||||
|
||||
RegCloseKey(key);
|
||||
}
|
||||
|
||||
void TrayApp::ShowContextMenu() {
|
||||
bool connected = m_controller->IsConnected();
|
||||
bool gameModeOn = m_controller->IsGameModeActive();
|
||||
bool trackpadOn = m_controller->IsTrackpadMouseEnabled();
|
||||
bool backButtonsOn = m_controller->IsBackButtonsEnabled();
|
||||
bool leftTrackpad = m_controller->IsUseLeftTrackpad();
|
||||
|
||||
HMENU menu = CreatePopupMenu();
|
||||
|
||||
UINT toggleFlags = MF_STRING | (connected ? MF_ENABLED : MF_GRAYED);
|
||||
AppendMenuW(menu, toggleFlags, IDM_TOGGLE,
|
||||
gameModeOn ? L"Disable Steamless Mode" : L"Enable Steamless Mode");
|
||||
|
||||
AppendMenuW(menu, MF_SEPARATOR, 0, nullptr);
|
||||
|
||||
UINT trackpadFlags = MF_STRING | (trackpadOn ? MF_CHECKED : MF_UNCHECKED);
|
||||
AppendMenuW(menu, trackpadFlags, IDM_TRACKPAD, L"Enable Trackpad Mouse");
|
||||
|
||||
UINT backFlags = MF_STRING | (backButtonsOn ? MF_CHECKED : MF_UNCHECKED);
|
||||
AppendMenuW(menu, backFlags, IDM_BACKBUTTONS, L"Enable Back Buttons for Clicking");
|
||||
|
||||
UINT leftFlags = MF_STRING | (leftTrackpad ? MF_CHECKED : MF_UNCHECKED);
|
||||
AppendMenuW(menu, leftFlags, IDM_LEFT_TRACKPAD, L"Use Left Trackpad Instead");
|
||||
|
||||
AppendMenuW(menu, MF_SEPARATOR, 0, nullptr);
|
||||
AppendMenuW(menu, MF_STRING, IDM_EXIT, L"Exit");
|
||||
|
||||
// SetForegroundWindow is required for the menu to dismiss on click-away.
|
||||
SetForegroundWindow(m_hwnd);
|
||||
|
||||
POINT pt;
|
||||
GetCursorPos(&pt);
|
||||
TrackPopupMenu(menu, TPM_RIGHTALIGN | TPM_BOTTOMALIGN | TPM_RIGHTBUTTON,
|
||||
pt.x, pt.y, 0, m_hwnd, nullptr);
|
||||
DestroyMenu(menu);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include <Windows.h>
|
||||
#include <memory>
|
||||
|
||||
class ControllerManager;
|
||||
|
||||
class TrayApp {
|
||||
public:
|
||||
TrayApp();
|
||||
~TrayApp();
|
||||
|
||||
bool Init(HINSTANCE hInstance);
|
||||
int Run();
|
||||
|
||||
private:
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
|
||||
LRESULT HandleMessage(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
|
||||
|
||||
void AddTrayIcon();
|
||||
void RemoveTrayIcon();
|
||||
void UpdateTrayIcon(bool connected, bool gameModeActive, bool vigemMissing = false);
|
||||
void ShowViGEmBalloon();
|
||||
void ShowContextMenu();
|
||||
void LoadSettings();
|
||||
void SaveSettings();
|
||||
|
||||
HWND m_hwnd = nullptr;
|
||||
HINSTANCE m_hInstance = nullptr;
|
||||
UINT m_wmTaskbar = 0;
|
||||
HICON m_iconOff = nullptr;
|
||||
HICON m_iconOn = nullptr;
|
||||
std::unique_ptr<ControllerManager> m_controller;
|
||||
|
||||
static constexpr UINT IDM_TOGGLE = 1001;
|
||||
static constexpr UINT IDM_EXIT = 1002;
|
||||
static constexpr UINT IDM_TRACKPAD = 1003;
|
||||
static constexpr UINT IDM_BACKBUTTONS = 1004;
|
||||
static constexpr UINT IDM_LEFT_TRACKPAD = 1005;
|
||||
static constexpr UINT WM_TRAY = WM_APP + 1;
|
||||
static constexpr UINT TRAY_UID = 1;
|
||||
};
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "VirtualController.h"
|
||||
#include "steam/SteamController.h"
|
||||
#include <ViGEmClient.h>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Report translation — 0x42 → XUSB_REPORT
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static XUSB_REPORT Translate(const uint8_t* buf, size_t n) {
|
||||
XUSB_REPORT r{};
|
||||
if (n < 18) return r;
|
||||
|
||||
const uint8_t b0 = buf[2];
|
||||
const uint8_t b1 = buf[3];
|
||||
const uint8_t b2 = buf[4];
|
||||
|
||||
// Face buttons
|
||||
if (b0 & SteamController::BTN_A) r.wButtons |= XUSB_GAMEPAD_A;
|
||||
if (b0 & SteamController::BTN_B) r.wButtons |= XUSB_GAMEPAD_B;
|
||||
if (b0 & SteamController::BTN_X) r.wButtons |= XUSB_GAMEPAD_X;
|
||||
if (b0 & SteamController::BTN_Y) r.wButtons |= XUSB_GAMEPAD_Y;
|
||||
|
||||
// Bumpers
|
||||
if (b2 & SteamController::BTN_LB) r.wButtons |= XUSB_GAMEPAD_LEFT_SHOULDER;
|
||||
if (b1 & SteamController::BTN_RB) r.wButtons |= XUSB_GAMEPAD_RIGHT_SHOULDER;
|
||||
|
||||
// Menu / View (Start / Back)
|
||||
if (b0 & SteamController::BTN_MENU) r.wButtons |= XUSB_GAMEPAD_START;
|
||||
if (b1 & SteamController::BTN_VIEW) r.wButtons |= XUSB_GAMEPAD_BACK;
|
||||
|
||||
// Stick clicks
|
||||
if (b1 & SteamController::BTN_LS) r.wButtons |= XUSB_GAMEPAD_LEFT_THUMB;
|
||||
if (b0 & SteamController::BTN_RS) r.wButtons |= XUSB_GAMEPAD_RIGHT_THUMB;
|
||||
|
||||
// Steam / Guide button
|
||||
if (b2 & SteamController::BTN_STEAM) r.wButtons |= XUSB_GAMEPAD_GUIDE;
|
||||
|
||||
// D-pad
|
||||
if (b1 & SteamController::BTN_DPAD_UP) r.wButtons |= XUSB_GAMEPAD_DPAD_UP;
|
||||
if (b1 & SteamController::BTN_DPAD_DN) r.wButtons |= XUSB_GAMEPAD_DPAD_DOWN;
|
||||
if (b1 & SteamController::BTN_DPAD_LT) r.wButtons |= XUSB_GAMEPAD_DPAD_LEFT;
|
||||
if (b1 & SteamController::BTN_DPAD_RT) r.wButtons |= XUSB_GAMEPAD_DPAD_RIGHT;
|
||||
|
||||
// Triggers: 16-bit signed (0x0000–0x7FFF) → 8-bit (0–255)
|
||||
int16_t ltRaw, rtRaw;
|
||||
memcpy(<Raw, buf + 6, 2);
|
||||
memcpy(&rtRaw, buf + 8, 2);
|
||||
r.bLeftTrigger = static_cast<uint8_t>(std::clamp<int>(ltRaw >> 7, 0, 255));
|
||||
r.bRightTrigger = static_cast<uint8_t>(std::clamp<int>(rtRaw >> 7, 0, 255));
|
||||
|
||||
// Sticks: 16-bit signed, same range as XInput — pass through directly
|
||||
memcpy(&r.sThumbLX, buf + 10, 2);
|
||||
memcpy(&r.sThumbLY, buf + 12, 2);
|
||||
memcpy(&r.sThumbRX, buf + 14, 2);
|
||||
memcpy(&r.sThumbRY, buf + 16, 2);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// VirtualController
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
VirtualController::VirtualController() {
|
||||
m_client = vigem_alloc();
|
||||
if (!m_client) { printf("[ViGEm] alloc failed\n"); return; }
|
||||
|
||||
VIGEM_ERROR err = vigem_connect(static_cast<PVIGEM_CLIENT>(m_client));
|
||||
if (!VIGEM_SUCCESS(err)) {
|
||||
if (err == VIGEM_ERROR_BUS_NOT_FOUND || err == VIGEM_ERROR_BUS_ACCESS_FAILED)
|
||||
m_driverMissing = true;
|
||||
vigem_free(static_cast<PVIGEM_CLIENT>(m_client));
|
||||
m_client = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
m_target = vigem_target_x360_alloc();
|
||||
if (!m_target) { printf("[ViGEm] target alloc failed\n"); return; }
|
||||
|
||||
err = vigem_target_add(static_cast<PVIGEM_CLIENT>(m_client),
|
||||
static_cast<PVIGEM_TARGET>(m_target));
|
||||
if (!VIGEM_SUCCESS(err)) {
|
||||
printf("[ViGEm] target_add failed: 0x%08X\n", err);
|
||||
vigem_target_free(static_cast<PVIGEM_TARGET>(m_target));
|
||||
m_target = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
printf("[ViGEm] Virtual Xbox 360 controller connected\n");
|
||||
m_valid = true;
|
||||
}
|
||||
|
||||
VirtualController::~VirtualController() {
|
||||
if (m_client && m_target) {
|
||||
vigem_target_remove(static_cast<PVIGEM_CLIENT>(m_client),
|
||||
static_cast<PVIGEM_TARGET>(m_target));
|
||||
}
|
||||
if (m_target) vigem_target_free(static_cast<PVIGEM_TARGET>(m_target));
|
||||
if (m_client) {
|
||||
vigem_disconnect(static_cast<PVIGEM_CLIENT>(m_client));
|
||||
vigem_free(static_cast<PVIGEM_CLIENT>(m_client));
|
||||
}
|
||||
}
|
||||
|
||||
void VirtualController::Update(const uint8_t* buf, size_t n) {
|
||||
if (!m_valid) return;
|
||||
XUSB_REPORT report = Translate(buf, n);
|
||||
vigem_target_x360_update(static_cast<PVIGEM_CLIENT>(m_client),
|
||||
static_cast<PVIGEM_TARGET>(m_target),
|
||||
report);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
|
||||
class VirtualController {
|
||||
public:
|
||||
VirtualController();
|
||||
~VirtualController();
|
||||
VirtualController(const VirtualController&) = delete;
|
||||
VirtualController& operator=(const VirtualController&) = delete;
|
||||
|
||||
bool IsValid() const { return m_valid; }
|
||||
bool IsDriverMissing() const { return m_driverMissing; }
|
||||
|
||||
void Update(const uint8_t* buf, size_t n);
|
||||
|
||||
private:
|
||||
void* m_client = nullptr;
|
||||
void* m_target = nullptr;
|
||||
bool m_valid = false;
|
||||
bool m_driverMissing = false;
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "TrayApp.h"
|
||||
#include <Windows.h>
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR, int) {
|
||||
// Prevent multiple instances.
|
||||
HANDLE mutex = CreateMutexW(nullptr, TRUE, L"SteamlessController_SingleInstance");
|
||||
if (!mutex || GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
if (mutex) CloseHandle(mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
TrayApp app;
|
||||
int result = 0;
|
||||
if (app.Init(hInstance))
|
||||
result = app.Run();
|
||||
|
||||
CloseHandle(mutex);
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <hidsdi.h>
|
||||
#include <hidpi.h>
|
||||
#include <setupapi.h>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "HidDevice.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Enumeration
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
std::vector<std::wstring> HidDevice::Enumerate(uint16_t vid, uint16_t pid, uint16_t usagePage) {
|
||||
GUID hidGuid;
|
||||
HidD_GetHidGuid(&hidGuid);
|
||||
|
||||
HDEVINFO devInfo = SetupDiGetClassDevsW(&hidGuid, nullptr, nullptr,
|
||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
||||
if (devInfo == INVALID_HANDLE_VALUE)
|
||||
return {};
|
||||
|
||||
std::vector<std::wstring> paths;
|
||||
SP_DEVICE_INTERFACE_DATA ifaceData{};
|
||||
ifaceData.cbSize = sizeof(ifaceData);
|
||||
|
||||
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(devInfo, nullptr, &hidGuid, i, &ifaceData); ++i) {
|
||||
DWORD needed = 0;
|
||||
SetupDiGetDeviceInterfaceDetailW(devInfo, &ifaceData, nullptr, 0, &needed, nullptr);
|
||||
if (needed == 0)
|
||||
continue;
|
||||
|
||||
auto detailBuf = std::make_unique<uint8_t[]>(needed);
|
||||
auto* detail = reinterpret_cast<SP_DEVICE_INTERFACE_DETAIL_DATA_W*>(detailBuf.get());
|
||||
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
|
||||
|
||||
if (!SetupDiGetDeviceInterfaceDetailW(devInfo, &ifaceData, detail, needed, nullptr, nullptr))
|
||||
continue;
|
||||
|
||||
// Open with no access just to query attributes — avoids needing exclusive open.
|
||||
HANDLE h = CreateFileW(detail->DevicePath, 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (h == INVALID_HANDLE_VALUE)
|
||||
continue;
|
||||
|
||||
HIDD_ATTRIBUTES attrs{};
|
||||
attrs.Size = sizeof(attrs);
|
||||
bool match = HidD_GetAttributes(h, &attrs)
|
||||
&& attrs.VendorID == vid
|
||||
&& attrs.ProductID == pid;
|
||||
|
||||
if (match && usagePage != 0) {
|
||||
PHIDP_PREPARSED_DATA preparsed;
|
||||
if (HidD_GetPreparsedData(h, &preparsed)) {
|
||||
HIDP_CAPS caps{};
|
||||
if (HidP_GetCaps(preparsed, &caps) == HIDP_STATUS_SUCCESS)
|
||||
match = (caps.UsagePage == usagePage);
|
||||
HidD_FreePreparsedData(preparsed);
|
||||
} else {
|
||||
match = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (match)
|
||||
paths.push_back(detail->DevicePath);
|
||||
|
||||
CloseHandle(h);
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(devInfo);
|
||||
return paths;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Move semantics
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
HidDevice::HidDevice(HidDevice&& o) noexcept
|
||||
: m_handle(o.m_handle), m_event(o.m_event), m_outputReportLen(o.m_outputReportLen) {
|
||||
o.m_handle = INVALID_HANDLE_VALUE;
|
||||
o.m_event = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
HidDevice& HidDevice::operator=(HidDevice&& o) noexcept {
|
||||
if (this != &o) {
|
||||
Close();
|
||||
m_handle = o.m_handle;
|
||||
m_event = o.m_event;
|
||||
m_outputReportLen = o.m_outputReportLen;
|
||||
o.m_handle = INVALID_HANDLE_VALUE;
|
||||
o.m_event = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Open / Close
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool HidDevice::Open(const std::wstring& path) {
|
||||
Close();
|
||||
|
||||
// FILE_SHARE_READ | FILE_SHARE_WRITE lets us coexist with Steam's open handle.
|
||||
// Remove the share flags for exclusive access (Steam must be closed first).
|
||||
m_handle = CreateFileW(path.c_str(),
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
nullptr);
|
||||
|
||||
if (m_handle == INVALID_HANDLE_VALUE) {
|
||||
wprintf(L"CreateFileW failed for %s: error %lu\n", path.c_str(), GetLastError());
|
||||
return false;
|
||||
}
|
||||
|
||||
m_event = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||
if (m_event == INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(m_handle);
|
||||
m_handle = INVALID_HANDLE_VALUE;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query report sizes. HidD_SetOutputReport and HidD_SetFeature each require
|
||||
// their buffer to be exactly the maximum report length of their respective type.
|
||||
PHIDP_PREPARSED_DATA preparsed;
|
||||
if (HidD_GetPreparsedData(m_handle, &preparsed)) {
|
||||
HIDP_CAPS caps{};
|
||||
if (HidP_GetCaps(preparsed, &caps) == HIDP_STATUS_SUCCESS) {
|
||||
if (caps.OutputReportByteLength > 0) m_outputReportLen = caps.OutputReportByteLength;
|
||||
if (caps.FeatureReportByteLength > 0) m_featureReportLen = caps.FeatureReportByteLength;
|
||||
}
|
||||
HidD_FreePreparsedData(preparsed);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void HidDevice::Close() {
|
||||
if (m_handle != INVALID_HANDLE_VALUE) {
|
||||
CancelIo(m_handle);
|
||||
CloseHandle(m_handle);
|
||||
m_handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
if (m_event != INVALID_HANDLE_VALUE) {
|
||||
CloseHandle(m_event);
|
||||
m_event = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// I/O
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool HidDevice::SendOutputReport(const uint8_t* data, size_t size) {
|
||||
// HidD_SetOutputReport requires the buffer to be exactly OutputReportByteLength.
|
||||
// Pad with zeros if the caller provided a shorter buffer (e.g. a 7-byte report
|
||||
// when the device's max output report is 64 bytes).
|
||||
if (size < m_outputReportLen) {
|
||||
std::vector<uint8_t> padded(m_outputReportLen, 0);
|
||||
std::memcpy(padded.data(), data, size);
|
||||
BOOLEAN ok = HidD_SetOutputReport(m_handle, padded.data(), m_outputReportLen);
|
||||
if (!ok)
|
||||
printf("SendOutputReport(0x%02X) failed: error %lu\n", data[0], GetLastError());
|
||||
return ok == TRUE;
|
||||
}
|
||||
|
||||
BOOLEAN ok = HidD_SetOutputReport(m_handle,
|
||||
const_cast<PVOID>(static_cast<const void*>(data)),
|
||||
static_cast<ULONG>(size));
|
||||
if (!ok)
|
||||
printf("SendOutputReport(0x%02X) failed: error %lu\n", data[0], GetLastError());
|
||||
return ok == TRUE;
|
||||
}
|
||||
|
||||
bool HidDevice::SendFeatureReport(const uint8_t* data, size_t size) {
|
||||
std::vector<uint8_t> buf(m_featureReportLen, 0);
|
||||
size_t copyLen = size < m_featureReportLen ? size : m_featureReportLen;
|
||||
std::memcpy(buf.data(), data, copyLen);
|
||||
BOOLEAN ok = HidD_SetFeature(m_handle, buf.data(), m_featureReportLen);
|
||||
if (!ok)
|
||||
printf("SendFeatureReport(reportId=0x%02X cmd=0x%02X) failed: error %lu\n",
|
||||
buf[0], buf[1], GetLastError());
|
||||
return ok == TRUE;
|
||||
}
|
||||
|
||||
size_t HidDevice::ReadInputReport(uint8_t* buffer, size_t size, uint32_t timeoutMs) {
|
||||
OVERLAPPED ov{};
|
||||
ov.hEvent = m_event;
|
||||
ResetEvent(m_event);
|
||||
|
||||
DWORD bytesRead = 0;
|
||||
if (!ReadFile(m_handle, buffer, static_cast<DWORD>(size), &bytesRead, &ov)) {
|
||||
if (GetLastError() != ERROR_IO_PENDING)
|
||||
return 0;
|
||||
|
||||
DWORD wait = WaitForSingleObject(m_event, timeoutMs);
|
||||
if (wait != WAIT_OBJECT_0) {
|
||||
CancelIo(m_handle);
|
||||
return 0;
|
||||
}
|
||||
if (!GetOverlappedResult(m_handle, &ov, &bytesRead, FALSE))
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<size_t>(bytesRead);
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class HidDevice {
|
||||
public:
|
||||
// Returns device paths for all HID interfaces matching vid/pid/usagePage.
|
||||
// Pass usagePage=0 to return all matching interfaces.
|
||||
static std::vector<std::wstring> Enumerate(uint16_t vid, uint16_t pid, uint16_t usagePage = 0);
|
||||
|
||||
HidDevice() = default;
|
||||
~HidDevice() { Close(); }
|
||||
HidDevice(const HidDevice&) = delete;
|
||||
HidDevice& operator=(const HidDevice&) = delete;
|
||||
HidDevice(HidDevice&& o) noexcept;
|
||||
HidDevice& operator=(HidDevice&& o) noexcept;
|
||||
|
||||
// Open with exclusive write access. Returns false if device is in use or not found.
|
||||
bool Open(const std::wstring& path);
|
||||
void Close();
|
||||
bool IsOpen() const { return m_handle != INVALID_HANDLE_VALUE; }
|
||||
|
||||
// Send a HID output report (interrupt OUT / SET_REPORT Output type).
|
||||
// data[0] must be the report ID. Padded to OutputReportByteLength automatically.
|
||||
bool SendOutputReport(const uint8_t* data, size_t size);
|
||||
|
||||
// Send a HID feature report (SET_REPORT Feature type via EP0 control pipe).
|
||||
// data[0] must be the feature report ID. Padded to FeatureReportByteLength automatically.
|
||||
// This is the command channel the original Steam Controller used for all firmware commands.
|
||||
bool SendFeatureReport(const uint8_t* data, size_t size);
|
||||
|
||||
ULONG OutputReportByteLength() const { return m_outputReportLen; }
|
||||
ULONG FeatureReportByteLength() const { return m_featureReportLen; }
|
||||
|
||||
// Read the next HID input report. buffer[0] will be the report ID on return.
|
||||
// Returns bytes read, or 0 on timeout/error.
|
||||
size_t ReadInputReport(uint8_t* buffer, size_t size, uint32_t timeoutMs = 1000);
|
||||
|
||||
private:
|
||||
HANDLE m_handle = INVALID_HANDLE_VALUE;
|
||||
HANDLE m_event = INVALID_HANDLE_VALUE;
|
||||
ULONG m_outputReportLen = 64;
|
||||
ULONG m_featureReportLen = 64;
|
||||
};
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
#include <Windows.h>
|
||||
#include <csignal>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <climits>
|
||||
#include "steam/SteamController.h"
|
||||
|
||||
static SteamController* g_controller = nullptr;
|
||||
|
||||
static void OnSignal(int) {
|
||||
printf("\nCleaning up — re-enabling lizard mode...\n");
|
||||
if (g_controller) {
|
||||
g_controller->EnableLizardMode();
|
||||
g_controller->Close();
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Calibration — collect idle frames to identify analog axis byte positions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static constexpr int CALIB_FRAMES = 200;
|
||||
static constexpr uint8_t AXIS_THRESHOLD = 0; // any idle movement → treat as axis
|
||||
|
||||
// Force specific byte pairs through calibration suppression even if they move at idle.
|
||||
// Set to true to pin those bytes as "not an axis" so they appear in diff output.
|
||||
static constexpr bool FORCE_SHOW_BYTES_10_11 = false; // left joystick X
|
||||
static constexpr bool FORCE_SHOW_BYTES_12_13 = false; // left joystick Y
|
||||
static constexpr bool FORCE_SHOW_BYTES_14_15 = false; // right joystick Y
|
||||
static constexpr bool FORCE_SHOW_BYTES_16_17 = true; // right joystick X
|
||||
|
||||
struct ByteStats {
|
||||
uint8_t lo = 0xFF;
|
||||
uint8_t hi = 0x00;
|
||||
bool seeded = false;
|
||||
uint8_t range() const { return seeded ? (hi - lo) : 0; }
|
||||
void feed(uint8_t v) {
|
||||
if (!seeded) { lo = hi = v; seeded = true; return; }
|
||||
if (v < lo) lo = v;
|
||||
if (v > hi) hi = v;
|
||||
}
|
||||
};
|
||||
|
||||
static ByteStats s_stats[64];
|
||||
static bool s_isAxis[64] = {}; // true → suppress in diff output
|
||||
static size_t s_reportLen = 0;
|
||||
|
||||
static void RunCalibration(SteamController& ctrl) {
|
||||
printf("--- Calibration: leave controller idle, collecting %d frames ---\n",
|
||||
CALIB_FRAMES);
|
||||
|
||||
uint8_t buf[64];
|
||||
int collected = 0;
|
||||
while (collected < CALIB_FRAMES) {
|
||||
size_t n = ctrl.ReadReport(buf, sizeof(buf), 200);
|
||||
if (n == 0 || buf[0] != SteamController::REPORT_STATE)
|
||||
continue;
|
||||
if (s_reportLen == 0) s_reportLen = n;
|
||||
for (size_t i = 1; i < n; ++i)
|
||||
s_stats[i].feed(buf[i]);
|
||||
++collected;
|
||||
if (collected % 50 == 0)
|
||||
printf(" %d / %d\n", collected, CALIB_FRAMES);
|
||||
}
|
||||
|
||||
// Apply force-show overrides before marking axes.
|
||||
auto forceShow = [](size_t i) -> bool {
|
||||
if (FORCE_SHOW_BYTES_10_11 && (i == 10 || i == 11)) return true;
|
||||
if (FORCE_SHOW_BYTES_12_13 && (i == 12 || i == 13)) return true;
|
||||
if (FORCE_SHOW_BYTES_14_15 && (i == 14 || i == 15)) return true;
|
||||
if (FORCE_SHOW_BYTES_16_17 && (i == 16 || i == 17)) return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
printf("\n--- Calibration complete. Byte ranges (idle): ---\n");
|
||||
printf(" [idx] lo hi range class\n");
|
||||
for (size_t i = 1; i < s_reportLen; ++i) {
|
||||
const char* cls = "";
|
||||
if (forceShow(i)) {
|
||||
s_isAxis[i] = false;
|
||||
cls = " [forced]";
|
||||
} else if (s_stats[i].range() > AXIS_THRESHOLD) {
|
||||
s_isAxis[i] = true;
|
||||
cls = " <axis>";
|
||||
} else if (s_stats[i].hi == 0) {
|
||||
cls = " (zero)";
|
||||
} else {
|
||||
cls = " [candidate]";
|
||||
}
|
||||
printf(" [%02zu] %02X %02X %3u %s\n",
|
||||
i, s_stats[i].lo, s_stats[i].hi, s_stats[i].range(), cls);
|
||||
}
|
||||
printf("\nAxes suppressed. Press buttons one at a time — only stable bytes shown.\n\n");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Differential display — axes masked, only changes shown
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static uint8_t s_prev42[64];
|
||||
static bool s_hasPrev42 = false;
|
||||
|
||||
static void PrintState42Diff(const uint8_t* buf, size_t n) {
|
||||
if (n < 2) return;
|
||||
|
||||
bool anyDiff = false;
|
||||
for (size_t i = 1; i < n; ++i) {
|
||||
if (s_isAxis[i]) continue;
|
||||
bool changed = !s_hasPrev42 || (buf[i] != s_prev42[i]);
|
||||
if (changed) {
|
||||
if (!anyDiff) { printf(" [idx] new old\n"); anyDiff = true; }
|
||||
printf(" [%02zu] %02X (%02X)\n", i, buf[i], s_hasPrev42 ? s_prev42[i] : 0);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(s_prev42, buf, n);
|
||||
s_hasPrev42 = true;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main() {
|
||||
signal(SIGINT, OnSignal);
|
||||
signal(SIGTERM, OnSignal);
|
||||
|
||||
printf("=== SteamProbe — 2026 Steam Controller (VID=28DE PID=1302) ===\n\n");
|
||||
printf("NOTE: Close Steam before running this tool.\n\n");
|
||||
|
||||
SteamController controller;
|
||||
g_controller = &controller;
|
||||
|
||||
if (!controller.Open()) {
|
||||
fprintf(stderr, "Could not open device.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Disabling lizard mode...\n");
|
||||
if (!controller.DisableLizardMode()) {
|
||||
fprintf(stderr, "Lizard mode disable failed.\n");
|
||||
return 1;
|
||||
}
|
||||
printf("Lizard mode OFF.\n\n");
|
||||
|
||||
RunCalibration(controller);
|
||||
|
||||
uint8_t buf[64];
|
||||
while (true) {
|
||||
size_t n = controller.ReadReport(buf, sizeof(buf), 100);
|
||||
if (n == 0) continue;
|
||||
|
||||
uint8_t reportId = buf[0];
|
||||
if (reportId == SteamController::REPORT_STATE) {
|
||||
PrintState42Diff(buf, n);
|
||||
} else {
|
||||
printf("[0x%02X %zu bytes] ", reportId, n);
|
||||
for (size_t i = 0; i < n; ++i) printf("%02X ", buf[i]);
|
||||
printf("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
#define NOMINMAX
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Gaming.Input.h>
|
||||
#include <Windows.h>
|
||||
#include <cstdio>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace Windows::Gaming::Input;
|
||||
|
||||
static constexpr uint16_t VALVE_VID = 0x28DE;
|
||||
static constexpr uint16_t SC_PID = 0x1302;
|
||||
|
||||
int main() {
|
||||
init_apartment();
|
||||
|
||||
printf("=== RawGameController Probe ===\n");
|
||||
printf("Close Steam before running. Enable Game Mode in the tray app first.\n\n");
|
||||
printf("Enumerating (waiting 1s for OS enumeration)...\n");
|
||||
Sleep(1000);
|
||||
|
||||
auto controllers = RawGameController::RawGameControllers();
|
||||
|
||||
if (controllers.Size() == 0) {
|
||||
printf("No RawGameController devices found.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
printf("All detected controllers:\n");
|
||||
RawGameController target = nullptr;
|
||||
for (auto const& ctrl : controllers) {
|
||||
auto name = to_string(ctrl.DisplayName());
|
||||
printf(" \"%s\" VID=%04X PID=%04X Buttons=%u Axes=%u Switches=%u\n",
|
||||
name.c_str(),
|
||||
ctrl.HardwareVendorId(), ctrl.HardwareProductId(),
|
||||
ctrl.ButtonCount(), ctrl.AxisCount(), ctrl.SwitchCount());
|
||||
|
||||
if (ctrl.HardwareVendorId() == VALVE_VID && ctrl.HardwareProductId() == SC_PID)
|
||||
target = ctrl;
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
if (!target) {
|
||||
printf("Steam Controller (VID=28DE PID=1302) not found.\n");
|
||||
printf("The OS may not be exposing the vendor HID collection to RawGameController.\n");
|
||||
printf("Registry mapping approach will NOT work.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t btnCount = target.ButtonCount();
|
||||
uint32_t axisCount = target.AxisCount();
|
||||
uint32_t switchCount = target.SwitchCount();
|
||||
|
||||
printf("Steam Controller found!\n");
|
||||
printf(" Buttons : %u\n", btnCount);
|
||||
printf(" Axes : %u\n", axisCount);
|
||||
printf(" Switches: %u\n\n", switchCount);
|
||||
|
||||
if (btnCount == 0 && axisCount == 0 && switchCount == 0) {
|
||||
printf("Device has no enumerable inputs.\n");
|
||||
printf("Registry mapping approach will NOT work.\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Registry mapping approach looks viable — polling live values.\n");
|
||||
printf("Move sticks, press buttons. Press Ctrl+C to exit.\n\n");
|
||||
|
||||
// vector<bool> is a bitset without .data(); use a heap bool array instead.
|
||||
auto buttons = std::make_unique<bool[]>(btnCount ? btnCount : 1);
|
||||
auto prevButtons= std::make_unique<bool[]>(btnCount ? btnCount : 1);
|
||||
std::vector<double> axes(axisCount, 0.0);
|
||||
std::vector<GameControllerSwitchPosition> switches(switchCount ? switchCount : 1);
|
||||
std::vector<double> prevAxes(axisCount, -1.0);
|
||||
for (uint32_t i = 0; i < btnCount; i++) { buttons[i] = false; prevButtons[i] = false; }
|
||||
|
||||
while (true) {
|
||||
// API order: buttons, switches, axes
|
||||
target.GetCurrentReading(
|
||||
winrt::array_view<bool>(buttons.get(), buttons.get() + btnCount),
|
||||
winrt::array_view<GameControllerSwitchPosition>(switches.data(), switches.data() + switchCount),
|
||||
winrt::array_view<double>(axes.data(), axes.data() + axisCount));
|
||||
|
||||
for (uint32_t i = 0; i < axisCount; i++) {
|
||||
if (std::abs(axes[i] - prevAxes[i]) > 0.001) {
|
||||
printf(" Axis[%02u] = %.4f\n", i, axes[i]);
|
||||
prevAxes[i] = axes[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < btnCount; i++) {
|
||||
if (buttons[i] != prevButtons[i]) {
|
||||
printf(" Button[%02u] = %s\n", i, buttons[i] ? "PRESSED" : "released");
|
||||
prevButtons[i] = buttons[i];
|
||||
}
|
||||
}
|
||||
|
||||
Sleep(16);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
#include "SteamController.h"
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Internal helper: build a 64-byte feature report command buffer.
|
||||
//
|
||||
// The 2026 Steam Controller routes firmware commands through Feature Report
|
||||
// 0x01 (same channel the original SC used, now with an explicit report ID).
|
||||
//
|
||||
// Buffer layout:
|
||||
// [0] FEATURE_REPORT_CMD (0x01) — HID feature report ID
|
||||
// [1] cmd — command byte (0x81, 0x87, etc.)
|
||||
// [2] payloadSize — number of payload bytes that follow
|
||||
// [3..3+payloadSize-1] payload — command arguments
|
||||
// [rest] zeros
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void BuildCmd(uint8_t (&buf)[64], uint8_t cmd,
|
||||
const uint8_t* payload = nullptr, uint8_t payloadSize = 0) {
|
||||
std::memset(buf, 0, 64);
|
||||
buf[0] = SteamController::FEATURE_REPORT_CMD;
|
||||
buf[1] = cmd;
|
||||
buf[2] = payloadSize;
|
||||
if (payload && payloadSize)
|
||||
std::memcpy(buf + 3, payload, payloadSize);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Open / Close
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool SteamController::Open() {
|
||||
auto paths = HidDevice::Enumerate(VALVE_VID, SC2026_PID, VENDOR_USAGE_PAGE);
|
||||
if (paths.empty()) {
|
||||
printf("No Steam Controller found (VID=%04X PID=%04X UsagePage=%04X).\n",
|
||||
VALVE_VID, SC2026_PID, VENDOR_USAGE_PAGE);
|
||||
return false;
|
||||
}
|
||||
|
||||
printf("Found %zu interface(s). Opening: ", paths.size());
|
||||
wprintf(L"%s\n", paths[0].c_str());
|
||||
|
||||
return m_device.Open(paths[0]);
|
||||
}
|
||||
|
||||
void SteamController::Close() {
|
||||
if (m_running.exchange(false) && m_heartbeat.joinable())
|
||||
m_heartbeat.join();
|
||||
m_device.Close();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Lizard mode
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool SteamController::DisableLizardMode() {
|
||||
uint8_t buf[64];
|
||||
|
||||
// Step 1: CLEAR_DIGITAL_MAPPINGS — kills keyboard/mouse button emulation.
|
||||
BuildCmd(buf, CMD_CLEAR_DIGITAL_MAPPINGS);
|
||||
if (!m_device.SendFeatureReport(buf, sizeof(buf))) {
|
||||
printf("Failed to send CLEAR_DIGITAL_MAPPINGS.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: SET_SETTINGS — set both trackpads to TRACKPAD_NONE.
|
||||
// Payload: pairs of [setting_id, val_lo, val_hi].
|
||||
const uint8_t settingsPayload[] = {
|
||||
SETTING_LEFT_TRACKPAD_MODE, 0x00, 0x00,
|
||||
SETTING_RIGHT_TRACKPAD_MODE, 0x00, 0x00,
|
||||
};
|
||||
BuildCmd(buf, CMD_SET_SETTINGS, settingsPayload, sizeof(settingsPayload));
|
||||
if (!m_device.SendFeatureReport(buf, sizeof(buf))) {
|
||||
printf("Failed to send SET_SETTINGS_VALUES.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_running.exchange(true))
|
||||
m_heartbeat = std::thread(&SteamController::HeartbeatLoop, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SteamController::EnableLizardMode() {
|
||||
if (m_running.exchange(false) && m_heartbeat.joinable())
|
||||
m_heartbeat.join();
|
||||
|
||||
uint8_t buf[64];
|
||||
BuildCmd(buf, CMD_SET_DEFAULT_MAPPINGS);
|
||||
return m_device.SendFeatureReport(buf, sizeof(buf));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
size_t SteamController::ReadReport(uint8_t* buffer, size_t size, uint32_t timeoutMs) {
|
||||
return m_device.ReadInputReport(buffer, size, timeoutMs);
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Heartbeat
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void SteamController::HeartbeatLoop() {
|
||||
uint8_t buf[64];
|
||||
BuildCmd(buf, CMD_CLEAR_DIGITAL_MAPPINGS);
|
||||
|
||||
while (m_running.load()) {
|
||||
m_device.SendFeatureReport(buf, sizeof(buf));
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(800));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
#include "hid/HidDevice.h"
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <thread>
|
||||
|
||||
class SteamController {
|
||||
public:
|
||||
static constexpr uint16_t VALVE_VID = 0x28DE;
|
||||
static constexpr uint16_t SC2026_PID = 0x1302;
|
||||
|
||||
// HID Usage Page for the vendor collection that carries all game input.
|
||||
static constexpr uint16_t VENDOR_USAGE_PAGE = 0xFF00;
|
||||
|
||||
// Input report IDs (device → host)
|
||||
static constexpr uint8_t REPORT_STATE = 0x42; // 53 bytes: main controller state
|
||||
static constexpr uint8_t REPORT_SECONDARY = 0x43; // 14 bytes: gyro / secondary state
|
||||
static constexpr uint8_t REPORT_STATUS = 0x44; // 5 bytes: battery / connection
|
||||
static constexpr uint8_t REPORT_EXTENDED = 0x45; // 45 bytes: extended sensor state
|
||||
static constexpr uint8_t REPORT_UNKNOWN_7B = 0x7B; // 12 bytes: TBD
|
||||
static constexpr uint8_t REPORT_UNKNOWN_79 = 0x79; // 1 byte: TBD
|
||||
|
||||
// Feature report IDs — the command channel to the firmware.
|
||||
// Commands are wrapped inside Feature Report 0x01 (or 0x02 as fallback).
|
||||
// Buffer layout: [feature_report_id | cmd_byte | payload_size | payload...]
|
||||
static constexpr uint8_t FEATURE_REPORT_CMD = 0x01;
|
||||
static constexpr uint8_t FEATURE_REPORT_CMD2 = 0x02; // fallback if 0x01 fails
|
||||
|
||||
// Command bytes (go in buffer[1] inside the feature report)
|
||||
static constexpr uint8_t CMD_SET_DIGITAL_MAPPINGS = 0x80;
|
||||
static constexpr uint8_t CMD_CLEAR_DIGITAL_MAPPINGS = 0x81; // ← lizard off
|
||||
static constexpr uint8_t CMD_GET_DIGITAL_MAPPINGS = 0x82;
|
||||
static constexpr uint8_t CMD_SET_DEFAULT_MAPPINGS = 0x85; // ← lizard on
|
||||
static constexpr uint8_t CMD_SET_SETTINGS = 0x87;
|
||||
static constexpr uint8_t CMD_GET_SETTINGS = 0x89;
|
||||
|
||||
// Setting key IDs (go in the payload of CMD_SET_SETTINGS)
|
||||
static constexpr uint8_t SETTING_RIGHT_TRACKPAD_MODE = 0x07;
|
||||
static constexpr uint8_t SETTING_LEFT_TRACKPAD_MODE = 0x08;
|
||||
static constexpr uint8_t TRACKPAD_NONE = 0x00;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Input report layout — 0x42 STATE report (buf[0] = 0x42)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// buf[01] — 8-bit sequence counter (wraps 0xFF → 0x00)
|
||||
|
||||
// buf[02] — button bitmask byte 0
|
||||
static constexpr uint8_t BTN_A = 0x01; // bit 0
|
||||
static constexpr uint8_t BTN_B = 0x02; // bit 1
|
||||
static constexpr uint8_t BTN_X = 0x04; // bit 2
|
||||
static constexpr uint8_t BTN_Y = 0x08; // bit 3
|
||||
// bit 4 (0x10): TBD
|
||||
static constexpr uint8_t BTN_RS = 0x20; // bit 5 — right stick click
|
||||
static constexpr uint8_t BTN_MENU = 0x40; // bit 6 — ≡ Menu / Start
|
||||
static constexpr uint8_t BTN_R4 = 0x80; // bit 7 — back paddle R4
|
||||
|
||||
// buf[03] — button bitmask byte 1
|
||||
static constexpr uint8_t BTN_R5 = 0x01; // bit 0 — back paddle R5
|
||||
static constexpr uint8_t BTN_RB = 0x02; // bit 1
|
||||
static constexpr uint8_t BTN_DPAD_DN = 0x04; // bit 2
|
||||
static constexpr uint8_t BTN_DPAD_RT = 0x08; // bit 3
|
||||
static constexpr uint8_t BTN_DPAD_LT = 0x10; // bit 4
|
||||
static constexpr uint8_t BTN_DPAD_UP = 0x20; // bit 5
|
||||
static constexpr uint8_t BTN_VIEW = 0x40; // bit 6 — ⧉ View / Back
|
||||
static constexpr uint8_t BTN_LS = 0x80; // bit 7 — left stick click
|
||||
|
||||
// buf[04] — button bitmask byte 2
|
||||
static constexpr uint8_t BTN_STEAM = 0x01; // bit 0 — Steam / Guide
|
||||
static constexpr uint8_t BTN_L4 = 0x02; // bit 1 — back paddle L4
|
||||
static constexpr uint8_t BTN_L5 = 0x04; // bit 2 — back paddle L5
|
||||
static constexpr uint8_t BTN_LB = 0x08; // bit 3
|
||||
static constexpr uint8_t BTN_RS_TOUCH = 0x10; // bit 4 — right stick capacitive touch
|
||||
static constexpr uint8_t BTN_TP_RT = 0x20; // bit 5 — right trackpad active (touch or click)
|
||||
// bit 6 (0x40): TBD — possibly right trackpad physical click (hard press only)
|
||||
static constexpr uint8_t BTN_RT_FULL = 0x80; // bit 7 — right trigger fully pressed (digital threshold)
|
||||
|
||||
// buf[05] — flags byte
|
||||
static constexpr uint8_t BTN_LS_TOUCH = 0x01; // bit 0 — left stick capacitive touch
|
||||
static constexpr uint8_t BTN_TP_LT = 0x02; // bit 1 — left trackpad active (touch or click)
|
||||
static constexpr uint8_t BTN_TP_LT_CLICK = 0x04; // bit 2 — left trackpad hard press
|
||||
// bit 3 (0x08): TBD
|
||||
static constexpr uint8_t FLAG_GRIP_RT = 0x10; // bit 4 — right grip sensor active
|
||||
static constexpr uint8_t FLAG_GRIP_LT = 0x20; // bit 5 — left grip sensor active
|
||||
// other bits TBD
|
||||
|
||||
// buf[06..07] — left trigger, 16-bit LE signed, 0x0000 (released) – 0x7FFF (full)
|
||||
// buf[08..09] — right trigger, 16-bit LE signed, 0x0000 (released) – 0x7FFF (full)
|
||||
|
||||
// buf[10..11] — left joystick X, 16-bit LE signed; center ≈ 0x0000, +0x7FFF = right, −0x8000 = left
|
||||
// buf[12..13] — left joystick Y, 16-bit LE signed; center ≈ 0x0000, +0x7FFF = up, −0x8000 = down
|
||||
// buf[14..15] — right joystick X, 16-bit LE signed; center ≈ 0x0000, +0x7FFF = right, −0x8000 = left
|
||||
// buf[16..17] — right joystick Y, 16-bit LE signed; center ≈ 0x0000, +0x7FFF = up, −0x8000 = down
|
||||
|
||||
// buf[18..19] — left trackpad X, 16-bit LE signed (0x0000 when not touching)
|
||||
// buf[20..21] — left trackpad Y, 16-bit LE signed (0x0000 when not touching)
|
||||
// buf[22..23] — left trackpad contact area, 16-bit LE (0 = no contact; higher = more area/pressure)
|
||||
|
||||
// buf[24..25] — right trackpad X, 16-bit LE signed (0x0000 when not touching)
|
||||
// buf[26..27] — right trackpad Y, 16-bit LE signed (0x0000 when not touching)
|
||||
// buf[28..29] — right trackpad contact area, 16-bit LE (0 = no contact; higher = more area/pressure)
|
||||
|
||||
// buf[30..31] — 0x03 0x46 constant (firmware info?)
|
||||
// buf[32..39] — IMU quaternion (4× 16-bit LE, little-endian)
|
||||
// buf[40..43] — unknown / always zero
|
||||
// buf[44..45] — 0xFF 0xFF at full charge (battery level)
|
||||
// buf[46..53] — gyro at rest (constant when stationary)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
SteamController() = default;
|
||||
~SteamController() { Close(); }
|
||||
SteamController(const SteamController&) = delete;
|
||||
SteamController& operator=(const SteamController&) = delete;
|
||||
|
||||
// Finds and opens the vendor HID interface. Returns false if not found.
|
||||
bool Open();
|
||||
void Close();
|
||||
bool IsOpen() const { return m_device.IsOpen(); }
|
||||
|
||||
// Two-step sequence: clears digital mappings + sets trackpads to NONE.
|
||||
// Starts the background heartbeat thread on first call.
|
||||
bool DisableLizardMode();
|
||||
|
||||
// Restores default mappings. Should be called before process exit.
|
||||
bool EnableLizardMode();
|
||||
|
||||
// Read the next raw input report. buffer[0] = report ID on return.
|
||||
// Returns 0 on timeout.
|
||||
size_t ReadReport(uint8_t* buffer, size_t size, uint32_t timeoutMs = 16);
|
||||
|
||||
private:
|
||||
void HeartbeatLoop();
|
||||
|
||||
HidDevice m_device;
|
||||
std::thread m_heartbeat;
|
||||
std::atomic<bool> m_running{false};
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior (example):
|
||||
1. Start program '...'
|
||||
2. Click on '....'
|
||||
3. Plug in device '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**System details (please complete the following information):**
|
||||
- OS: [e.g. Windows 10 1803]
|
||||
- Feeder software: [e.g. VDX, DS4Windows, ...]
|
||||
- Driver Version: [e.g. 1.14.3.0]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
name: You need support
|
||||
about: This is the wrong place to ask for support
|
||||
|
||||
---
|
||||
|
||||
**Please don't abuse the issue tracker as a helpdesk!** We have a Discord server (linked in the README) where you can ask the lovely like-minded folks for assistance. Thank you for your compliance :smiley:
|
||||
+1
@@ -0,0 +1 @@
|
||||
# TBD :smiley:
|
||||
@@ -0,0 +1,45 @@
|
||||
################################################################################
|
||||
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||
################################################################################
|
||||
|
||||
/.vs/ViGEmBus/v15
|
||||
/build/bin/Release
|
||||
/build/obj/Release
|
||||
/lib
|
||||
/packages
|
||||
/sys/x64/Release
|
||||
/x64/Release
|
||||
/x64/Release (dynamic)
|
||||
/x64/Release (static)
|
||||
/.tmp
|
||||
/lib/Release (static)
|
||||
/Release
|
||||
/sys/Release
|
||||
/Release (static)
|
||||
/build/bin/Debug
|
||||
/build/obj/Debug
|
||||
/bin
|
||||
/sys/x64
|
||||
/x64
|
||||
*.user
|
||||
/.vs/config
|
||||
/sys/RCa21300
|
||||
/sys/RCb21300
|
||||
/build/*.dotsettings
|
||||
/artifacts
|
||||
/.vs/ViGEmBus/DesignTimeBuild
|
||||
/build/obj
|
||||
/.vs/ViGEmClient
|
||||
/src/Release (static)
|
||||
/src/x64/Debug (static)
|
||||
/src/x64/Release (dynamic)
|
||||
/src/x64/Release (static)
|
||||
*.TMP
|
||||
/src/Debug_LIB
|
||||
/src/x64/Debug_LIB
|
||||
/x64/Debug_LIB
|
||||
/Debug_LIB
|
||||
*.aps
|
||||
/Release_LIB
|
||||
/src/Release_LIB
|
||||
/src/x64
|
||||
+1
@@ -0,0 +1 @@
|
||||
ViGEmClient.sln
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Benjamin Höglinger-Stelzer
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# ViGEm Client Native SDK
|
||||
|
||||
Developer SDK for communication with `ViGEmBus`.
|
||||
|
||||
[](https://ci.appveyor.com/project/nefarius/vigemclient) [](https://discord.gg/QTJpBX5) [](https://vigem.org/) [](<https://paypal.me/NefariusMaximus>) [](<https://www.patreon.com/nefarius>) [](https://github.com/nefarius) [](https://twitter.com/nefariusmaximus)
|
||||
|
||||
## About
|
||||
|
||||
TBD
|
||||
|
||||
## How to build
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Visual Studio **2017** ([Community Edition](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) is just fine)
|
||||
- [.NET Core SDK 2.1](https://www.microsoft.com/net/download/dotnet-core/2.1) (or greater, required for building only)
|
||||
|
||||
You can either build directly within Visual Studio or in PowerShell by running the build script:
|
||||
|
||||
```PowerShell
|
||||
.\build.ps1 -configuration release
|
||||
```
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.27130.2024
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ViGEmClient", "src\ViGEmClient.vcxproj", "{7DB06674-1F4F-464B-8E1C-172E9587F9DC}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "build\_build.csproj", "{A11CD588-F74A-41B5-9E5B-E60937052009}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug_DLL|x64 = Debug_DLL|x64
|
||||
Debug_DLL|x86 = Debug_DLL|x86
|
||||
Debug_LIB|x64 = Debug_LIB|x64
|
||||
Debug_LIB|x86 = Debug_LIB|x86
|
||||
Release_DLL|x64 = Release_DLL|x64
|
||||
Release_DLL|x86 = Release_DLL|x86
|
||||
Release_LIB|x64 = Release_LIB|x64
|
||||
Release_LIB|x86 = Release_LIB|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x64.ActiveCfg = Debug_DLL|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x64.Build.0 = Debug_DLL|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x86.ActiveCfg = Debug_DLL|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_DLL|x86.Build.0 = Debug_DLL|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x64.ActiveCfg = Debug_LIB|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x64.Build.0 = Debug_LIB|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x86.ActiveCfg = Debug_LIB|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Debug_LIB|x86.Build.0 = Debug_LIB|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x64.ActiveCfg = Release_DLL|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x64.Build.0 = Release_DLL|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x86.ActiveCfg = Release_DLL|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_DLL|x86.Build.0 = Release_DLL|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x64.ActiveCfg = Release_LIB|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x64.Build.0 = Release_LIB|x64
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x86.ActiveCfg = Release_LIB|Win32
|
||||
{7DB06674-1F4F-464B-8E1C-172E9587F9DC}.Release_LIB|x86.Build.0 = Release_LIB|Win32
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Debug_DLL|x64.ActiveCfg = Debug|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Debug_DLL|x86.ActiveCfg = Debug|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Debug_LIB|x64.ActiveCfg = Debug|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Debug_LIB|x86.ActiveCfg = Debug|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Release_DLL|x64.ActiveCfg = Release|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Release_DLL|x86.ActiveCfg = Release|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Release_LIB|x64.ActiveCfg = Release|Any CPU
|
||||
{A11CD588-F74A-41B5-9E5B-E60937052009}.Release_LIB|x86.ActiveCfg = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {D5CD61FD-80BB-4E0E-840C-BAF66ABB1CF0}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -0,0 +1,9 @@
|
||||
version: 1.16.{build}.0
|
||||
image: Visual Studio 2017
|
||||
build_script:
|
||||
- ps: .\build.ps1 -configuration Release_DLL
|
||||
deploy:
|
||||
- provider: Environment
|
||||
name: BUILDBOT
|
||||
on:
|
||||
appveyor_repo_tag: true
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
[CmdletBinding()]
|
||||
Param(
|
||||
#[switch]$CustomParam,
|
||||
[Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)]
|
||||
[string[]]$BuildArguments
|
||||
)
|
||||
|
||||
Write-Output "Windows PowerShell $($Host.Version)"
|
||||
|
||||
Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { $host.SetShouldExit(1) }
|
||||
$PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
$BuildProjectFile = "$PSScriptRoot\build\_build.csproj"
|
||||
$TempDirectory = "$PSScriptRoot\\.tmp"
|
||||
|
||||
$DotNetGlobalFile = "$PSScriptRoot\\global.json"
|
||||
$DotNetInstallUrl = "https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.ps1"
|
||||
$DotNetReleasesUrl = "https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json"
|
||||
|
||||
$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1
|
||||
$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1
|
||||
$env:NUGET_XMLDOC_MODE = "skip"
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function ExecSafe([scriptblock] $cmd) {
|
||||
& $cmd
|
||||
if ($LASTEXITCODE) { exit $LASTEXITCODE }
|
||||
}
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if (Test-Path $DotNetGlobalFile) {
|
||||
$DotNetVersion = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json).sdk.version
|
||||
}
|
||||
|
||||
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
||||
if ((Get-Command "dotnet" -ErrorAction SilentlyContinue) -ne $null -and `
|
||||
(!(Test-Path variable:DotNetVersion) -or $(& dotnet --version) -eq $DotNetVersion)) {
|
||||
$env:DOTNET_EXE = (Get-Command "dotnet").Path
|
||||
}
|
||||
else {
|
||||
$DotNetDirectory = "$TempDirectory\dotnet-win"
|
||||
$env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe"
|
||||
|
||||
# If expected version is not set, get latest version
|
||||
if (!(Test-Path variable:DotNetVersion)) {
|
||||
$DotNetVersion = $(Invoke-WebRequest -UseBasicParsing $DotNetReleasesUrl | ConvertFrom-Json)[0]."version-sdk"
|
||||
}
|
||||
|
||||
# Download and execute install script
|
||||
$DotNetInstallFile = "$TempDirectory\dotnet-install.ps1"
|
||||
md -force $TempDirectory > $null
|
||||
(New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile)
|
||||
ExecSafe { & $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath }
|
||||
}
|
||||
|
||||
Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)"
|
||||
|
||||
ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile -- $BuildArguments }
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo $(bash --version 2>&1 | head -n 1)
|
||||
|
||||
#CUSTOMPARAM=0
|
||||
BUILD_ARGUMENTS=()
|
||||
for i in "$@"; do
|
||||
case $(echo $1 | awk '{print tolower($0)}') in
|
||||
# -custom-param) CUSTOMPARAM=1;;
|
||||
*) BUILD_ARGUMENTS+=("$1") ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
set -eo pipefail
|
||||
SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
|
||||
|
||||
###########################################################################
|
||||
# CONFIGURATION
|
||||
###########################################################################
|
||||
|
||||
BUILD_PROJECT_FILE="$SCRIPT_DIR/build/_build.csproj"
|
||||
TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
|
||||
|
||||
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
|
||||
DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
|
||||
DOTNET_RELEASES_URL="https://raw.githubusercontent.com/dotnet/core/master/release-notes/releases.json"
|
||||
|
||||
export DOTNET_CLI_TELEMETRY_OPTOUT=1
|
||||
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
|
||||
export NUGET_XMLDOC_MODE="skip"
|
||||
|
||||
###########################################################################
|
||||
# EXECUTION
|
||||
###########################################################################
|
||||
|
||||
function FirstJsonValue {
|
||||
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
|
||||
}
|
||||
|
||||
# If global.json exists, load expected version
|
||||
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
|
||||
DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
|
||||
fi
|
||||
|
||||
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
|
||||
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") ]]; then
|
||||
export DOTNET_EXE="$(command -v dotnet)"
|
||||
else
|
||||
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
|
||||
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
|
||||
|
||||
# If expected version is not set, get latest version
|
||||
if [ -z ${DOTNET_VERSION+x} ]; then
|
||||
DOTNET_VERSION=$(FirstJsonValue "version-sdk" $(curl -s "$DOTNET_RELEASES_URL"))
|
||||
fi
|
||||
|
||||
# Download and execute install script
|
||||
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
|
||||
mkdir -p "$TEMP_DIRECTORY"
|
||||
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
|
||||
chmod +x "$DOTNET_INSTALL_FILE"
|
||||
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
|
||||
fi
|
||||
|
||||
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
|
||||
|
||||
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}
|
||||
@@ -0,0 +1,495 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Benjamin "Nefarius" Höglinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ViGEmClient_h__
|
||||
#define ViGEmClient_h__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "ViGEmCommon.h"
|
||||
|
||||
#ifdef VIGEM_DYNAMIC
|
||||
#ifdef VIGEM_EXPORTS
|
||||
#define VIGEM_API __declspec(dllexport)
|
||||
#else
|
||||
#define VIGEM_API __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#define VIGEM_API
|
||||
#endif
|
||||
|
||||
/**
|
||||
* \typedef enum _VIGEM_ERRORS
|
||||
*
|
||||
* \brief Defines an alias representing the ViGEm errors.
|
||||
*/
|
||||
typedef enum _VIGEM_ERRORS
|
||||
{
|
||||
VIGEM_ERROR_NONE = 0x20000000,
|
||||
VIGEM_ERROR_BUS_NOT_FOUND = 0xE0000001,
|
||||
VIGEM_ERROR_NO_FREE_SLOT = 0xE0000002,
|
||||
VIGEM_ERROR_INVALID_TARGET = 0xE0000003,
|
||||
VIGEM_ERROR_REMOVAL_FAILED = 0xE0000004,
|
||||
VIGEM_ERROR_ALREADY_CONNECTED = 0xE0000005,
|
||||
VIGEM_ERROR_TARGET_UNINITIALIZED = 0xE0000006,
|
||||
VIGEM_ERROR_TARGET_NOT_PLUGGED_IN = 0xE0000007,
|
||||
VIGEM_ERROR_BUS_VERSION_MISMATCH = 0xE0000008,
|
||||
VIGEM_ERROR_BUS_ACCESS_FAILED = 0xE0000009,
|
||||
VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED = 0xE0000010,
|
||||
VIGEM_ERROR_CALLBACK_NOT_FOUND = 0xE0000011,
|
||||
VIGEM_ERROR_BUS_ALREADY_CONNECTED = 0xE0000012,
|
||||
VIGEM_ERROR_BUS_INVALID_HANDLE = 0xE0000013,
|
||||
VIGEM_ERROR_XUSB_USERINDEX_OUT_OF_RANGE = 0xE0000014
|
||||
} VIGEM_ERROR;
|
||||
|
||||
/**
|
||||
* \def VIGEM_SUCCESS(_val_) (_val_ == VIGEM_ERROR_NONE);
|
||||
*
|
||||
* \brief A macro that defines success.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param _val_ The VIGEM_ERROR value.
|
||||
*/
|
||||
#define VIGEM_SUCCESS(_val_) (_val_ == VIGEM_ERROR_NONE)
|
||||
|
||||
/**
|
||||
* \typedef struct _VIGEM_CLIENT_T *PVIGEM_CLIENT
|
||||
*
|
||||
* \brief Defines an alias representing a driver connection object.
|
||||
*/
|
||||
typedef struct _VIGEM_CLIENT_T *PVIGEM_CLIENT;
|
||||
|
||||
/**
|
||||
* \typedef struct _VIGEM_TARGET_T *PVIGEM_TARGET
|
||||
*
|
||||
* \brief Defines an alias representing a target device object.
|
||||
*/
|
||||
typedef struct _VIGEM_TARGET_T *PVIGEM_TARGET;
|
||||
|
||||
typedef
|
||||
_Function_class_(EVT_VIGEM_TARGET_ADD_RESULT)
|
||||
VOID
|
||||
EVT_VIGEM_TARGET_ADD_RESULT(
|
||||
PVIGEM_CLIENT Client,
|
||||
PVIGEM_TARGET Target,
|
||||
VIGEM_ERROR Result
|
||||
);
|
||||
|
||||
typedef EVT_VIGEM_TARGET_ADD_RESULT *PFN_VIGEM_TARGET_ADD_RESULT;
|
||||
|
||||
typedef
|
||||
_Function_class_(EVT_VIGEM_X360_NOTIFICATION)
|
||||
VOID
|
||||
EVT_VIGEM_X360_NOTIFICATION(
|
||||
PVIGEM_CLIENT Client,
|
||||
PVIGEM_TARGET Target,
|
||||
UCHAR LargeMotor,
|
||||
UCHAR SmallMotor,
|
||||
UCHAR LedNumber
|
||||
);
|
||||
|
||||
typedef EVT_VIGEM_X360_NOTIFICATION *PFN_VIGEM_X360_NOTIFICATION;
|
||||
|
||||
typedef
|
||||
_Function_class_(EVT_VIGEM_DS4_NOTIFICATION)
|
||||
VOID
|
||||
EVT_VIGEM_DS4_NOTIFICATION(
|
||||
PVIGEM_CLIENT Client,
|
||||
PVIGEM_TARGET Target,
|
||||
UCHAR LargeMotor,
|
||||
UCHAR SmallMotor,
|
||||
DS4_LIGHTBAR_COLOR LightbarColor
|
||||
);
|
||||
|
||||
typedef EVT_VIGEM_DS4_NOTIFICATION *PFN_VIGEM_DS4_NOTIFICATION;
|
||||
|
||||
/**
|
||||
* \fn PVIGEM_CLIENT vigem_alloc(void);
|
||||
*
|
||||
* \brief Allocates an object representing a driver connection.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \return A new driver connection object.
|
||||
*/
|
||||
VIGEM_API PVIGEM_CLIENT vigem_alloc(void);
|
||||
|
||||
/**
|
||||
* \fn void vigem_free(PVIGEM_CLIENT vigem);
|
||||
*
|
||||
* \brief Frees up memory used by the driver connection object.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
*/
|
||||
VIGEM_API void vigem_free(PVIGEM_CLIENT vigem);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_connect(PVIGEM_CLIENT vigem);
|
||||
*
|
||||
* \brief Initializes the driver object and establishes a connection to the emulation bus
|
||||
* driver. Returns an error if no compatible bus device has been found.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_connect(PVIGEM_CLIENT vigem);
|
||||
|
||||
/**
|
||||
* \fn void vigem_disconnect(PVIGEM_CLIENT vigem);
|
||||
*
|
||||
* \brief Disconnects from the bus device and resets the driver object state. The driver object
|
||||
* may be reused again after calling this function. When called, all targets which may
|
||||
* still be connected will be destroyed automatically. Be aware, that allocated target
|
||||
* objects won't be automatically freed, this has to be taken care of by the caller.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
*/
|
||||
VIGEM_API void vigem_disconnect(PVIGEM_CLIENT vigem);
|
||||
|
||||
/**
|
||||
* \fn PVIGEM_TARGET vigem_target_x360_alloc(void);
|
||||
*
|
||||
* \brief Allocates an object representing an Xbox 360 Controller device.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \return A PVIGEM_TARGET representing an Xbox 360 Controller device.
|
||||
*/
|
||||
VIGEM_API PVIGEM_TARGET vigem_target_x360_alloc(void);
|
||||
|
||||
/**
|
||||
* \fn PVIGEM_TARGET vigem_target_ds4_alloc(void);
|
||||
*
|
||||
* \brief Allocates an object representing a DualShock 4 Controller device.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \return A PVIGEM_TARGET representing a DualShock 4 Controller device.
|
||||
*/
|
||||
VIGEM_API PVIGEM_TARGET vigem_target_ds4_alloc(void);
|
||||
|
||||
/**
|
||||
* \fn void vigem_target_free(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Frees up memory used by the target device object. This does not automatically remove
|
||||
* the associated device from the bus, if present. If the target device doesn't get
|
||||
* removed before this call, the device becomes orphaned until the owning process is
|
||||
* terminated.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*/
|
||||
VIGEM_API void vigem_target_free(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_add(PVIGEM_CLIENT vigem, PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Adds a provided target device to the bus driver, which is equal to a device plug-in
|
||||
* event of a physical hardware device. This function blocks until the target device is
|
||||
* in full operational mode.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_add(PVIGEM_CLIENT vigem, PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_add_async(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PVIGEM_TARGET_ADD_RESULT result);
|
||||
*
|
||||
* \brief Adds a provided target device to the bus driver, which is equal to a device plug-in
|
||||
* event of a physical hardware device. This function immediately returns. An optional
|
||||
* callback may be registered which gets called on error or if the target device has
|
||||
* become fully operational.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
* \param result An optional function getting called when the target device becomes available.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_add_async(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PFN_VIGEM_TARGET_ADD_RESULT result);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_remove(PVIGEM_CLIENT vigem, PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Removes a provided target device from the bus driver, which is equal to a device
|
||||
* unplug event of a physical hardware device. The target device object may be reused
|
||||
* after this function is called. If this function is never called on target device
|
||||
* objects, they will be removed from the bus when the owning process terminates.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_remove(PVIGEM_CLIENT vigem, PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_x360_register_notification(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PVIGEM_X360_NOTIFICATION notification);
|
||||
*
|
||||
* \brief Registers a function which gets called, when LED index or vibration state changes
|
||||
* occur on the provided target device. This function fails if the provided target
|
||||
* device isn't fully operational or in an erroneous state.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
* \param notification The notification callback.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_x360_register_notification(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PFN_VIGEM_X360_NOTIFICATION notification);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_ds4_register_notification(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PVIGEM_DS4_NOTIFICATION notification);
|
||||
*
|
||||
* \brief Registers a function which gets called, when LightBar or vibration state changes
|
||||
* occur on the provided target device. This function fails if the provided target
|
||||
* device isn't fully operational or in an erroneous state.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
* \param notification The notification callback.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_ds4_register_notification(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PFN_VIGEM_DS4_NOTIFICATION notification);
|
||||
|
||||
/**
|
||||
* \fn void vigem_target_x360_unregister_notification(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Removes a previously registered callback function from the provided target object.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*/
|
||||
VIGEM_API void vigem_target_x360_unregister_notification(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn void vigem_target_ds4_unregister_notification(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Removes a previously registered callback function from the provided target object.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*/
|
||||
VIGEM_API void vigem_target_ds4_unregister_notification(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn void vigem_target_set_vid(PVIGEM_TARGET target, USHORT vid);
|
||||
*
|
||||
* \brief Overrides the default Vendor ID value with the provided one.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
* \param vid The Vendor ID to set.
|
||||
*/
|
||||
VIGEM_API void vigem_target_set_vid(PVIGEM_TARGET target, USHORT vid);
|
||||
|
||||
/**
|
||||
* \fn void vigem_target_set_pid(PVIGEM_TARGET target, USHORT pid);
|
||||
*
|
||||
* \brief Overrides the default Product ID value with the provided one.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
* \param pid The Product ID to set.
|
||||
*/
|
||||
VIGEM_API void vigem_target_set_pid(PVIGEM_TARGET target, USHORT pid);
|
||||
|
||||
/**
|
||||
* \fn USHORT vigem_target_get_vid(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Returns the Vendor ID of the provided target device object.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return The Vendor ID.
|
||||
*/
|
||||
VIGEM_API USHORT vigem_target_get_vid(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn USHORT vigem_target_get_pid(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Returns the Product ID of the provided target device object.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return The Product ID.
|
||||
*/
|
||||
VIGEM_API USHORT vigem_target_get_pid(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_x360_update(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, XUSB_REPORT report);
|
||||
*
|
||||
* \brief Sends a state report to the provided target device.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
* \param report The report to send to the target device.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_x360_update(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, XUSB_REPORT report);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_ERROR vigem_target_ds4_update(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, DS4_REPORT report);
|
||||
*
|
||||
* \brief Sends a state report to the provided target device.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
* \param report The report to send to the target device.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_ds4_update(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, DS4_REPORT report);
|
||||
|
||||
/**
|
||||
* \fn ULONG vigem_target_get_index(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Returns the internal index (serial number) the bus driver assigned to the provided
|
||||
* target device object. Note that this value is specific to the inner workings of the
|
||||
* bus driver, it does not reflect related values like player index or device arrival
|
||||
* order experienced by other APIs. It may be used to identify the target device object
|
||||
* for its lifetime. This value becomes invalid once the target device is removed from
|
||||
* the bus and may change on the next addition of the device.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return The internally used index of the target device.
|
||||
*/
|
||||
VIGEM_API ULONG vigem_target_get_index(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_TARGET_TYPE vigem_target_get_type(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Returns the type of the provided target device object.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 28.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return A VIGEM_TARGET_TYPE.
|
||||
*/
|
||||
VIGEM_API VIGEM_TARGET_TYPE vigem_target_get_type(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn BOOL vigem_target_is_attached(PVIGEM_TARGET target);
|
||||
*
|
||||
* \brief Returns TRUE if the provided target device object is currently attached to the bus,
|
||||
* FALSE otherwise.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 30.08.2017
|
||||
*
|
||||
* \param target The target device object.
|
||||
*
|
||||
* \return TRUE if device is attached to the bus, FALSE otherwise.
|
||||
*/
|
||||
VIGEM_API BOOL vigem_target_is_attached(PVIGEM_TARGET target);
|
||||
|
||||
/**
|
||||
* \fn VIGEM_API VIGEM_ERROR vigem_target_x360_get_user_index(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PULONG index);
|
||||
*
|
||||
* \brief Returns the user index of the emulated Xenon device. This value correspondents to the
|
||||
* (zero-based) index number representing the player number via LED present on a
|
||||
* physical controller and is compatible to the dwUserIndex propery of the XInput* APIs.
|
||||
*
|
||||
* \author Benjamin "Nefarius" Höglinger
|
||||
* \date 10.05.2018
|
||||
*
|
||||
* \param vigem The driver connection object.
|
||||
* \param target The target device object.
|
||||
* \param index The (zero-based) user index of the Xenon device.
|
||||
*
|
||||
* \return A VIGEM_ERROR.
|
||||
*/
|
||||
VIGEM_API VIGEM_ERROR vigem_target_x360_get_user_index(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PULONG index);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ViGEmClient_h__
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Benjamin "Nefarius" Höglinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
//
|
||||
// Represents the desired target type for the emulated device.
|
||||
//
|
||||
typedef enum _VIGEM_TARGET_TYPE
|
||||
{
|
||||
//
|
||||
// Microsoft Xbox 360 Controller (wired)
|
||||
//
|
||||
Xbox360Wired,
|
||||
//
|
||||
// Microsoft Xbox One Controller (wired)
|
||||
//
|
||||
XboxOneWired,
|
||||
//
|
||||
// Sony DualShock 4 (wired)
|
||||
//
|
||||
DualShock4Wired
|
||||
|
||||
} VIGEM_TARGET_TYPE, *PVIGEM_TARGET_TYPE;
|
||||
|
||||
//
|
||||
// Possible XUSB report buttons.
|
||||
//
|
||||
typedef enum _XUSB_BUTTON
|
||||
{
|
||||
XUSB_GAMEPAD_DPAD_UP = 0x0001,
|
||||
XUSB_GAMEPAD_DPAD_DOWN = 0x0002,
|
||||
XUSB_GAMEPAD_DPAD_LEFT = 0x0004,
|
||||
XUSB_GAMEPAD_DPAD_RIGHT = 0x0008,
|
||||
XUSB_GAMEPAD_START = 0x0010,
|
||||
XUSB_GAMEPAD_BACK = 0x0020,
|
||||
XUSB_GAMEPAD_LEFT_THUMB = 0x0040,
|
||||
XUSB_GAMEPAD_RIGHT_THUMB = 0x0080,
|
||||
XUSB_GAMEPAD_LEFT_SHOULDER = 0x0100,
|
||||
XUSB_GAMEPAD_RIGHT_SHOULDER = 0x0200,
|
||||
XUSB_GAMEPAD_GUIDE = 0x0400,
|
||||
XUSB_GAMEPAD_A = 0x1000,
|
||||
XUSB_GAMEPAD_B = 0x2000,
|
||||
XUSB_GAMEPAD_X = 0x4000,
|
||||
XUSB_GAMEPAD_Y = 0x8000
|
||||
|
||||
} XUSB_BUTTON, *PXUSB_BUTTON;
|
||||
|
||||
//
|
||||
// Represents an XINPUT_GAMEPAD-compatible report structure.
|
||||
//
|
||||
typedef struct _XUSB_REPORT
|
||||
{
|
||||
USHORT wButtons;
|
||||
BYTE bLeftTrigger;
|
||||
BYTE bRightTrigger;
|
||||
SHORT sThumbLX;
|
||||
SHORT sThumbLY;
|
||||
SHORT sThumbRX;
|
||||
SHORT sThumbRY;
|
||||
|
||||
} XUSB_REPORT, *PXUSB_REPORT;
|
||||
|
||||
//
|
||||
// Initializes a _XUSB_REPORT structure.
|
||||
//
|
||||
VOID FORCEINLINE XUSB_REPORT_INIT(
|
||||
_Out_ PXUSB_REPORT Report
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Report, sizeof(XUSB_REPORT));
|
||||
}
|
||||
|
||||
//
|
||||
// The color value (RGB) of a DualShock 4 Lightbar
|
||||
//
|
||||
typedef struct _DS4_LIGHTBAR_COLOR
|
||||
{
|
||||
//
|
||||
// Red part of the Lightbar (0-255).
|
||||
//
|
||||
UCHAR Red;
|
||||
|
||||
//
|
||||
// Green part of the Lightbar (0-255).
|
||||
//
|
||||
UCHAR Green;
|
||||
|
||||
//
|
||||
// Blue part of the Lightbar (0-255).
|
||||
//
|
||||
UCHAR Blue;
|
||||
|
||||
} DS4_LIGHTBAR_COLOR, *PDS4_LIGHTBAR_COLOR;
|
||||
|
||||
//
|
||||
// DualShock 4 digital buttons
|
||||
//
|
||||
typedef enum _DS4_BUTTONS
|
||||
{
|
||||
DS4_BUTTON_THUMB_RIGHT = 1 << 15,
|
||||
DS4_BUTTON_THUMB_LEFT = 1 << 14,
|
||||
DS4_BUTTON_OPTIONS = 1 << 13,
|
||||
DS4_BUTTON_SHARE = 1 << 12,
|
||||
DS4_BUTTON_TRIGGER_RIGHT = 1 << 11,
|
||||
DS4_BUTTON_TRIGGER_LEFT = 1 << 10,
|
||||
DS4_BUTTON_SHOULDER_RIGHT = 1 << 9,
|
||||
DS4_BUTTON_SHOULDER_LEFT = 1 << 8,
|
||||
DS4_BUTTON_TRIANGLE = 1 << 7,
|
||||
DS4_BUTTON_CIRCLE = 1 << 6,
|
||||
DS4_BUTTON_CROSS = 1 << 5,
|
||||
DS4_BUTTON_SQUARE = 1 << 4
|
||||
|
||||
} DS4_BUTTONS, *PDS4_BUTTONS;
|
||||
|
||||
//
|
||||
// DualShock 4 special buttons
|
||||
//
|
||||
typedef enum _DS4_SPECIAL_BUTTONS
|
||||
{
|
||||
DS4_SPECIAL_BUTTON_PS = 1 << 0,
|
||||
DS4_SPECIAL_BUTTON_TOUCHPAD = 1 << 1
|
||||
|
||||
} DS4_SPECIAL_BUTTONS, *PDS4_SPECIAL_BUTTONS;
|
||||
|
||||
//
|
||||
// DualShock 4 directional pad (HAT) values
|
||||
//
|
||||
typedef enum _DS4_DPAD_DIRECTIONS
|
||||
{
|
||||
DS4_BUTTON_DPAD_NONE = 0x8,
|
||||
DS4_BUTTON_DPAD_NORTHWEST = 0x7,
|
||||
DS4_BUTTON_DPAD_WEST = 0x6,
|
||||
DS4_BUTTON_DPAD_SOUTHWEST = 0x5,
|
||||
DS4_BUTTON_DPAD_SOUTH = 0x4,
|
||||
DS4_BUTTON_DPAD_SOUTHEAST = 0x3,
|
||||
DS4_BUTTON_DPAD_EAST = 0x2,
|
||||
DS4_BUTTON_DPAD_NORTHEAST = 0x1,
|
||||
DS4_BUTTON_DPAD_NORTH = 0x0
|
||||
|
||||
} DS4_DPAD_DIRECTIONS, *PDS4_DPAD_DIRECTIONS;
|
||||
|
||||
//
|
||||
// DualShock 4 HID Input report
|
||||
//
|
||||
typedef struct _DS4_REPORT
|
||||
{
|
||||
BYTE bThumbLX;
|
||||
BYTE bThumbLY;
|
||||
BYTE bThumbRX;
|
||||
BYTE bThumbRY;
|
||||
USHORT wButtons;
|
||||
BYTE bSpecial;
|
||||
BYTE bTriggerL;
|
||||
BYTE bTriggerR;
|
||||
|
||||
} DS4_REPORT, *PDS4_REPORT;
|
||||
|
||||
//
|
||||
// Sets the current state of the D-PAD on a DualShock 4 report.
|
||||
//
|
||||
VOID FORCEINLINE DS4_SET_DPAD(
|
||||
_Out_ PDS4_REPORT Report,
|
||||
_In_ DS4_DPAD_DIRECTIONS Dpad
|
||||
)
|
||||
{
|
||||
Report->wButtons &= ~0xF;
|
||||
Report->wButtons |= (USHORT)Dpad;
|
||||
}
|
||||
|
||||
VOID FORCEINLINE DS4_REPORT_INIT(
|
||||
_Out_ PDS4_REPORT Report
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Report, sizeof(DS4_REPORT));
|
||||
|
||||
Report->bThumbLX = 0x80;
|
||||
Report->bThumbLY = 0x80;
|
||||
Report->bThumbRX = 0x80;
|
||||
Report->bThumbRY = 0x80;
|
||||
|
||||
DS4_SET_DPAD(Report, DS4_BUTTON_DPAD_NONE);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "ViGEmCommon.h"
|
||||
#include <limits.h>
|
||||
|
||||
VOID FORCEINLINE XUSB_TO_DS4_REPORT(
|
||||
_Out_ PXUSB_REPORT Input,
|
||||
_Out_ PDS4_REPORT Output
|
||||
)
|
||||
{
|
||||
if (Input->wButtons & XUSB_GAMEPAD_BACK) Output->wButtons |= DS4_BUTTON_SHARE;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_START) Output->wButtons |= DS4_BUTTON_OPTIONS;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_LEFT_THUMB) Output->wButtons |= DS4_BUTTON_THUMB_LEFT;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_RIGHT_THUMB) Output->wButtons |= DS4_BUTTON_THUMB_RIGHT;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_LEFT_SHOULDER) Output->wButtons |= DS4_BUTTON_SHOULDER_LEFT;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_RIGHT_SHOULDER) Output->wButtons |= DS4_BUTTON_SHOULDER_RIGHT;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_GUIDE) Output->bSpecial |= DS4_SPECIAL_BUTTON_PS;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_A) Output->wButtons |= DS4_BUTTON_CROSS;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_B) Output->wButtons |= DS4_BUTTON_CIRCLE;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_X) Output->wButtons |= DS4_BUTTON_SQUARE;
|
||||
if (Input->wButtons & XUSB_GAMEPAD_Y) Output->wButtons |= DS4_BUTTON_TRIANGLE;
|
||||
|
||||
Output->bTriggerL = Input->bLeftTrigger;
|
||||
Output->bTriggerR = Input->bRightTrigger;
|
||||
|
||||
if (Input->bLeftTrigger > 0)Output->wButtons |= DS4_BUTTON_TRIGGER_LEFT;
|
||||
if (Input->bRightTrigger > 0)Output->wButtons |= DS4_BUTTON_TRIGGER_RIGHT;
|
||||
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_UP) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_NORTH);
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_RIGHT) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_EAST);
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_DOWN) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_SOUTH);
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_LEFT) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_WEST);
|
||||
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_UP
|
||||
&& Input->wButtons & XUSB_GAMEPAD_DPAD_RIGHT) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_NORTHEAST);
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_RIGHT
|
||||
&& Input->wButtons & XUSB_GAMEPAD_DPAD_DOWN) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_SOUTHEAST);
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_DOWN
|
||||
&& Input->wButtons & XUSB_GAMEPAD_DPAD_LEFT) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_SOUTHWEST);
|
||||
if (Input->wButtons & XUSB_GAMEPAD_DPAD_LEFT
|
||||
&& Input->wButtons & XUSB_GAMEPAD_DPAD_UP) DS4_SET_DPAD(Output, DS4_BUTTON_DPAD_NORTHWEST);
|
||||
|
||||
Output->bThumbLX = ((Input->sThumbLX + ((USHRT_MAX / 2) + 1)) / 257);
|
||||
Output->bThumbLY = (-(Input->sThumbLY + ((USHRT_MAX / 2) - 1)) / 257);
|
||||
Output->bThumbLY = (Output->bThumbLY == 0) ? 0xFF : Output->bThumbLY;
|
||||
Output->bThumbRX = ((Input->sThumbRX + ((USHRT_MAX / 2) + 1)) / 257);
|
||||
Output->bThumbRY = (-(Input->sThumbRY + ((USHRT_MAX / 2) + 1)) / 257);
|
||||
Output->bThumbRY = (Output->bThumbRY == 0) ? 0xFF : Output->bThumbRY;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,493 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Benjamin "Nefarius" Höglinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
// {96E42B22-F5E9-42F8-B043-ED0F932F014F}
|
||||
// ReSharper disable once CppMissingIncludeGuard
|
||||
DEFINE_GUID(GUID_DEVINTERFACE_BUSENUM_VIGEM,
|
||||
0x96E42B22, 0xF5E9, 0x42F8, 0xB0, 0x43, 0xED, 0x0F, 0x93, 0x2F, 0x01, 0x4F);
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ViGEmCommon.h"
|
||||
|
||||
//
|
||||
// Common version for user-mode library and driver compatibility
|
||||
//
|
||||
// On initialization, the user-mode library has this number embedded
|
||||
// and sends it to the bus on its enumeration. The bus compares this
|
||||
// number to the one it was compiled with. If they match, the bus
|
||||
// access is permitted and success reported. If they mismatch, an
|
||||
// error is reported and the user-mode library skips this instance.
|
||||
//
|
||||
#define VIGEM_COMMON_VERSION 0x0001
|
||||
|
||||
#define FILE_DEVICE_BUSENUM FILE_DEVICE_BUS_EXTENDER
|
||||
#define BUSENUM_IOCTL(_index_) CTL_CODE(FILE_DEVICE_BUSENUM, _index_, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
#define BUSENUM_W_IOCTL(_index_) CTL_CODE(FILE_DEVICE_BUSENUM, _index_, METHOD_BUFFERED, FILE_WRITE_DATA)
|
||||
#define BUSENUM_R_IOCTL(_index_) CTL_CODE(FILE_DEVICE_BUSENUM, _index_, METHOD_BUFFERED, FILE_READ_DATA)
|
||||
#define BUSENUM_RW_IOCTL(_index_) CTL_CODE(FILE_DEVICE_BUSENUM, _index_, METHOD_BUFFERED, FILE_WRITE_DATA | FILE_READ_DATA)
|
||||
|
||||
#define IOCTL_VIGEM_BASE 0x801
|
||||
|
||||
//
|
||||
// IO control codes
|
||||
//
|
||||
#define IOCTL_VIGEM_PLUGIN_TARGET BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x000)
|
||||
#define IOCTL_VIGEM_UNPLUG_TARGET BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x001)
|
||||
#define IOCTL_VIGEM_CHECK_VERSION BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x002)
|
||||
|
||||
#define IOCTL_XUSB_REQUEST_NOTIFICATION BUSENUM_RW_IOCTL(IOCTL_VIGEM_BASE + 0x200)
|
||||
#define IOCTL_XUSB_SUBMIT_REPORT BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x201)
|
||||
#define IOCTL_DS4_SUBMIT_REPORT BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x202)
|
||||
#define IOCTL_DS4_REQUEST_NOTIFICATION BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x203)
|
||||
#define IOCTL_XGIP_SUBMIT_REPORT BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x204)
|
||||
#define IOCTL_XGIP_SUBMIT_INTERRUPT BUSENUM_W_IOCTL (IOCTL_VIGEM_BASE + 0x205)
|
||||
#define IOCTL_XUSB_GET_USER_INDEX BUSENUM_RW_IOCTL(IOCTL_VIGEM_BASE + 0x206)
|
||||
|
||||
|
||||
//
|
||||
// Data structure used in PlugIn and UnPlug ioctls
|
||||
//
|
||||
|
||||
#pragma region Plugin
|
||||
|
||||
//
|
||||
// Data structure used in IOCTL_VIGEM_PLUGIN_TARGET requests.
|
||||
//
|
||||
typedef struct _VIGEM_PLUGIN_TARGET
|
||||
{
|
||||
//
|
||||
// sizeof (struct _BUSENUM_HARDWARE)
|
||||
//
|
||||
IN ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
IN ULONG SerialNo;
|
||||
|
||||
//
|
||||
// Type of the target device to emulate.
|
||||
//
|
||||
VIGEM_TARGET_TYPE TargetType;
|
||||
|
||||
//
|
||||
// If set, the vendor ID the emulated device is reporting
|
||||
//
|
||||
USHORT VendorId;
|
||||
|
||||
//
|
||||
// If set, the product ID the emulated device is reporting
|
||||
//
|
||||
USHORT ProductId;
|
||||
|
||||
} VIGEM_PLUGIN_TARGET, *PVIGEM_PLUGIN_TARGET;
|
||||
|
||||
//
|
||||
// Initializes a VIGEM_PLUGIN_TARGET structure.
|
||||
//
|
||||
VOID FORCEINLINE VIGEM_PLUGIN_TARGET_INIT(
|
||||
_Out_ PVIGEM_PLUGIN_TARGET PlugIn,
|
||||
_In_ ULONG SerialNo,
|
||||
_In_ VIGEM_TARGET_TYPE TargetType
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(PlugIn, sizeof(VIGEM_PLUGIN_TARGET));
|
||||
|
||||
PlugIn->Size = sizeof(VIGEM_PLUGIN_TARGET);
|
||||
PlugIn->SerialNo = SerialNo;
|
||||
PlugIn->TargetType = TargetType;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Unplug
|
||||
|
||||
//
|
||||
// Data structure used in IOCTL_VIGEM_UNPLUG_TARGET requests.
|
||||
//
|
||||
typedef struct _VIGEM_UNPLUG_TARGET
|
||||
{
|
||||
//
|
||||
// sizeof (struct _REMOVE_HARDWARE)
|
||||
//
|
||||
IN ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
} VIGEM_UNPLUG_TARGET, *PVIGEM_UNPLUG_TARGET;
|
||||
|
||||
//
|
||||
// Initializes a VIGEM_UNPLUG_TARGET structure.
|
||||
//
|
||||
VOID FORCEINLINE VIGEM_UNPLUG_TARGET_INIT(
|
||||
_Out_ PVIGEM_UNPLUG_TARGET UnPlug,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(UnPlug, sizeof(VIGEM_UNPLUG_TARGET));
|
||||
|
||||
UnPlug->Size = sizeof(VIGEM_UNPLUG_TARGET);
|
||||
UnPlug->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region Check version
|
||||
|
||||
typedef struct _VIGEM_CHECK_VERSION
|
||||
{
|
||||
IN ULONG Size;
|
||||
|
||||
IN ULONG Version;
|
||||
|
||||
} VIGEM_CHECK_VERSION, *PVIGEM_CHECK_VERSION;
|
||||
|
||||
VOID FORCEINLINE VIGEM_CHECK_VERSION_INIT(
|
||||
_Out_ PVIGEM_CHECK_VERSION CheckVersion,
|
||||
_In_ ULONG Version
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(CheckVersion, sizeof(VIGEM_CHECK_VERSION));
|
||||
|
||||
CheckVersion->Size = sizeof(VIGEM_CHECK_VERSION);
|
||||
CheckVersion->Version = Version;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region XUSB (aka Xbox 360 device) section
|
||||
|
||||
//
|
||||
// Data structure used in IOCTL_XUSB_REQUEST_NOTIFICATION requests.
|
||||
//
|
||||
typedef struct _XUSB_REQUEST_NOTIFICATION
|
||||
{
|
||||
//
|
||||
// sizeof(struct _XUSB_REQUEST_NOTIFICATION)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// Vibration intensity value of the large motor (0-255).
|
||||
//
|
||||
UCHAR LargeMotor;
|
||||
|
||||
//
|
||||
// Vibration intensity value of the small motor (0-255).
|
||||
//
|
||||
UCHAR SmallMotor;
|
||||
|
||||
//
|
||||
// Index number of the slot/LED that XUSB.sys has assigned.
|
||||
//
|
||||
UCHAR LedNumber;
|
||||
|
||||
} XUSB_REQUEST_NOTIFICATION, *PXUSB_REQUEST_NOTIFICATION;
|
||||
|
||||
//
|
||||
// Initializes a XUSB_REQUEST_NOTIFICATION structure.
|
||||
//
|
||||
VOID FORCEINLINE XUSB_REQUEST_NOTIFICATION_INIT(
|
||||
_Out_ PXUSB_REQUEST_NOTIFICATION Request,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Request, sizeof(XUSB_REQUEST_NOTIFICATION));
|
||||
|
||||
Request->Size = sizeof(XUSB_REQUEST_NOTIFICATION);
|
||||
Request->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
//
|
||||
// Data structure used in IOCTL_XUSB_SUBMIT_REPORT requests.
|
||||
//
|
||||
typedef struct _XUSB_SUBMIT_REPORT
|
||||
{
|
||||
//
|
||||
// sizeof(struct _XUSB_SUBMIT_REPORT)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// Report to submit to the target device.
|
||||
//
|
||||
XUSB_REPORT Report;
|
||||
|
||||
} XUSB_SUBMIT_REPORT, *PXUSB_SUBMIT_REPORT;
|
||||
|
||||
//
|
||||
// Initializes an XUSB report.
|
||||
//
|
||||
VOID FORCEINLINE XUSB_SUBMIT_REPORT_INIT(
|
||||
_Out_ PXUSB_SUBMIT_REPORT Report,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Report, sizeof(XUSB_SUBMIT_REPORT));
|
||||
|
||||
Report->Size = sizeof(XUSB_SUBMIT_REPORT);
|
||||
Report->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
typedef struct _XUSB_GET_USER_INDEX
|
||||
{
|
||||
//
|
||||
// sizeof(struct _XUSB_GET_USER_INDEX)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// User index of target device.
|
||||
//
|
||||
OUT ULONG UserIndex;
|
||||
|
||||
} XUSB_GET_USER_INDEX, *PXUSB_GET_USER_INDEX;
|
||||
|
||||
//
|
||||
// Initializes XUSB_GET_USER_INDEX structure.
|
||||
//
|
||||
VOID FORCEINLINE XUSB_GET_USER_INDEX_INIT(
|
||||
_Out_ PXUSB_GET_USER_INDEX GetRequest,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(GetRequest, sizeof(XUSB_GET_USER_INDEX));
|
||||
|
||||
GetRequest->Size = sizeof(XUSB_GET_USER_INDEX);
|
||||
GetRequest->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region DualShock 4 section
|
||||
|
||||
typedef struct _DS4_OUTPUT_REPORT
|
||||
{
|
||||
//
|
||||
// Vibration intensity value of the small motor (0-255).
|
||||
//
|
||||
UCHAR SmallMotor;
|
||||
|
||||
//
|
||||
// Vibration intensity value of the large motor (0-255).
|
||||
//
|
||||
UCHAR LargeMotor;
|
||||
|
||||
//
|
||||
// Color values of the Lightbar.
|
||||
//
|
||||
DS4_LIGHTBAR_COLOR LightbarColor;
|
||||
|
||||
} DS4_OUTPUT_REPORT, *PDS4_OUTPUT_REPORT;
|
||||
|
||||
//
|
||||
// Data structure used in IOCTL_DS4_REQUEST_NOTIFICATION requests.
|
||||
//
|
||||
typedef struct _DS4_REQUEST_NOTIFICATION
|
||||
{
|
||||
//
|
||||
// sizeof(struct _XUSB_REQUEST_NOTIFICATION)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// The HID output report
|
||||
//
|
||||
DS4_OUTPUT_REPORT Report;
|
||||
|
||||
} DS4_REQUEST_NOTIFICATION, *PDS4_REQUEST_NOTIFICATION;
|
||||
|
||||
//
|
||||
// Initializes a DS4_REQUEST_NOTIFICATION structure.
|
||||
//
|
||||
VOID FORCEINLINE DS4_REQUEST_NOTIFICATION_INIT(
|
||||
_Out_ PDS4_REQUEST_NOTIFICATION Request,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Request, sizeof(DS4_REQUEST_NOTIFICATION));
|
||||
|
||||
Request->Size = sizeof(DS4_REQUEST_NOTIFICATION);
|
||||
Request->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
//
|
||||
// DualShock 4 request data
|
||||
//
|
||||
typedef struct _DS4_SUBMIT_REPORT
|
||||
{
|
||||
//
|
||||
// sizeof(struct _DS4_SUBMIT_REPORT)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// HID Input report
|
||||
//
|
||||
DS4_REPORT Report;
|
||||
|
||||
} DS4_SUBMIT_REPORT, *PDS4_SUBMIT_REPORT;
|
||||
|
||||
//
|
||||
// Initializes a DualShock 4 report.
|
||||
//
|
||||
VOID FORCEINLINE DS4_SUBMIT_REPORT_INIT(
|
||||
_Out_ PDS4_SUBMIT_REPORT Report,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Report, sizeof(DS4_SUBMIT_REPORT));
|
||||
|
||||
Report->Size = sizeof(DS4_SUBMIT_REPORT);
|
||||
Report->SerialNo = SerialNo;
|
||||
|
||||
DS4_REPORT_INIT(&Report->Report);
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region XGIP (aka Xbox One device) section - EXPERIMENTAL
|
||||
|
||||
typedef struct _XGIP_REPORT
|
||||
{
|
||||
UCHAR Buttons1;
|
||||
UCHAR Buttons2;
|
||||
SHORT LeftTrigger;
|
||||
SHORT RightTrigger;
|
||||
SHORT ThumbLX;
|
||||
SHORT ThumbLY;
|
||||
SHORT ThumbRX;
|
||||
SHORT ThumbRY;
|
||||
|
||||
} XGIP_REPORT, *PXGIP_REPORT;
|
||||
|
||||
//
|
||||
// Xbox One request data
|
||||
//
|
||||
typedef struct _XGIP_SUBMIT_REPORT
|
||||
{
|
||||
//
|
||||
// sizeof(struct _XGIP_SUBMIT_REPORT)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// HID Input report
|
||||
//
|
||||
XGIP_REPORT Report;
|
||||
|
||||
} XGIP_SUBMIT_REPORT, *PXGIP_SUBMIT_REPORT;
|
||||
|
||||
//
|
||||
// Initializes an Xbox One report.
|
||||
//
|
||||
VOID FORCEINLINE XGIP_SUBMIT_REPORT_INIT(
|
||||
_Out_ PXGIP_SUBMIT_REPORT Report,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Report, sizeof(XGIP_SUBMIT_REPORT));
|
||||
|
||||
Report->Size = sizeof(XGIP_SUBMIT_REPORT);
|
||||
Report->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
//
|
||||
// Xbox One interrupt data
|
||||
//
|
||||
typedef struct _XGIP_SUBMIT_INTERRUPT
|
||||
{
|
||||
//
|
||||
// sizeof(struct _XGIP_SUBMIT_INTERRUPT)
|
||||
//
|
||||
ULONG Size;
|
||||
|
||||
//
|
||||
// Serial number of target device.
|
||||
//
|
||||
ULONG SerialNo;
|
||||
|
||||
//
|
||||
// Interrupt buffer.
|
||||
//
|
||||
UCHAR Interrupt[64];
|
||||
|
||||
//
|
||||
// Length of interrupt buffer.
|
||||
//
|
||||
ULONG InterruptLength;
|
||||
|
||||
} XGIP_SUBMIT_INTERRUPT, *PXGIP_SUBMIT_INTERRUPT;
|
||||
|
||||
//
|
||||
// Initializes an Xbox One interrupt.
|
||||
//
|
||||
VOID FORCEINLINE XGIP_SUBMIT_INTERRUPT_INIT(
|
||||
_Out_ PXGIP_SUBMIT_INTERRUPT Report,
|
||||
_In_ ULONG SerialNo
|
||||
)
|
||||
{
|
||||
RtlZeroMemory(Report, sizeof(XGIP_SUBMIT_INTERRUPT));
|
||||
|
||||
Report->Size = sizeof(XGIP_SUBMIT_INTERRUPT);
|
||||
Report->SerialNo = SerialNo;
|
||||
}
|
||||
|
||||
#pragma endregion
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
# ViGEm user-mode client library
|
||||
This static library provides the gateway to the bus drivers functionalities. It offers:
|
||||
* Searching and connecting to a library-compatible bus device on the system
|
||||
* Attaching and removing a (artificially limited) number of virtual devices
|
||||
* Feeding the emulated devices (aka providing input state changes)
|
||||
|
||||
## Dependencies
|
||||
In addition to this library you'll need to link against `setupapi.lib`.
|
||||
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Benjamin "Nefarius" Höglinger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <cstdlib>
|
||||
#include <SetupAPI.h>
|
||||
#include <initguid.h>
|
||||
|
||||
#include "km/ViGEmBusShared.h"
|
||||
#include "ViGEmClient.h"
|
||||
#include <winioctl.h>
|
||||
#include <climits>
|
||||
#include <mutex>
|
||||
|
||||
#define VIGEM_TARGETS_MAX USHRT_MAX
|
||||
|
||||
//
|
||||
// Represents a driver connection object.
|
||||
//
|
||||
typedef struct _VIGEM_CLIENT_T
|
||||
{
|
||||
HANDLE hBusDevice;
|
||||
|
||||
} VIGEM_CLIENT;
|
||||
|
||||
//
|
||||
// Represents the (connection) state of a target device object.
|
||||
//
|
||||
typedef enum _VIGEM_TARGET_STATE
|
||||
{
|
||||
VIGEM_TARGET_NEW,
|
||||
VIGEM_TARGET_INITIALIZED,
|
||||
VIGEM_TARGET_CONNECTED,
|
||||
VIGEM_TARGET_DISCONNECTED
|
||||
} VIGEM_TARGET_STATE, *PVIGEM_TARGET_STATE;
|
||||
|
||||
//
|
||||
// Represents a virtual gamepad object.
|
||||
//
|
||||
typedef struct _VIGEM_TARGET_T
|
||||
{
|
||||
ULONG Size;
|
||||
ULONG SerialNo;
|
||||
VIGEM_TARGET_STATE State;
|
||||
USHORT VendorId;
|
||||
USHORT ProductId;
|
||||
VIGEM_TARGET_TYPE Type;
|
||||
DWORD_PTR Notification;
|
||||
|
||||
} VIGEM_TARGET;
|
||||
|
||||
//
|
||||
// Initializes a virtual gamepad object.
|
||||
//
|
||||
PVIGEM_TARGET FORCEINLINE VIGEM_TARGET_ALLOC_INIT(
|
||||
_In_ VIGEM_TARGET_TYPE Type
|
||||
)
|
||||
{
|
||||
auto target = static_cast<PVIGEM_TARGET>(malloc(sizeof(VIGEM_TARGET)));
|
||||
RtlZeroMemory(target, sizeof(VIGEM_TARGET));
|
||||
|
||||
target->Size = sizeof(VIGEM_TARGET);
|
||||
target->State = VIGEM_TARGET_INITIALIZED;
|
||||
target->Type = Type;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
|
||||
PVIGEM_CLIENT vigem_alloc()
|
||||
{
|
||||
const auto driver = static_cast<PVIGEM_CLIENT>(malloc(sizeof(VIGEM_CLIENT)));
|
||||
|
||||
if (!driver)
|
||||
return nullptr;
|
||||
|
||||
RtlZeroMemory(driver, sizeof(VIGEM_CLIENT));
|
||||
driver->hBusDevice = INVALID_HANDLE_VALUE;
|
||||
|
||||
return driver;
|
||||
}
|
||||
|
||||
void vigem_free(PVIGEM_CLIENT vigem)
|
||||
{
|
||||
if (vigem)
|
||||
free(vigem);
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_connect(PVIGEM_CLIENT vigem)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { 0 };
|
||||
deviceInterfaceData.cbSize = sizeof(deviceInterfaceData);
|
||||
DWORD memberIndex = 0;
|
||||
DWORD requiredSize = 0;
|
||||
auto error = VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
// check for already open handle as re-opening accidentally would destroy all live targets
|
||||
if (vigem->hBusDevice != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
return VIGEM_ERROR_BUS_ALREADY_CONNECTED;
|
||||
}
|
||||
|
||||
const auto deviceInfoSet = SetupDiGetClassDevs(
|
||||
&GUID_DEVINTERFACE_BUSENUM_VIGEM,
|
||||
nullptr,
|
||||
nullptr,
|
||||
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE
|
||||
);
|
||||
|
||||
// enumerate device instances
|
||||
while (SetupDiEnumDeviceInterfaces(
|
||||
deviceInfoSet,
|
||||
nullptr,
|
||||
&GUID_DEVINTERFACE_BUSENUM_VIGEM,
|
||||
memberIndex++,
|
||||
&deviceInterfaceData
|
||||
))
|
||||
{
|
||||
// get required target buffer size
|
||||
SetupDiGetDeviceInterfaceDetail(deviceInfoSet, &deviceInterfaceData, nullptr, 0, &requiredSize, nullptr);
|
||||
|
||||
// allocate target buffer
|
||||
const auto detailDataBuffer = static_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA>(malloc(requiredSize));
|
||||
detailDataBuffer->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
||||
|
||||
// get detail buffer
|
||||
if (!SetupDiGetDeviceInterfaceDetail(
|
||||
deviceInfoSet,
|
||||
&deviceInterfaceData,
|
||||
detailDataBuffer,
|
||||
requiredSize,
|
||||
&requiredSize,
|
||||
nullptr
|
||||
))
|
||||
{
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
free(detailDataBuffer);
|
||||
error = VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
continue;
|
||||
}
|
||||
|
||||
// bus found, open it
|
||||
vigem->hBusDevice = CreateFile(
|
||||
detailDataBuffer->DevicePath,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED,
|
||||
nullptr
|
||||
);
|
||||
|
||||
// check bus open result
|
||||
if (vigem->hBusDevice == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
error = VIGEM_ERROR_BUS_ACCESS_FAILED;
|
||||
free(detailDataBuffer);
|
||||
continue;
|
||||
}
|
||||
|
||||
DWORD transfered = 0;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
VIGEM_CHECK_VERSION version;
|
||||
VIGEM_CHECK_VERSION_INIT(&version, VIGEM_COMMON_VERSION);
|
||||
|
||||
// send compiled library version to driver to check compatibility
|
||||
DeviceIoControl(
|
||||
vigem->hBusDevice,
|
||||
IOCTL_VIGEM_CHECK_VERSION,
|
||||
&version,
|
||||
version.Size,
|
||||
nullptr,
|
||||
0,
|
||||
&transfered,
|
||||
&lOverlapped
|
||||
);
|
||||
|
||||
// wait for result
|
||||
if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) != 0)
|
||||
{
|
||||
error = VIGEM_ERROR_NONE;
|
||||
free(detailDataBuffer);
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
break;
|
||||
}
|
||||
|
||||
error = VIGEM_ERROR_BUS_VERSION_MISMATCH;
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
free(detailDataBuffer);
|
||||
}
|
||||
|
||||
SetupDiDestroyDeviceInfoList(deviceInfoSet);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
void vigem_disconnect(PVIGEM_CLIENT vigem)
|
||||
{
|
||||
if (!vigem)
|
||||
return;
|
||||
|
||||
if (vigem->hBusDevice != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(vigem->hBusDevice);
|
||||
|
||||
RtlZeroMemory(vigem, sizeof(VIGEM_CLIENT));
|
||||
vigem->hBusDevice = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
}
|
||||
|
||||
PVIGEM_TARGET vigem_target_x360_alloc(void)
|
||||
{
|
||||
const auto target = VIGEM_TARGET_ALLOC_INIT(Xbox360Wired);
|
||||
|
||||
if (!target)
|
||||
return nullptr;
|
||||
|
||||
target->VendorId = 0x045E;
|
||||
target->ProductId = 0x028E;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
PVIGEM_TARGET vigem_target_ds4_alloc(void)
|
||||
{
|
||||
const auto target = VIGEM_TARGET_ALLOC_INIT(DualShock4Wired);
|
||||
|
||||
if (!target)
|
||||
return nullptr;
|
||||
|
||||
target->VendorId = 0x054C;
|
||||
target->ProductId = 0x05C4;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
void vigem_target_free(PVIGEM_TARGET target)
|
||||
{
|
||||
if (target)
|
||||
free(target);
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_add(PVIGEM_CLIENT vigem, PVIGEM_TARGET target)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->State == VIGEM_TARGET_NEW)
|
||||
return VIGEM_ERROR_TARGET_UNINITIALIZED;
|
||||
|
||||
if (target->State == VIGEM_TARGET_CONNECTED)
|
||||
return VIGEM_ERROR_ALREADY_CONNECTED;
|
||||
|
||||
DWORD transfered = 0;
|
||||
VIGEM_PLUGIN_TARGET plugin;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
for (target->SerialNo = 1; target->SerialNo <= VIGEM_TARGETS_MAX; target->SerialNo++)
|
||||
{
|
||||
VIGEM_PLUGIN_TARGET_INIT(&plugin, target->SerialNo, target->Type);
|
||||
|
||||
plugin.VendorId = target->VendorId;
|
||||
plugin.ProductId = target->ProductId;
|
||||
|
||||
DeviceIoControl(
|
||||
vigem->hBusDevice,
|
||||
IOCTL_VIGEM_PLUGIN_TARGET,
|
||||
&plugin,
|
||||
plugin.Size,
|
||||
nullptr,
|
||||
0,
|
||||
&transfered,
|
||||
&lOverlapped
|
||||
);
|
||||
|
||||
if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) != 0)
|
||||
{
|
||||
target->State = VIGEM_TARGET_CONNECTED;
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
return VIGEM_ERROR_NO_FREE_SLOT;
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_add_async(PVIGEM_CLIENT vigem, PVIGEM_TARGET target, PFN_VIGEM_TARGET_ADD_RESULT result)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->State == VIGEM_TARGET_NEW)
|
||||
return VIGEM_ERROR_TARGET_UNINITIALIZED;
|
||||
|
||||
if (target->State == VIGEM_TARGET_CONNECTED)
|
||||
return VIGEM_ERROR_ALREADY_CONNECTED;
|
||||
|
||||
std::thread _async{ [](
|
||||
PVIGEM_TARGET _Target,
|
||||
PVIGEM_CLIENT _Client,
|
||||
PFN_VIGEM_TARGET_ADD_RESULT _Result)
|
||||
{
|
||||
DWORD transfered = 0;
|
||||
VIGEM_PLUGIN_TARGET plugin;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
for (_Target->SerialNo = 1; _Target->SerialNo <= VIGEM_TARGETS_MAX; _Target->SerialNo++)
|
||||
{
|
||||
VIGEM_PLUGIN_TARGET_INIT(&plugin, _Target->SerialNo, _Target->Type);
|
||||
|
||||
plugin.VendorId = _Target->VendorId;
|
||||
plugin.ProductId = _Target->ProductId;
|
||||
|
||||
DeviceIoControl(_Client->hBusDevice, IOCTL_VIGEM_PLUGIN_TARGET, &plugin, plugin.Size, nullptr, 0, &transfered, &lOverlapped);
|
||||
|
||||
if (GetOverlappedResult(_Client->hBusDevice, &lOverlapped, &transfered, TRUE) != 0)
|
||||
{
|
||||
_Target->State = VIGEM_TARGET_CONNECTED;
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
if (_Result)
|
||||
_Result(_Client, _Target, VIGEM_ERROR_NONE);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
if (_Result)
|
||||
_Result(_Client, _Target, VIGEM_ERROR_NO_FREE_SLOT);
|
||||
|
||||
}, target, vigem, result };
|
||||
|
||||
_async.detach();
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_remove(PVIGEM_CLIENT vigem, PVIGEM_TARGET target)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->State == VIGEM_TARGET_NEW)
|
||||
return VIGEM_ERROR_TARGET_UNINITIALIZED;
|
||||
|
||||
if (target->State != VIGEM_TARGET_CONNECTED)
|
||||
return VIGEM_ERROR_TARGET_NOT_PLUGGED_IN;
|
||||
|
||||
DWORD transfered = 0;
|
||||
VIGEM_UNPLUG_TARGET unplug;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
VIGEM_UNPLUG_TARGET_INIT(&unplug, target->SerialNo);
|
||||
|
||||
DeviceIoControl(
|
||||
vigem->hBusDevice,
|
||||
IOCTL_VIGEM_UNPLUG_TARGET,
|
||||
&unplug,
|
||||
unplug.Size,
|
||||
nullptr,
|
||||
0,
|
||||
&transfered,
|
||||
&lOverlapped
|
||||
);
|
||||
|
||||
if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) != 0)
|
||||
{
|
||||
target->State = VIGEM_TARGET_DISCONNECTED;
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
return VIGEM_ERROR_REMOVAL_FAILED;
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_x360_register_notification(
|
||||
PVIGEM_CLIENT vigem,
|
||||
PVIGEM_TARGET target,
|
||||
PFN_VIGEM_X360_NOTIFICATION notification
|
||||
)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->SerialNo == 0 || notification == nullptr)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (target->Notification == reinterpret_cast<DWORD_PTR>(notification))
|
||||
return VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED;
|
||||
|
||||
target->Notification = reinterpret_cast<DWORD_PTR>(notification);
|
||||
|
||||
std::thread _async{ [](
|
||||
PVIGEM_TARGET _Target,
|
||||
PVIGEM_CLIENT _Client)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
DWORD transfered = 0;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
XUSB_REQUEST_NOTIFICATION notify;
|
||||
XUSB_REQUEST_NOTIFICATION_INIT(¬ify, _Target->SerialNo);
|
||||
|
||||
do
|
||||
{
|
||||
DeviceIoControl(_Client->hBusDevice,
|
||||
IOCTL_XUSB_REQUEST_NOTIFICATION,
|
||||
¬ify,
|
||||
notify.Size,
|
||||
¬ify,
|
||||
notify.Size,
|
||||
&transfered,
|
||||
&lOverlapped);
|
||||
|
||||
if (GetOverlappedResult(_Client->hBusDevice, &lOverlapped, &transfered, TRUE) != 0)
|
||||
{
|
||||
if (_Target->Notification == NULL)
|
||||
{
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
reinterpret_cast<PFN_VIGEM_X360_NOTIFICATION>(_Target->Notification)(
|
||||
_Client,
|
||||
_Target,
|
||||
notify.LargeMotor,
|
||||
notify.SmallMotor,
|
||||
notify.LedNumber);
|
||||
}
|
||||
else
|
||||
{
|
||||
error = GetLastError();
|
||||
}
|
||||
} while (error != ERROR_OPERATION_ABORTED && error != ERROR_ACCESS_DENIED);
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
}, target, vigem };
|
||||
|
||||
_async.detach();
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_ds4_register_notification(
|
||||
PVIGEM_CLIENT vigem,
|
||||
PVIGEM_TARGET target,
|
||||
PFN_VIGEM_DS4_NOTIFICATION notification
|
||||
)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->SerialNo == 0 || notification == nullptr)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (target->Notification == reinterpret_cast<DWORD_PTR>(notification))
|
||||
return VIGEM_ERROR_CALLBACK_ALREADY_REGISTERED;
|
||||
|
||||
target->Notification = reinterpret_cast<DWORD_PTR>(notification);
|
||||
|
||||
std::thread _async{ [](
|
||||
PVIGEM_TARGET _Target,
|
||||
PVIGEM_CLIENT _Client)
|
||||
{
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
DWORD transfered = 0;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
DS4_REQUEST_NOTIFICATION notify;
|
||||
DS4_REQUEST_NOTIFICATION_INIT(¬ify, _Target->SerialNo);
|
||||
|
||||
do
|
||||
{
|
||||
DeviceIoControl(_Client->hBusDevice,
|
||||
IOCTL_DS4_REQUEST_NOTIFICATION,
|
||||
¬ify,
|
||||
notify.Size,
|
||||
¬ify,
|
||||
notify.Size,
|
||||
&transfered,
|
||||
&lOverlapped);
|
||||
|
||||
if (GetOverlappedResult(_Client->hBusDevice, &lOverlapped, &transfered, TRUE) != 0)
|
||||
{
|
||||
if (_Target->Notification == NULL)
|
||||
{
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
reinterpret_cast<PFN_VIGEM_DS4_NOTIFICATION>(_Target->Notification)(
|
||||
_Client,
|
||||
_Target,
|
||||
notify.Report.LargeMotor,
|
||||
notify.Report.SmallMotor,
|
||||
notify.Report.LightbarColor);
|
||||
}
|
||||
else
|
||||
{
|
||||
error = GetLastError();
|
||||
}
|
||||
} while (error != ERROR_OPERATION_ABORTED && error != ERROR_ACCESS_DENIED);
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
}, target, vigem };
|
||||
|
||||
_async.detach();
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
|
||||
void vigem_target_x360_unregister_notification(PVIGEM_TARGET target)
|
||||
{
|
||||
target->Notification = NULL;
|
||||
}
|
||||
|
||||
void vigem_target_ds4_unregister_notification(PVIGEM_TARGET target)
|
||||
{
|
||||
target->Notification = NULL;
|
||||
}
|
||||
|
||||
void vigem_target_set_vid(PVIGEM_TARGET target, USHORT vid)
|
||||
{
|
||||
target->VendorId = vid;
|
||||
}
|
||||
|
||||
void vigem_target_set_pid(PVIGEM_TARGET target, USHORT pid)
|
||||
{
|
||||
target->ProductId = pid;
|
||||
}
|
||||
|
||||
USHORT vigem_target_get_vid(PVIGEM_TARGET target)
|
||||
{
|
||||
return target->VendorId;
|
||||
}
|
||||
|
||||
USHORT vigem_target_get_pid(PVIGEM_TARGET target)
|
||||
{
|
||||
return target->ProductId;
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_x360_update(
|
||||
PVIGEM_CLIENT vigem,
|
||||
PVIGEM_TARGET target,
|
||||
XUSB_REPORT report
|
||||
)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->SerialNo == 0)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
DWORD transfered = 0;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
XUSB_SUBMIT_REPORT xsr;
|
||||
XUSB_SUBMIT_REPORT_INIT(&xsr, target->SerialNo);
|
||||
|
||||
xsr.Report = report;
|
||||
|
||||
DeviceIoControl(
|
||||
vigem->hBusDevice,
|
||||
IOCTL_XUSB_SUBMIT_REPORT,
|
||||
&xsr,
|
||||
xsr.Size,
|
||||
nullptr,
|
||||
0,
|
||||
&transfered,
|
||||
&lOverlapped
|
||||
);
|
||||
|
||||
if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) == 0)
|
||||
{
|
||||
if (GetLastError() == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_ds4_update(
|
||||
PVIGEM_CLIENT vigem,
|
||||
PVIGEM_TARGET target,
|
||||
DS4_REPORT report
|
||||
)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->SerialNo == 0)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
DWORD transfered = 0;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
DS4_SUBMIT_REPORT dsr;
|
||||
DS4_SUBMIT_REPORT_INIT(&dsr, target->SerialNo);
|
||||
|
||||
dsr.Report = report;
|
||||
|
||||
DeviceIoControl(
|
||||
vigem->hBusDevice,
|
||||
IOCTL_DS4_SUBMIT_REPORT,
|
||||
&dsr,
|
||||
dsr.Size,
|
||||
nullptr,
|
||||
0,
|
||||
&transfered,
|
||||
&lOverlapped
|
||||
);
|
||||
|
||||
if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) == 0)
|
||||
{
|
||||
if (GetLastError() == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
|
||||
ULONG vigem_target_get_index(PVIGEM_TARGET target)
|
||||
{
|
||||
return target->SerialNo;
|
||||
}
|
||||
|
||||
VIGEM_TARGET_TYPE vigem_target_get_type(PVIGEM_TARGET target)
|
||||
{
|
||||
return target->Type;
|
||||
}
|
||||
|
||||
BOOL vigem_target_is_attached(PVIGEM_TARGET target)
|
||||
{
|
||||
return (target->State == VIGEM_TARGET_CONNECTED);
|
||||
}
|
||||
|
||||
VIGEM_ERROR vigem_target_x360_get_user_index(
|
||||
PVIGEM_CLIENT vigem,
|
||||
PVIGEM_TARGET target,
|
||||
PULONG index
|
||||
)
|
||||
{
|
||||
if (!vigem)
|
||||
return VIGEM_ERROR_BUS_INVALID_HANDLE;
|
||||
|
||||
if (!target)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
if (vigem->hBusDevice == nullptr)
|
||||
return VIGEM_ERROR_BUS_NOT_FOUND;
|
||||
|
||||
if (target->SerialNo == 0 || target->Type != Xbox360Wired)
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
|
||||
DWORD transfered = 0;
|
||||
OVERLAPPED lOverlapped = { 0 };
|
||||
lOverlapped.hEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
|
||||
|
||||
XUSB_GET_USER_INDEX gui;
|
||||
XUSB_GET_USER_INDEX_INIT(&gui, target->SerialNo);
|
||||
|
||||
DeviceIoControl(
|
||||
vigem->hBusDevice,
|
||||
IOCTL_XUSB_GET_USER_INDEX,
|
||||
&gui,
|
||||
gui.Size,
|
||||
&gui,
|
||||
gui.Size,
|
||||
&transfered,
|
||||
&lOverlapped
|
||||
);
|
||||
|
||||
if (GetOverlappedResult(vigem->hBusDevice, &lOverlapped, &transfered, TRUE) == 0)
|
||||
{
|
||||
const auto error = GetLastError();
|
||||
|
||||
if (error == ERROR_ACCESS_DENIED)
|
||||
{
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
return VIGEM_ERROR_INVALID_TARGET;
|
||||
}
|
||||
|
||||
// TODO: handle userindex out-of-range situation here
|
||||
//if (error == ???)
|
||||
//{
|
||||
// CloseHandle(lOverlapped.hEvent);
|
||||
// return VIGEM_ERROR_XUSB_USERINDEX_OUT_OF_RANGE;
|
||||
//}
|
||||
}
|
||||
|
||||
CloseHandle(lOverlapped.hEvent);
|
||||
|
||||
*index = gui.UserIndex;
|
||||
|
||||
return VIGEM_ERROR_NONE;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "winres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// German (Austria) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_DEA)
|
||||
LANGUAGE LANG_GERMAN, SUBLANG_GERMAN_AUSTRIAN
|
||||
#pragma code_page(1252)
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""winres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,14,3,0
|
||||
PRODUCTVERSION 1,14,3,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x7L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "000904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Benjamin Höglinger-Stelzer"
|
||||
VALUE "FileDescription", "Virtual Gamepad Emulation User-Mode Library"
|
||||
VALUE "FileVersion", "1.14.3.0"
|
||||
VALUE "InternalName", "Virtual Gamepad Emulation User-Mode Library"
|
||||
VALUE "LegalCopyright", "Copyright (C) Benjamin Höglinger-Stelzer 2017"
|
||||
VALUE "OriginalFilename", "ViGEmCli.lib"
|
||||
VALUE "ProductName", "Virtual Gamepad Emulation User-Mode Library"
|
||||
VALUE "ProductVersion", "1.14.3.0"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x9, 1200
|
||||
END
|
||||
END
|
||||
|
||||
#endif // German (Austria) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
@@ -0,0 +1,313 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug_DLL|Win32">
|
||||
<Configuration>Debug_DLL</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug_DLL|x64">
|
||||
<Configuration>Debug_DLL</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug_LIB|Win32">
|
||||
<Configuration>Debug_LIB</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release_DLL|Win32">
|
||||
<Configuration>Release_DLL</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release_DLL|x64">
|
||||
<Configuration>Release_DLL</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release_LIB|Win32">
|
||||
<Configuration>Release_LIB</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug_LIB|x64">
|
||||
<Configuration>Debug_LIB</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release_LIB|x64">
|
||||
<Configuration>Release_LIB</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{7DB06674-1F4F-464B-8E1C-172E9587F9DC}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>ViGEmClient</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(WindowsTargetPlatformVersion)'==''">
|
||||
<!-- Latest Target Version property -->
|
||||
<LatestTargetPlatformVersion>$([Microsoft.Build.Utilities.ToolLocationHelper]::GetLatestSDKTargetPlatformVersion('Windows', '10.0'))</LatestTargetPlatformVersion>
|
||||
<WindowsTargetPlatformVersion Condition="'$(WindowsTargetPlatformVersion)' == ''">$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
|
||||
<TargetPlatformVersion>$(WindowsTargetPlatformVersion)</TargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|x64'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release_LIB|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|Win32'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release_LIB|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|Win32'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)lib\debug\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|Win32'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)bin\debug\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|Win32'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)lib\release\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|Win32'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)bin\release\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|x64'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)lib\debug\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|x64'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)bin\debug\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|x64'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)lib\release\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|x64'">
|
||||
<IncludePath>$(ProjectDir)../include;$(VC_IncludePath);$(WindowsSDK_IncludePath);</IncludePath>
|
||||
<OutDir>$(SolutionDir)bin\release\$(PlatformShortName)\</OutDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
<Lib>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>VIGEM_DYNAMIC;VIGEM_EXPORTS;WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<AdditionalDependencies>setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_LIB|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug_DLL|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>VIGEM_DYNAMIC;VIGEM_EXPORTS;_DEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<AdditionalDependencies>setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
<Lib>
|
||||
<TargetMachine>MachineX86</TargetMachine>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>VIGEM_DYNAMIC;VIGEM_EXPORTS;WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_LIB|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release_DLL|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>
|
||||
</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>VIGEM_DYNAMIC;VIGEM_EXPORTS;NDEBUG;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>setupapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmBusShared.h" />
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmClient.h" />
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmCommon.h" />
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmUtil.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ViGEmClient.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="ViGEmClient.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmBusShared.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmClient.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmCommon.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="$(SolutionDir)\Include\ViGEmUtil.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ViGEmClient.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="ViGEmClient.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,14 @@
|
||||
//{{NO_DEPENDENCIES}}
|
||||
// Microsoft Visual C++ generated include file.
|
||||
// Used by ViGEmClient.rc
|
||||
|
||||
// Next default values for new objects
|
||||
//
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#ifndef APSTUDIO_READONLY_SYMBOLS
|
||||
#define _APS_NEXT_RESOURCE_VALUE 101
|
||||
#define _APS_NEXT_COMMAND_VALUE 40001
|
||||
#define _APS_NEXT_CONTROL_VALUE 1001
|
||||
#define _APS_NEXT_SYMED_VALUE 101
|
||||
#endif
|
||||
#endif
|
||||
Reference in new issue
Block a user