2
0
mirror of https://github.com/Alia5/GlosSI.git synced 2024-11-01 09:20:17 +00:00
GlosSI/GlosSITarget/HidHide.cpp
Peter Repukat 4ae430b813 Hide gaming devices from all apps but Steam
This means support for more than just the SteamController.
2021-10-21 19:17:51 +02:00

489 lines
19 KiB
C++

/*
Copyright 2021 Peter Repukat - FlatspotSoftware
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// parts of code adapted from https://github.com/ViGEm/HidHide/blob/HEAD/HidHideCLI/src/HID.cpp
// // (c) Eric Korff de Gidts
// SPDX-License-Identifier: MIT
#include "HidHide.h"
#include <iostream>
#include <numeric>
#include <spdlog/spdlog.h>
#include <vector>
// Device configuration related
#include <cfgmgr32.h>
#include <initguid.h>
//
#include <devguid.h>
#include <devpkey.h>
#include <regex>
// {D61CA365-5AF4-4486-998B-9DB4734C6CA3}add the XUSB class GUID as it is missing in the public interfaces
DEFINE_GUID(GUID_DEVCLASS_XUSBCLASS, 0xD61CA365, 0x5AF4, 0x4486, 0x99, 0x8B, 0x9D, 0xB4, 0x73, 0x4C, 0x6C, 0xA3);
// {EC87F1E3-C13B-4100-B5F7-8B84D54260CB} add the XUSB interface class GUID as it is missing in the public interfaces
DEFINE_GUID(GUID_DEVINTERFACE_XUSB, 0xEC87F1E3, 0xC13B, 0x4100, 0xB5, 0xF7, 0x8B, 0x84, 0xD5, 0x42, 0x60, 0xCB);
// {00000000-0000-0000-FFFF-FFFFFFFFFFFF} the system container id
DEFINE_GUID(GUID_CONTAINER_ID_SYSTEM, 0x00000000, 0x0000, 0x0000, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
HidHide::HidHide() = default;
void HidHide::openCtrlDevice()
{
hidhide_handle = CreateFile(
L"\\\\.\\HidHide",
GENERIC_READ,
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
void HidHide::closeCtrlDevice()
{
if (hidhide_handle == nullptr) {
return;
}
CloseHandle(hidhide_handle);
hidhide_handle = nullptr;
}
void HidHide::hideDevices(const std::filesystem::path& steam_path)
{
openCtrlDevice();
auto active = getActive();
if (active) {
// disable hidhide so we can see devices ourselves
setActive(false);
}
auto whitelist = getAppWhiteList();
// has anyone more than 4 keys to open overlay?!
std::wsmatch m;
const auto steam_path_string = steam_path.wstring();
if (!std::regex_search(steam_path_string, m, std::wregex(L"(.:)(\\/|\\\\)")) || m.size() < 3) {
spdlog::warn("Couldn't detect steam drive letter; Device hiding may not function");
return;
}
const auto dos_device = DosDeviceForVolume(m[1]);
if (dos_device.empty()) {
spdlog::warn("Couldn't detect steam drive letter DOS Path; Device hiding may not function");
return;
}
for (const auto& exe : whitelist_executeables_) {
auto path = std::regex_replace(steam_path_string, std::wregex(L"(.:)(\\/|\\\\)"), dos_device + L"\\");
path = std::regex_replace(path, std::wregex(L"\\/"), L"\\") + L"\\" + std::wstring{exe};
if (std::ranges::none_of(whitelist, [&path](auto ep) { // make copy!
auto p = path; // non-const(!) copy of path
std::ranges::transform(path, p.begin(), tolower);
std::ranges::transform(ep, ep.begin(), tolower);
return p == ep;
})) {
whitelist.push_back(path);
}
}
setAppWhiteList(whitelist);
const auto device_list = GetHidDeviceList();
auto blacklist = getBlackListDevices();
for (const auto& dev : device_list) {
if (std::ranges::none_of(blacklist, [&dev](const auto& blackdev) {
return blackdev == dev.device_instance_path || blackdev == dev.base_container_device_instance_path;
})) {
if (!dev.device_instance_path.empty()) {
blacklist.push_back(dev.device_instance_path);
}
if (!dev.device_instance_path.empty()) {
blacklist.push_back(dev.base_container_device_instance_path);
}
}
}
setBlacklistDevices(blacklist);
setActive(true);
closeCtrlDevice();
spdlog::info("Hid Gaming Devices"); // TODO: add list of blacklisted devices
}
void HidHide::disableHidHide()
{
openCtrlDevice();
setActive(false);
closeCtrlDevice();
spdlog::info("Un-hid Gaming Devices"); // TODO: add list of blacklisted devices
}
std::wstring HidHide::DosDeviceForVolume(const std::wstring& volume)
{
std::vector<WCHAR> buffer(UNICODE_STRING_MAX_CHARS);
QueryDosDeviceW(volume.c_str(), buffer.data(), static_cast<DWORD>(buffer.size()));
return {buffer.data()};
}
std::vector<std::wstring> HidHide::getAppWhiteList() const
{
DWORD bytes_needed = getRequiredOutputBufferSize(IOCTL_TYPE::GET_WHITELIST);
if (bytes_needed == 0) {
return std::vector<std::wstring>{};
}
std::vector<WCHAR> buffer(bytes_needed);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_WHITELIST), nullptr, 0, buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), &bytes_needed, nullptr)) {
spdlog::error("Couldn't retrieve HidHide Whitelist");
return std::vector<std::wstring>{};
}
return BufferToStringVec(buffer);
}
std::vector<std::wstring> HidHide::getBlackListDevices() const
{
DWORD bytes_needed = getRequiredOutputBufferSize(IOCTL_TYPE::GET_BLACKLIST);
if (bytes_needed == 0) {
return std::vector<std::wstring>{};
}
std::vector<WCHAR> buffer(bytes_needed);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_BLACKLIST), nullptr, 0, buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), &bytes_needed, nullptr)) {
spdlog::error("Couldn't retrieve HidHide Blacklist");
return std::vector<std::wstring>{};
}
return BufferToStringVec(buffer);
}
bool HidHide::getActive() const
{
DWORD bytes_needed;
BOOLEAN res;
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::GET_ACTIVE), nullptr, 0, &res, sizeof(BOOLEAN), &bytes_needed, nullptr)) {
spdlog::error("Couldn't retrieve HidHide State");
return false;
}
return res;
}
void HidHide::setAppWhiteList(const std::vector<std::wstring>& whitelist) const
{
DWORD bytes_needed;
auto buffer = StringListToMultiString(whitelist);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_WHITELIST), buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't set HidHide WhiteList");
}
}
void HidHide::setBlacklistDevices(const std::vector<std::wstring>& blacklist) const
{
DWORD bytes_needed;
auto buffer = StringListToMultiString(blacklist);
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_BLACKLIST), buffer.data(), static_cast<DWORD>(buffer.size() * sizeof(WCHAR)), nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't set HidHide BlackList");
}
}
void HidHide::setActive(bool active) const
{
DWORD bytes_needed;
if (!DeviceIoControl(
hidhide_handle, static_cast<DWORD>(IOCTL_TYPE::SET_ACTIVE), &active, sizeof(BOOLEAN), nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't set HidHide State");
}
}
DWORD HidHide::getRequiredOutputBufferSize(IOCTL_TYPE type) const
{
DWORD bytes_needed;
if (!DeviceIoControl(hidhide_handle, static_cast<DWORD>(type), nullptr, 0, nullptr, 0, &bytes_needed, nullptr)) {
spdlog::error("Couldn't determine required HidHide output buffer size; type: {}", type);
return 0;
}
return bytes_needed;
}
std::vector<std::wstring> HidHide::BufferToStringVec(const auto& buffer)
{
std::vector<std::wstring> res;
if (buffer[0] != L'\0') {
res.emplace_back();
for (const auto& ch : buffer) {
if (ch == L'\0') {
if ((res.end() - 1)->length() == 0) {
res.erase(res.end() - 1);
break;
}
res.emplace_back();
continue;
}
(res.end() - 1)->push_back(ch);
}
}
return res;
}
std::vector<WCHAR> HidHide::StringListToMultiString(const std::vector<std::wstring>& stringlist)
{
auto res = std::accumulate(stringlist.begin(), stringlist.end(), std::vector<WCHAR>{}, [](auto acc, const auto& curr) {
acc.insert(acc.end(), curr.begin(), curr.end());
acc.push_back(L'\0');
return acc;
});
res.push_back(L'\0');
return res;
}
std::vector<HidHide::SmallHidInfo> HidHide::GetHidDeviceList()
{
std::wstring hid_class_guid_string;
hid_class_guid_string.resize(39);
if (!StringFromGUID2(GUID_DEVCLASS_HIDCLASS, hid_class_guid_string.data(), static_cast<int>(hid_class_guid_string.size()))) {
spdlog::error("couldn't convert GUID to string");
}
ULONG needed{};
if (const auto result = CM_Get_Device_ID_List_SizeW(&needed, hid_class_guid_string.c_str(), CM_GETIDLIST_FILTER_CLASS);
(CR_SUCCESS != result)) {
spdlog::error("Couldn't get device id list size; code: {}", result);
}
std::vector<WCHAR> buffer(needed);
if (const auto result = CM_Get_Device_ID_ListW(hid_class_guid_string.c_str(), buffer.data(), needed, CM_GETIDLIST_FILTER_CLASS);
(CR_SUCCESS != result)) {
spdlog::error("Couldn't get device id list; code: {}", result);
}
auto device_instance_paths = BufferToStringVec(buffer);
device_instance_paths.erase(
std::ranges::remove_if(
device_instance_paths,
[](const auto& dev) { return !DevicePresent(dev); })
.begin(),
device_instance_paths.end());
GUID hid_device_interface_guid{};
HidD_GetHidGuid(&hid_device_interface_guid);
std::vector<SmallHidInfo> res;
for (auto& instance_path : device_instance_paths) {
auto symlink = SymbolicLink(hid_device_interface_guid, instance_path);
if (!symlink.empty()) {
res.push_back(GetDeviceInfo(instance_path, symlink));
}
}
res.erase(
std::ranges::remove_if(
res,
[](const auto& dev) { return !dev.gaming_device; })
.begin(),
res.end());
return res;
}
HidHide::SmallHidInfo HidHide::GetDeviceInfo(const DeviceInstancePath& instance_path, const std::filesystem::path& symlink)
{
SmallHidInfo res;
res.device_instance_path = instance_path;
// Open a handle to communicate with the HID device
const CloseHandlePtr device_object(
CreateFileW(
symlink.c_str(),
GENERIC_READ,
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
nullptr,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
nullptr),
&CloseHandle);
if (INVALID_HANDLE_VALUE == device_object.get()) {
const auto err = GetLastError();
switch (err) {
case ERROR_ACCESS_DENIED:
// The device is opened exclusively and in use hence we can't interact with it
__fallthrough;
case ERROR_SHARING_VIOLATION:
// The device is (most-likely) cloaked by Hid Hide itself while its client application isn't on the white-list
__fallthrough;
case ERROR_FILE_NOT_FOUND:
// The device is currently not present hence we can't query its details
return res;
default:
spdlog::error(L"Couldn't open device {}; code: {}", instance_path, err);
return res;
}
}
PHIDP_PREPARSED_DATA pre_parsed_data;
if (!HidD_GetPreparsedData(device_object.get(), &pre_parsed_data)) {
spdlog::error(L"Couldn't get PreParsed data; Device: {}", instance_path);
return {};
}
const HidD_FreePreparsedDataPtr free_preparsed_data_ptr(pre_parsed_data, &HidD_FreePreparsedData);
HIDP_CAPS capabilities;
if (HIDP_STATUS_SUCCESS != HidP_GetCaps(pre_parsed_data, &capabilities)) {
spdlog::error(L"Could get Hid capabilities; Device: {}", instance_path);
return {};
}
HIDD_ATTRIBUTES attributes;
if (!HidD_GetAttributes(device_object.get(), &attributes)) {
spdlog::error(L"Could get Hid attributes; Device: {}", instance_path);
return {};
}
std::wstring buffer;
buffer.resize(127 * sizeof WCHAR);
res.name = (HidD_GetProductString(device_object.get(), buffer.data(), static_cast<ULONG>(sizeof(WCHAR) * buffer.size()))
? buffer
: L"");
res.base_container_device_instance_path = BaseContainerDeviceInstancePath(instance_path);
res.gaming_device = IsGamingDevice(attributes, capabilities);
return res;
}
bool HidHide::DevicePresent(const DeviceInstancePath& dev)
{
DEVINST dev_inst{};
if (
const auto result = CM_Locate_DevNodeW(&dev_inst, const_cast<DEVINSTID_W>(dev.c_str()), CM_LOCATE_DEVNODE_NORMAL);
(CR_NO_SUCH_DEVNODE == result) || (CR_SUCCESS == result)) {
return (CR_SUCCESS == result);
}
spdlog::error(L"Couldn't determine if device \"{}\" is present", dev);
return false;
}
std::filesystem::path HidHide::SymbolicLink(GUID const& interface_guid, DeviceInstancePath const& instance_path)
{
// Ask the device for the device interface
// Note that this call will succeed, whether or not the interface is present, but the iterator will have no entries, when the device interface isn't supported
const SetupDiDestroyDeviceInfoListPtr handle(SetupDiGetClassDevsW(&interface_guid, instance_path.c_str(), nullptr, DIGCF_DEVICEINTERFACE), &SetupDiDestroyDeviceInfoList);
if (INVALID_HANDLE_VALUE == handle.get()) {
spdlog::error(L"Device Handle invalid; device: {}", instance_path);
return {};
}
// Is the interface supported ?
SP_DEVICE_INTERFACE_DATA device_interface_data;
device_interface_data.cbSize = sizeof(device_interface_data);
if (!SetupDiEnumDeviceInterfaces(handle.get(), nullptr, &interface_guid, 0, &device_interface_data)) {
if (ERROR_NO_MORE_ITEMS != GetLastError()) {
spdlog::error(L"Couldn't get Device interfaces; device: {}", instance_path);
return {};
}
return {};
}
// Determine the buffer length needed
DWORD needed{};
if (!SetupDiGetDeviceInterfaceDetailW(handle.get(), &device_interface_data, nullptr, 0, &needed, nullptr) && ERROR_INSUFFICIENT_BUFFER != GetLastError()) {
spdlog::error(L"Couldn't get Device interface details; device: {}", instance_path);
return {};
}
std::vector<BYTE> buffer(needed);
// Acquire the detailed data containing the symbolic link (aka. device path)
auto& [cbSize, DevicePath]{*reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W>(buffer.data())};
cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);
if (!SetupDiGetDeviceInterfaceDetailW(handle.get(), &device_interface_data, reinterpret_cast<PSP_DEVICE_INTERFACE_DETAIL_DATA_W>(buffer.data()), static_cast<DWORD>(buffer.size()), nullptr, nullptr)) {
spdlog::error(L"Couldn't get Device interface details; device: {}", instance_path);
return {};
}
return {std::wstring(DevicePath)};
}
HidHide::DeviceInstancePath HidHide::BaseContainerDeviceInstancePath(DeviceInstancePath const& device_instance_path)
{
const GUID base_container_id(BaseContainerId(device_instance_path));
if ((GUID_NULL == base_container_id) || (GUID_CONTAINER_ID_SYSTEM == base_container_id))
return (std::wstring{});
for (auto it{device_instance_path};;) {
if (const auto device_instance_path_parent = DeviceInstancePathParent(it); (base_container_id == BaseContainerId(device_instance_path_parent)))
it = device_instance_path_parent;
else
return (it);
}
}
GUID HidHide::BaseContainerId(DeviceInstancePath const& device_instance_path)
{
// Bail out when the device instance path is empty
if (device_instance_path.empty())
return (GUID_NULL);
DEVINST devInst{};
DEVPROPTYPE devPropType{};
GUID buffer{};
ULONG needed{sizeof(buffer)};
if (const auto result = CM_Locate_DevNodeW(&devInst, const_cast<DEVINSTID_W>(device_instance_path.c_str()), CM_LOCATE_DEVNODE_PHANTOM); (CR_SUCCESS != result)) {
spdlog::error(L"Couldn't locate device DevNode; Device {}; Code: {}", device_instance_path, result);
return {};
}
if (const auto result = CM_Get_DevNode_PropertyW(devInst, &DEVPKEY_Device_ContainerId, &devPropType, reinterpret_cast<PBYTE>(&buffer), &needed, 0); (CR_SUCCESS != result)) {
// Bail out when the container id property isn't present
if (CR_NO_SUCH_VALUE == result) {
return (GUID_NULL);
}
spdlog::error(L"Couldn't locate device DevNode Property; Device {}; Code: {}", device_instance_path, result);
return {};
}
if (DEVPROP_TYPE_GUID != devPropType) {
spdlog::error(L"Device Prop is not GUID; Device {}", device_instance_path);
return {};
}
return (buffer);
}
HidHide::DeviceInstancePath HidHide::DeviceInstancePathParent(DeviceInstancePath const& device_instance_path)
{
DEVINST dev_inst{};
DEVPROPTYPE dev_prop_type{};
DEVINST dev_inst_parent{};
std::wstring res;
res.resize(UNICODE_STRING_MAX_CHARS);
ULONG needed{static_cast<ULONG>(res.size())};
if (const auto result = CM_Locate_DevNodeW(&dev_inst, const_cast<DEVINSTID_W>(device_instance_path.c_str()), CM_LOCATE_DEVNODE_PHANTOM); (CR_SUCCESS != result)) {
spdlog::error(L"Couldn't locate device DevNode; Device {}; Code: {}", device_instance_path, result);
return {};
}
if (const auto result = CM_Get_Parent(&dev_inst_parent, dev_inst, 0); (CR_SUCCESS != result)) {
spdlog::error(L"Couldn't get device Parent; Device {}; Code: {}", device_instance_path, result);
return {};
}
if (const auto result = CM_Get_DevNode_PropertyW(dev_inst_parent, &DEVPKEY_Device_InstanceId, &dev_prop_type, reinterpret_cast<PBYTE>(res.data()), &needed, 0); (CR_SUCCESS != result)) {
spdlog::error(L"Couldn't locate device DevNode Property; Device {}; Code: {}", device_instance_path, result);
return {};
}
if (DEVPROP_TYPE_STRING != dev_prop_type) {
spdlog::error(L"Device Prop is not STRING; Device {}", device_instance_path);
return {};
}
return res;
}
bool HidHide::IsGamingDevice(const HIDD_ATTRIBUTES& attributes, const HIDP_CAPS& capabilities)
{
return (
// 0x28DE 0x1142 = Valve Corporation Steam Controller
// keep them for now
/* ((attributes.VendorID == 0x28DE) && (attributes.ProductID == 0x1142)) || */
(0x05 == capabilities.UsagePage) || (0x01 == capabilities.UsagePage) && ((0x04 == capabilities.Usage) || (0x05 == capabilities.Usage)));
}