Initial release

This commit is contained in:
Dylan Deverill committed 2026-05-10 19:28:03 -07:00
commit 589ed11e4a
45 files changed
+4557

No files matched your search

+25
View File
@@ -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
+62
View File
@@ -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()
+21
View File
@@ -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.
+67
View File
@@ -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

+4
View File
@@ -0,0 +1,4 @@
#include "resource.h"
IDI_ICON_OFF ICON "SteamControllerOFF.ico"
IDI_ICON_ON ICON "SteamControllerON.ico"
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#define IDI_ICON_OFF 101
#define IDI_ICON_ON 102
+113
View File
@@ -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);
}
}
+56
View File
@@ -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};
};
+93
View File
@@ -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;
}
}
}
+27
View File
@@ -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;
};
+257
View File
@@ -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);
}
+41
View File
@@ -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;
};
+113
View File
@@ -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(&ltRaw, 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);
}
+22
View File
@@ -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;
};
+19
View File
@@ -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;
}
+210
View File
@@ -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);
}
+47
View File
@@ -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
View File
@@ -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;
}
+102
View File
@@ -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);
}
}
+116
View File
@@ -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));
}
}
+138
View File
@@ -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:
@@ -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
View File
@@ -0,0 +1 @@
ViGEmClient.sln
+21
View File
@@ -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
View File
@@ -0,0 +1,22 @@
# ViGEm Client Native SDK
Developer SDK for communication with `ViGEmBus`.
[![Build status](https://ci.appveyor.com/api/projects/status/k806d3m2egjr0j56?svg=true)](https://ci.appveyor.com/project/nefarius/vigemclient) [![Discord](https://img.shields.io/discord/346756263763378176.svg)](https://discord.gg/QTJpBX5) [![Website](https://img.shields.io/website-up-down-green-red/https/vigem.org.svg?label=ViGEm.org)](https://vigem.org/) [![PayPal Donate](https://img.shields.io/badge/paypal-donate-blue.svg)](<https://paypal.me/NefariusMaximus>) [![Support on Patreon](https://img.shields.io/badge/patreon-donate-orange.svg)](<https://www.patreon.com/nefarius>) [![GitHub followers](https://img.shields.io/github/followers/nefarius.svg?style=social&label=Follow)](https://github.com/nefarius) [![Twitter Follow](https://img.shields.io/twitter/follow/nefariusmaximus.svg?style=social&label=Follow)](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
```
+53
View File
@@ -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
+9
View File
@@ -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
View File
@@ -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
View File
@@ -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[@]}
+495
View File
@@ -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__
+206
View File
@@ -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);
}
+50
View File
@@ -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
+8
View File
@@ -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`.
+794
View File
@@ -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(&notify, _Target->SerialNo);
do
{
DeviceIoControl(_Client->hBusDevice,
IOCTL_XUSB_REQUEST_NOTIFICATION,
&notify,
notify.Size,
&notify,
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(&notify, _Target->SerialNo);
do
{
DeviceIoControl(_Client->hBusDevice,
IOCTL_DS4_REQUEST_NOTIFICATION,
&notify,
notify.Size,
&notify,
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;
}
+100
View File
@@ -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>
+14
View File
@@ -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