add core api

This commit is contained in:
Nguyen Duy
2023-12-30 16:33:02 +07:00
parent 280bf118b9
commit 9f4d7c4388
3 changed files with 441 additions and 214 deletions
-214
View File
@@ -1,214 +0,0 @@
#include <chrono>
#include <thread>
#include <tchar.h>
#include <strsafe.h>
#include <Windows.h>
#include <SetupAPI.h>
#pragma comment(lib, "Setupapi.lib")
BOOLEAN GetDevicePath2(
_In_ LPCGUID InterfaceGuid,
_Out_writes_(BufLen) PTCHAR DevicePath,
_In_ size_t BufLen
)
{
HANDLE hDevice = INVALID_HANDLE_VALUE;
PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
ULONG predictedLength = 0;
ULONG requiredLength = 0;
HDEVINFO hardwareDeviceInfo;
SP_DEVICE_INTERFACE_DATA deviceInterfaceData;
BOOLEAN status = FALSE;
HRESULT hr;
hardwareDeviceInfo = SetupDiGetClassDevs(
InterfaceGuid,
NULL, // Define no enumerator (global)
NULL, // Define no
(DIGCF_PRESENT | // Only Devices present
DIGCF_DEVICEINTERFACE)); // Function class devices.
if (INVALID_HANDLE_VALUE == hardwareDeviceInfo)
{
printf("Idd device: SetupDiGetClassDevs failed, last error 0x%x\n", GetLastError());
return FALSE;
}
deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (!SetupDiEnumDeviceInterfaces(hardwareDeviceInfo,
0, // No care about specific PDOs
InterfaceGuid,
0, //
&deviceInterfaceData))
{
printf("Idd device: SetupDiEnumDeviceInterfaces failed, last error 0x%x\n", GetLastError());
goto Clean0;
}
//
// Allocate a function class device data structure to receive the
// information about this particular device.
//
SetupDiGetDeviceInterfaceDetail(
hardwareDeviceInfo,
&deviceInterfaceData,
NULL, // probing so no output buffer yet
0, // probing so output buffer length of zero
&requiredLength,
NULL);//not interested in the specific dev-node
if (ERROR_INSUFFICIENT_BUFFER != GetLastError())
{
printf("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError());
goto Clean0;
}
predictedLength = requiredLength;
deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)HeapAlloc(
GetProcessHeap(),
HEAP_ZERO_MEMORY,
predictedLength
);
if (deviceInterfaceDetailData)
{
deviceInterfaceDetailData->cbSize =
sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
}
else
{
printf("Idd device: HeapAlloc failed, last error 0x%x\n", GetLastError());
goto Clean0;
}
if (!SetupDiGetDeviceInterfaceDetail(
hardwareDeviceInfo,
&deviceInterfaceData,
deviceInterfaceDetailData,
predictedLength,
&requiredLength,
NULL))
{
printf("Idd device: SetupDiGetDeviceInterfaceDetail failed, last error 0x%x\n", GetLastError());
goto Clean1;
}
hr = StringCchCopy(DevicePath, BufLen, deviceInterfaceDetailData->DevicePath);
if (FAILED(hr))
{
printf("Error: StringCchCopy failed with HRESULT 0x%x", hr);
status = FALSE;
goto Clean1;
}
else
{
status = TRUE;
}
Clean1:
(VOID)HeapFree(GetProcessHeap(), 0, deviceInterfaceDetailData);
Clean0:
(VOID)SetupDiDestroyDeviceInfoList(hardwareDeviceInfo);
return status;
}
HANDLE DeviceOpenHandle(const GUID &devGuid)
{
// const int maxDevPathLen = 256;
TCHAR devicePath[256] = { 0 };
HANDLE hDevice = INVALID_HANDLE_VALUE;
do
{
if (FALSE == GetDevicePath2(
&devGuid,
devicePath,
sizeof(devicePath) / sizeof(devicePath[0])))
{
break;
}
if (_tcslen(devicePath) == 0)
{
printf("GetDevicePath got empty device path\n");
break;
}
_tprintf(_T("Idd device: try open %s\n"), devicePath);
hDevice = CreateFile(
devicePath,
GENERIC_READ | GENERIC_WRITE,
// FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
NULL, // no SECURITY_ATTRIBUTES structure
OPEN_EXISTING, // No special create flags
0, // No special attributes
NULL
);
if (hDevice == INVALID_HANDLE_VALUE || hDevice == NULL)
{
DWORD error = GetLastError();
printf("CreateFile failed 0x%lx\n", error);
}
} while (0);
return hDevice;
}
enum VddCtlCode
{
IOCTL_VDD_CONNECT = 0x22A008,
IOCTL_VDD_ADD = 0x22E004,
IOCTL_VDD_UPDATE = 0x22A00C,
};
void VddIoCtl(HANDLE vdd, VddCtlCode code)
{
BYTE InBuffer[32]{};
int OutBuffer = 0;
OVERLAPPED Overlapped{};
DWORD NumberOfBytesTransferred;
Overlapped.hEvent = CreateEventW(NULL, NULL, NULL, NULL);
DeviceIoControl(vdd, code, InBuffer, _countof(InBuffer), &OutBuffer, sizeof(OutBuffer), NULL, &Overlapped);
GetOverlappedResult(vdd, &Overlapped, &NumberOfBytesTransferred, TRUE);
if (Overlapped.hEvent && Overlapped.hEvent != INVALID_HANDLE_VALUE)
CloseHandle(Overlapped.hEvent);
}
int main()
{
const GUID PARSEC_VDD_DEVINTERFACE = \
{ 0x00b41627, 0x04c4, 0x429e, { 0xa2, 0x6e, 0x02, 0x65, 0xcf, 0x50, 0xc8, 0xfa } };
// try to get device handle with GUID
HANDLE vdd = DeviceOpenHandle(PARSEC_VDD_DEVINTERFACE);
if (!vdd || vdd == INVALID_HANDLE_VALUE)
{
printf("failed to get ParsecVDD device handle.\n");
return 1;
}
// connect & plug in
VddIoCtl(vdd, IOCTL_VDD_CONNECT);
VddIoCtl(vdd, IOCTL_VDD_UPDATE);
VddIoCtl(vdd, IOCTL_VDD_ADD);
VddIoCtl(vdd, IOCTL_VDD_UPDATE);
// work for 5s
const int kDuration = 5000;
auto startTime = std::chrono::high_resolution_clock::now();
while (std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - startTime).count() < kDuration)
{
// update each 100ms
std::this_thread::sleep_for(std::chrono::milliseconds(100));
VddIoCtl(vdd, IOCTL_VDD_UPDATE);
}
// disconnect
VddIoCtl(vdd, IOCTL_VDD_CONNECT);
CloseHandle(vdd);
return 0;
}
+91
View File
@@ -0,0 +1,91 @@
#include <stdio.h>
#include <conio.h>
#include <thread>
#include <chrono>
#include <vector>
#include "parsec-vdd.h"
using namespace std::chrono_literals;
using namespace parsec_vdd;
int main()
{
// Check driver status.
DeviceStatus status = QueryDeviceStatus(&VDD_CLASS_GUID, VDD_HARDWARE_ID);
if (status != DEVICE_OK)
{
printf("Parsec VDD device is not OK, got status %d.\n", status);
return 1;
}
// Obtain device handle.
HANDLE vdd = OpenDeviceHandle(&VDD_ADAPTER_GUID);
if (vdd == NULL || vdd == INVALID_HANDLE_VALUE) {
printf("Failed to obtain the device handle.\n");
return 1;
}
bool running = true;
std::vector<int> displays;
// Side thread for updating vdd.
std::thread updater([&running, vdd] {
while (running) {
VddUpdate(vdd);
std::this_thread::sleep_for(100ms);
}
});
updater.detach();
// Print out guide.
printf("Press A to add a virtual display.\n");
printf("Press R to remove the last added.\n");
printf("Press Q to quit (then unplug all).\n\n");
while (running) {
switch (_getch()) {
// quit
case 'q':
running = false;
break;
// add display
case 'a':
if (displays.size() < VDD_MAX_DISPLAYS) {
int index = VddAddDisplay(vdd);
displays.push_back(index);
printf("Added a new virtual display, index: %d.\n", index);
}
else {
printf("Limit exceeded (%d), could not add more virtual displays.\n", VDD_MAX_DISPLAYS);
}
break;
// remove display
case 'r':
if (displays.size() > 0) {
int index = displays.back();
VddRemoveDisplay(vdd, index);
displays.pop_back();
printf("Removed the last virtual display, index: %d.\n", index);
}
else {
printf("No added virtual displays.\n");
}
break;
}
}
// Remove all before exiting.
for (int index : displays) {
VddRemoveDisplay(vdd, index);
}
if (updater.joinable()) {
updater.join();
}
// Close the device handle.
CloseDeviceHandle(vdd);
return 0;
}
+350
View File
@@ -0,0 +1,350 @@
/*
* Copyright (c) 2023, Nguyen Duy <wuuyi123@gmail.com> All rights reserved.
* GitHub repo: https://github.com/nomi-san/parsec-vdd/
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
#ifndef __PARSEC_VDD_H
#define __PARSEC_VDD_H
#include <windows.h>
#include <setupapi.h>
#include <cfgmgr32.h>
#ifdef _MSC_VER
#pragma comment(lib, "cfgmgr32.lib")
#pragma comment(lib, "setupapi.lib")
#endif
#ifdef __cplusplus
namespace parsec_vdd
{
#endif
// Device helper.
//////////////////////////////////////////////////
typedef enum {
DEVICE_OK = 0, // Ready to use
DEVICE_INACCESSIBLE, // Inaccessible
DEVICE_UNKNOW, // Unknow status
DEVICE_UNKNOW_PROBLEM, // Unknow problem
DEVICE_DISABLED, // Device is disabled
DEVICE_DRIVER_ERROR, // Device encountered error
DEVICE_RESTART_REQUIRED, // Must restart PC to use (could ignore but would have issue)
DEVICE_DISABLED_SERVICE, // Service is disabled
DEVICE_NOT_INSTALLED // Driver is not installed
} DeviceStatus;
/**
* Query the driver status.
*
* @param classGuid The GUID of the class.
* @param deviceId The device/hardware ID of the driver.
* @return DeviceStatus
*/
static DeviceStatus QueryDeviceStatus(const GUID *classGuid, const char *deviceId)
{
DeviceStatus status = DEVICE_INACCESSIBLE;
SP_DEVINFO_DATA devInfoData;
ZeroMemory(&devInfoData, sizeof(SP_DEVINFO_DATA));
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
HDEVINFO devInfo = SetupDiGetClassDevsA(classGuid, NULL, NULL, DIGCF_PRESENT);
if (devInfo != INVALID_HANDLE_VALUE)
{
BOOL foundProp = FALSE;
UINT deviceIndex = 0;
do
{
if (!SetupDiEnumDeviceInfo(devInfo, deviceIndex, &devInfoData))
break;
DWORD requiredSize = 0;
SetupDiGetDeviceRegistryPropertyA(devInfo, &devInfoData,
SPDRP_HARDWAREID, NULL, NULL, 0, &requiredSize);
if (requiredSize > 0)
{
DWORD regDataType = 0;
LPBYTE propBuffer = (LPBYTE)calloc(1, requiredSize);
if (SetupDiGetDeviceRegistryPropertyA(
devInfo,
&devInfoData,
SPDRP_HARDWAREID,
&regDataType,
propBuffer,
requiredSize,
&requiredSize))
{
if (regDataType == REG_SZ || regDataType == REG_MULTI_SZ)
{
for (LPCSTR cp = (LPCSTR)propBuffer; ; cp += lstrlenA(cp) + 1)
{
if (!cp || *cp == 0 || cp >= (LPCSTR)(propBuffer + requiredSize))
{
status = DEVICE_NOT_INSTALLED;
goto except;
}
if (lstrcmpA(deviceId, cp) == 0)
break;
}
foundProp = TRUE;
ULONG devStatus, devProblemNum;
if (CM_Get_DevNode_Status(&devStatus, &devProblemNum, devInfoData.DevInst, 0) != CR_SUCCESS)
{
status = DEVICE_NOT_INSTALLED;
goto except;
}
if ((devStatus & (DN_DRIVER_LOADED | DN_STARTED)) != 0)
{
status = DEVICE_OK;
}
else if ((devStatus & DN_HAS_PROBLEM) != 0)
{
switch (devProblemNum)
{
case CM_PROB_NEED_RESTART:
status = DEVICE_RESTART_REQUIRED;
break;
case CM_PROB_DISABLED:
case CM_PROB_HARDWARE_DISABLED:
status = DEVICE_DISABLED;
break;
case CM_PROB_DISABLED_SERVICE:
status = DEVICE_DISABLED_SERVICE;
break;
default:
if (devProblemNum == CM_PROB_FAILED_POST_START)
status = DEVICE_DRIVER_ERROR;
else
status = DEVICE_UNKNOW_PROBLEM;
break;
}
}
else
{
status = DEVICE_UNKNOW;
}
}
}
except:
free(propBuffer);
}
++deviceIndex;
} while (!foundProp);
if (!foundProp && GetLastError() != 0)
status = DEVICE_NOT_INSTALLED;
SetupDiDestroyDeviceInfoList(devInfo);
}
return status;
}
/**
* Obtain the device handle.
* Returns NULL or INVALID_HANDLE_VALUE if fails, otherwise a valid handle.
* Should call CloseDeviceHandle to close this handle after use.
*
* @param interfaceGuid The adapter/interface GUID of the target device.
* @return HANDLE
*/
static HANDLE OpenDeviceHandle(const GUID *interfaceGuid)
{
HANDLE handle = INVALID_HANDLE_VALUE;
HDEVINFO devInfo = SetupDiGetClassDevsA(interfaceGuid,
NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (devInfo != INVALID_HANDLE_VALUE)
{
SP_DEVICE_INTERFACE_DATA devInterface;
ZeroMemory(&devInterface, sizeof(SP_DEVICE_INTERFACE_DATA));
devInterface.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
for (DWORD i = 0; SetupDiEnumDeviceInterfaces(devInfo, NULL, interfaceGuid, i, &devInterface); ++i)
{
DWORD detailSize = 0;
SetupDiGetDeviceInterfaceDetailA(devInfo, &devInterface, NULL, 0, &detailSize, NULL);
SP_DEVICE_INTERFACE_DETAIL_DATA_A *detail = (SP_DEVICE_INTERFACE_DETAIL_DATA_A *)calloc(1, detailSize);
detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_A);
if (SetupDiGetDeviceInterfaceDetailA(devInfo, &devInterface, detail, detailSize, &detailSize, NULL))
{
handle = CreateFileA(detail->DevicePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_NO_BUFFERING | FILE_FLAG_OVERLAPPED | FILE_FLAG_WRITE_THROUGH,
NULL);
if (handle != NULL && handle != INVALID_HANDLE_VALUE)
break;
}
free(detail);
}
SetupDiDestroyDeviceInfoList(devInfo);
}
return handle;
}
/* Release the device handle */
static void CloseDeviceHandle(HANDLE handle)
{
if (handle != NULL && handle != INVALID_HANDLE_VALUE)
CloseHandle(handle);
}
// Parsec VDD core.
//////////////////////////////////////////////////
// Display name info.
static const char *VDD_DISPLAY_ID = "PSCCDD0"; // You will see it in registry (HKLM\SYSTEM\CurrentControlSet\Enum\DISPLAY)
static const char *VDD_DISPLAY_NAME = "ParsecVDA"; // You will see it in the [Advanced display settings] tab.
// Apdater GUID to obtain the device handle.
// {00b41627-04c4-429e-a26e-0265cf50c8fa}
static const GUID VDD_ADAPTER_GUID = { 0x00b41627, 0x04c4, 0x429e, { 0xa2, 0x6e, 0x02, 0x65, 0xcf, 0x50, 0xc8, 0xfa } };
static const char *VDD_ADAPTER_NAME = "Parsec Virtual Display Adapter";
// Class and hwid to query device status.
// {4d36e968-e325-11ce-bfc1-08002be10318}
static const GUID VDD_CLASS_GUID = { 0x4d36e968, 0xe325, 0x11ce, { 0xbf, 0xc1, 0x08, 0x00, 0x2b, 0xe1, 0x03, 0x18 } };
static const char *VDD_HARDWARE_ID = "Root\\Parsec\\VDA";
// Actually up to 16 devices could be created per adapter
// so just use a half to avoid plugging lag.
static const int VDD_MAX_DISPLAYS = 8;
// Core IoControl codes, see usage below.
typedef enum {
VDD_IOCTL_ADD = 0x0022e004,
VDD_IOCTL_REMOVE = 0x0022a008,
VDD_IOCTL_UPDATE = 0x0022a00c,
VDD_IOCTL_VERSION = 0x0022e010,
} VddCtlCode;
// Generic DeviceIoControl for all IoControl codes.
static DWORD VddIoControl(HANDLE vdd, VddCtlCode code, const void *data, size_t size)
{
if (vdd == NULL || vdd == INVALID_HANDLE_VALUE)
return 0;
BYTE InBuffer[32];
ZeroMemory(InBuffer, sizeof(InBuffer));
OVERLAPPED Overlapped;
ZeroMemory(&Overlapped, sizeof(OVERLAPPED));
DWORD OutBuffer = 0;
DWORD NumberOfBytesTransferred;
if (data != NULL && size > 0)
memcpy(InBuffer, data, (size < sizeof(InBuffer)) ? size : sizeof(InBuffer));
Overlapped.hEvent = CreateEventA(NULL, FALSE, FALSE, NULL);
DeviceIoControl(vdd, (DWORD)code, InBuffer, sizeof(InBuffer), &OutBuffer, sizeof(DWORD), NULL, &Overlapped);
GetOverlappedResult(vdd, &Overlapped, &NumberOfBytesTransferred, TRUE);
if (Overlapped.hEvent != NULL)
CloseHandle(Overlapped.hEvent);
return OutBuffer;
}
/**
* Query VDD minor version.
*
* @param vdd The device handle of VDD.
* @return The number of minor version.
*/
static int VddVersion(HANDLE vdd)
{
int minor = VddIoControl(vdd, VDD_IOCTL_VERSION, NULL, 0);
return minor;
}
/**
* Update/ping to VDD.
* Should call this function in a side thread for each
* less than 100ms to keep all added virtual displays alive.
*
* @param vdd The device handle of VDD.
*/
static void VddUpdate(HANDLE vdd)
{
VddIoControl(vdd, VDD_IOCTL_UPDATE, NULL, 0);
}
/**
* Add/plug a virtual display.
*
* @param vdd The device handle of VDD.
* @return The index of the added display.
*/
static int VddAddDisplay(HANDLE vdd)
{
int idx = VddIoControl(vdd, VDD_IOCTL_ADD, NULL, 0);
VddUpdate(vdd);
return idx;
}
/**
* Remove/unplug a virtual display.
*
* @param vdd The device handle of VDD.
* @param index The index of the display will be removed.
*/
static void VddRemoveDisplay(HANDLE vdd, int index)
{
// 16-bit BE index
UINT16 indexData = ((index & 0xFF) << 8) | ((index >> 8) & 0xFF);
VddIoControl(vdd, VDD_IOCTL_REMOVE, &indexData, sizeof(indexData));
VddUpdate(vdd);
}
#ifdef __cplusplus
}
#endif
#endif