检查 HSTI 的简便方法

‌HSTI(Hardware Security Test Interface)‌是由Microsoft定义的硬件安全测试接口,其全称为Hardware Security Testability Interface。HSTI的主要作用是确保Windows设备上的安全配置正确,通过定义一系列测试来验证设备的安全性。这些测试包括SPI闪存或eMMC分区锁定、SMM配置、Intel Boot Guard等安全功能的正确设置‌。

基本的原理是:BIOS在启动的时候进行一些检查,然后将检查结果汇报给 Windows。在进行微软 WHQL 测试时,这是一个必测的项目。通常情况下,BIOS工程师需要搭建WHQL测试环境,在目标机上安装 WHQL Client软件,每次修改后都需要在 WHQL Server 上运行测试并且查看结果。

现在,可以通过本地运行 FaintSnow 编写的 HE (Hardware Read & Write Utility)来进行检测,避免繁琐的WHQL环境设定:

打开工具后在 Tools ->Dump Hardware Security Test Interface Report中即可看到结果:

工具读取到的信息:

可以直接拉到最下面查看结果:

作者的网站是 http://hwrwdrv.phpnet.us 。可以在上面看到最新版。此外,还可以在 https://www.lab-z.com/allsoft/ 常用测试工具以及软件下载页面看到(不定期更新,下载速度较快),有测试需要的朋友可以试试这个工具。

参考:

1. https://www.lab-z.com/hstiwhql/

USB 手柄转无线

这是一个能够将USB 手柄转为无线手柄的设备。这样,你可以将有线的 USB 手柄变为无线的手柄进行游戏。之所以做这个项目是因为我发现Linux 系统在支持多HID设备上和 Windows上有所不同。之前设计的,能够在 Windows上工作正常的USB转蓝牙设备无法在 Linux 系统上正常工作。

这次带来的方案是有两部分:发射端和接收端。

发射端是 ESP32 C3 配合 CH9350实现的,它能够实现USB手柄数据的读取和无线信号的发送。其中 CH9350负责USB手柄数据的解析;解析后的数据由ESP32 C3通过ESP-Now发送出去。CH9350是 WCH (就是出品 Ch340 的那个公司)推出的USB HID 转串口通讯控制芯片。就是说USB 手柄连接到这个芯片之后,数据会转化为串口输出。关于这个芯片的功能介绍如下:

  • 支持12Mbps全速USB传输和1.5Mbps低速USB传输,兼容USB V2.0。
  • 上位机端USB端口符合标准HID类协议,不需要额外安装驱动程序,支持内置HID类设备驱动的Windows、Linux、macOS等操作系统。
  • 同一芯片可配置为上位机模式和下位机模式,分别连接USB-Host主机和USB键盘、鼠标。
  • 支持USB键盘鼠标在BIOS界面使用,支持多媒体功能键,支持不同分辨率USB鼠标。
  • 支持各种品牌的USB键盘鼠标、USB无线键盘鼠标、USB转PS2线等。
  • 上位机端和下位机端支持热插拔。
  • 提供发送状态引脚,支持485通讯。
  • 串口支持115200/57600/38400串口通信波特率。
  • 内置晶振和上电复位电路,外围电路简单。
  • 支持5V、3.3V电源电压。
  • 提供LQFP-48无铅封装,兼容RoHS。

发射端方案的优点是:成本比较低,体积比较小,容易DIY焊接(ESP32 C3 是我用过的最容易焊接的ESP32芯片)。能够同时支持2个USB设备,就是说你可以同时使用2个USB手柄,同时转化为无线给主机使用。

接收器使用的是ESP32-S2 ,它带有USB Device ,能够方便的将自身模拟为一个USB手柄。因此,无论 Windows 还是 Linux 只要支持USB 手柄,在操作系统端看起来插入的就是一个USB手柄,完全不会碰到兼容性问题。这里使用淘宝直接购买的Mini ESP32-S2 开发板,体积小,价格便宜。

接下来首先进行电路的设计。

  1. 主控 ESP32 C3 部分。这款主控内置了 USB 下载电路,我们设计一个 USB接口即可工作。此外,复位与下载按钮是必须的,当出现问题无法下载时,这两个按钮随时可以帮助恢复。

2.接下来时CH9350部分,它外部电路非常简单,只需要一个 3.3uf和一个 0.1uf电容即可工作。外部的 LED1和 LED2用来指示USB工作状态,没有有效数据时会亮,有数据传输时会熄灭。其中的USB3 是一个双层USB座子,这样我们可以同时使用两个USB手柄。

3.我们使用 TLV1117将5V 转为3.3V,同时还预留一个假负载,用来避免在使用充电宝供电,输出小于50ma 一段时间后自动关机的问题。

电路比较简单PCB设计也相对简单:

预览如下:

上面就是硬件设计,接下来着手软件的设计。

同样,分成发送端和接收端分别介绍。

发送端的主要工作是:USB 手柄数据的获取,获得数据的发送。

对于USB 手柄数据的获取和之前的并没有多少差别,只需要解析来自串口的数据即可;特别注意代码加入了判断,只有收到和之前数据不同的时候才会发送;

我们使用 ESP-Now 进行数据发送。对于 Arduino 开发来说,这个非常简单。创建 ESPNow 对象,然后指定发送的地址即可:

// 创建esp_now_peer_info_t类型变量存储有关peer方的信息。
esp_now_peer_info_t peerInfo0;
esp_now_peer_info_t peerInfo1;

//  ESP32 接收器 MAC 地址
uint8_t broadcastAddress0[] = {'L', 'A', 'B', 'Z', '-', '0'};
uint8_t broadcastAddress1[] = {'L', 'A', 'B', 'Z', '-', '1'};

之后每次收到改变后的USB手柄数据,可以使用下面的函数进行发送

// Send message via ESP-NOW
                esp_err_t result = esp_now_send(broadcastAddress1, (uint8_t *) &Data[i + 6], 7);

对应的接收端的设计如下:

  1. 程序开始处,设置当前ESP32 的MAC地址,作为接收数据的地址。发送端同时支持2个USB手柄,因此我们通过GPIO 判断设置2个Mac。这样,我们的程序刷到2个ESP32 S2主板上之后,根据外部跳线可以选择不同的 Mac 非常方便。
  if (digitalRead(ADDRESSPIN2) == HIGH) {
    esp_wifi_set_mac(WIFI_IF_STA, &newMACAddress0[0]);
  } else {
    esp_wifi_set_mac(WIFI_IF_STA, &newMACAddress1[0]);
  }
  if (DEBUGMODE) {
    Serial.print("[NEW] ESP32 Board MAC Address:  ");
    Serial.println(WiFi.macAddress());
  }

2.为了实现USB 手柄,我们使用USB 手柄相同的 Report 描述符,这样在操作系统端看起来插入的就是一个 HID 手柄。

    CustomHIDDevice(void) {
      static bool initialized = false;
      if (!initialized) {
        initialized = true;
        HID.addDevice(this, sizeof(report_descriptor));
      }
}

3.接收到的来自ESP NOW 的数据会出现在 OnDataRecv() 这个回调函数中,我们接收之后无需额外处理直接作为USB数据发送给主机即可。

// 创建一个回调函数,当 ESP32 通过 ESP-NOW 接收到数据时将被调用
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  if (DEBUGMODE) {
    // 收到的数字,例如:
    // 128 128 128    128 31 0
    // 发过来的数据直接就是 RAW 格式
    for (uint8_t i = 0; i < len; i++) {
      printf("%02x  ", incomingData[i]);
    }
    printf("\n");
  }

  if (HID.ready()) {
    Device.send((uint8_t *)&incomingData[0]);
  }
}

工作的视频在B站可以看到:

https://www.bilibili.com/video/BV168411r7r3/?share_source=copy_web&vd_source=5ca375392c3dd819bfc37d4672cb6d54

对应的 Arduino 代码在这里下载:

香港保险投诉局案例分析1

最近听说香港有一个“香港保险投诉局(Insurance Complaints Bureau)”,它一家成立于2018年1月16日,前身是香港保险投诉索偿局,是一个独立的、非营利性的保险纠纷调解机构。该机构旨在为保险消费者提供便捷、高效的纠纷解决服务,维护消费者权益,促进保险市场的公平和诚信。

也就是说当投保人申请理赔时遇到拒赔时,且认为理赔结果不合理时,无需先走司法程序,可直接向保险投诉局投诉,他们接到投诉后将以裁决的方式处理。【参考1】

好奇之下,去看看他们的案例【参考2】,第二页:

何谓「表面证据不成立」?

陈先生计划于2021年7月前往上海,并向保险公司投购旅游保险,而保险公司网页上闪烁的宣传字句「隔离现金津贴」吸引了他的注意。
根据中国对2019冠状病毒病疫情实施的检疫政策,陈先生于上海的一家酒店接受了 14 天的集中强制检疫,酒店费用接近12,000人民币。回港后,他向保险公司申索于隔离期间的酒店费用,但他的索偿遭保险公司拒绝。
有关保单的「强制隔离现金津贴」条款订明:「如受保人因疑似感染或确诊患上传染病而于旅程期间或于返回常住地方后七日内被强制隔离,保险公司将就每个完整天数支付现金津贴500港元……」
陈先生的个案被定为表面证据不成立,原因是:
1) 他被强制隔离的原因并非因为疑似感染或确诊患上传染病;及
2) 保险的原则仅就不可预见或预料的事件向个人或实体提供保障。由于内地当时的政策要求
所有到上海的入境旅客必须隔离至少 14 天,因此相关隔离而导致的酒店费用属预期和可
以预见,不符合保险的承保原则。

这就是为什么语文要做阅读理解吧?话说保险条款还真是阅读理解的好材料。

三十多年前,我老家(大庆市)流传着一个故事。本地人习惯将罐车称作“大罐”。

然后有“狡猾”的外地人过来和本地人签合同,内容是以几十万元的价格出售“大罐”给本地。交付的时候,真的是大铁罐,但是合同如此,最终官司打不赢白白让对方占了便宜。

参考:

1.https://baijiahao.baidu.com/s?id=1820155891286124609&wfr=spider&for=pc

2.https://www.icb.org.hk/gb/doc/ICB-Connect-Issue-1.pdf

Step to UEFI (297)编写一个 UEFI Shell Command 的框架

本文根据 https://github.com/apop2/GopRotate 的代码编写,这个在之前的【参考1】和【参考2】中有介绍过。

本文介绍了如何编写一个 UEFI Shell Command 的方法,然后给出了一个基本的框架,有需要的朋友可以方便的扩展定制自己所需的 UEFI Shell。代码在 edk2 202411 下实验成功。

以WinHost 模拟器为例,介绍例子的测试方法:

1.解压到 \ShellPkg\Library 下面

2.修改 \EmulatorPkg\EmulatorPkg.dsc文件

BcfgCommandLib|ShellPkg/Library/UefiShellBcfgCommandLib/UefiShellBcfgCommandLib.inf
      IoLib|MdePkg/Library/BaseIoLibIntrinsic/BaseIoLibIntrinsic.inf
##LABZDEBUG_Start
	  NULL|ShellPkg/Library/MyShellCommandLib/MyShellCommandLib.inf
##LABZDEBUG_End	

    &lt;PcdsFixedAtBuild>
      gEfiMdePkgTokenSpaceGuid.PcdDebugPropertyMask|0xFF
      gEfiShellPkgTokenSpaceGuid.PcdShellLibAutoInitialize|FALSE
      gEfiMdePkgTokenSpaceGuid.PcdUefiLibMaxPrintBufferSize|8000

3.重新编译,使用 build -a X64 即可

运行 WinHost测试如下:

这里定了一个 mycmd 命令,然后可以接受一个参数。MyCommand.c 中的MyShellCommand() 函数负责实际执行 ShellPkg\Library\MyShellCommandLib\MyShellCommandLib.c 中的MyShellCommandLibConstructor() 函数负责向 Shell 注册 mycmd 命令,同时注册一个帮助文本,通过 STR_GET_MYCMD_HELP 定义。

EFI_STATUS EFIAPI MyShellCommandLibConstructor(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE *SystemTable)
{
    gMyShellCommandHiiHandle = HiiAddPackages (&gMyShellCommandGuid, ImageHandle, MyShellCommandLibStrings, NULL);
    if(gMyShellCommandHiiHandle != NULL)
    {
        ShellCommandRegisterCommandName(L"mycmd", MyShellCommand, MyShellCommandGetFileName, 0, L"", FALSE, gMyShellCommandHiiHandle, STRING_TOKEN(STR_GET_MYCMD_HELP));
    }
    return EFI_SUCCESS;
}

有兴趣的朋友可以使用这个框架自行扩展 UEFI Shell。

参考:

  1. https://www.lab-z.com/stu90shc/
  2. http://www.lab-z.com/stu88/

TrustedInstaller 无法删除目录和文件的解决方法

最近在测试的时候多次遇到了文件无法删除的情况

经过研究可以使用下面的方法进行删除:

1.运行Regfix.reg 文件将设置导入注册表

2.选中文件或者目录后,鼠标右键菜单会出现“Take Ownership”的选项,选中执行即可

3.运行UninstallRegfix.reg 可以去掉上述的菜单选项

下载:

亲测有效,推荐有需要的朋友试试。

参考:

1.https://thegeekpage.com/how-to-delete-files-protected-by-trustedinstaller-in-windows-10/

EDK2 202411 来了

去年11月份,EDK2 202411正式发布在:

https://github.com/tianocore/edk2/releases/tag/edk2-stable202411

从 History 来看,只是有一些功能性改进:

和之前类似,这里放上一个完整版,补全了所有的三方库,大小是467MB 左右。下载之后,运行过一次 edksetup.bat forcerebuild 编译了需要用的工具,否则直接使用可能会遇到一些工具无法找到的错误。

链接:https://pan.baidu.com/s/1JjJOr7MWxxUsj0ip1Leg2Q?pwd=LABZ

提取码: LABZ

个人建议:除非有特别明确的目的,否则没有必要追求最新的版本, 所谓 “小车能跑只管推”。

三亚 外一篇:沙滩

我在三亚居住的酒店距离大海非常近,早晨起来我一个人出门去了海边。春节时间到三亚最重要的问题是保暖,千万不要因为感觉一下子暖和起来就减去衣物。作为一个东北人,我对温度还是非常敏感的,稍微感觉寒冷就会添加衣物。而我的老婆则是“非常抗冻”的湖北人,于是到达三亚的第二天,她穿的很少和我们一起去了一下南山寺。刚一下车她就开始后悔穿的过少。我都戏称门上的“二不”是专门说她的。第三天,她就开始不舒服头疼,后面更是在九所的宾馆一直“趴窝”了。

沙滩的沙子很细腻,这边远离市区,加上比较早,所以没有多少人。

太阳还没有升起,云高海阔如是:

沙滩上有无数小洞穴:

洞很圆,很有规则的形状,是小螃蟹挖出来的。打一个洞,然后藏身于此。海滩上有小朋友拿着铲子对准洞穴,斜着挖下去,运气好的话马上就会有小螃蟹慌张的逃出来:

刚好在海滩上遇到一群人,腰上缠绕着绳子,绳子末端带有一个圆球。起初我以为是要进行什么游戏。

等他们列队开工成我才知道原来是一种古老的捕鱼方式。首先,海里面有一艘渔船,下网,然后网的一头捆扎住绳子交给岸上的人。接下来这些人用腰间的绳子连接到这个网的绳子上。缠绕的方式也非常简单:用球在绳子上一丢很快就缠绕好了,然后用腰部的力量拉拽着网绳。拉拽的过程中完全凭借腰部和身体的力量完全不用手,可以一边吸烟一边工作着。前面的人用力拉拽,向着远离大海的方向退去,退倒最后再将带着球的绳子解下来,再走到最前面缠绳子上重复这样的动作。这样我怀疑最早的拔河比赛是否也发源于此。后来查了一下,一百年前,拔河是奥运比赛项目,后来因为英国人创造性的作弊导致这个项目最终被取消了。

渔网很长,我看了半个多小时也没有到头的意思,后面就回去酒店了。路上偶然看到了波罗蜜,原来它是这样长在树上的:

细枝硕果:

PVD:绝对值鼠标

我们通常使用的鼠标发送的都是相对坐标,比如:输出 X=100,Y=200, 意思是通知系统将鼠标从目前的位置移动(100,200)个单位。此外,Windows 还有一个鼠标优化机制,比如,你的鼠标输出范围是 +/- 127, 如果你的屏幕宽度是 1920, 经过优化之后无需滑动 1920/127 次才能将鼠标指针从最左边移动到最右边。

这次带来的PVD (Physical Virtual Device)是一款绝对值鼠标,它模拟了一个绝对值鼠标,能够将从USB串口输入的数据转化为绝对值鼠标数据发送出来。这样,可以方便的实现鼠标点击屏幕上的任意位置。

硬件部分使用和上一次设计相同【参考1】,核心是 WCH出品的 CH554e单片机。

代码是 Arduino 来实现的,屏幕左上角是 (0,0), 输出坐标为 0-32767。实践表明:送出来的数据和屏幕分辨率存在定比例关系。比如,在 1920*1024屏幕上,如果想让鼠标移动到 (300, 200)的位置,那么需要设备输出(300/1920 *32768, 200/1024 *32768)这样的数据。

主程序代码如下:

#ifndef USER_USB_RAM
#error "This example needs to be compiled with a USER USB setting"
#endif

#include <WS2812.h>
#include "src/CdcHidCombo/USBCDC.h"
#include "src/CdcHidCombo/USBHIDKeyboardMouse.h"

#define NUM_LEDS 1
#define COLOR_PER_LEDS 3
#define NUM_BYTES (NUM_LEDS*COLOR_PER_LEDS)

__xdata uint8_t ledData[NUM_BYTES];

#define  KeyboardReportID  0x01
#define  rMouseReportID    0x02
#define  aMouseReportID    0x03
#define  OnBoardLED        0x04

// Data format
// Keyboard(Total 9 bytes): 01(ReportID 01) + Keyboard data (8 Bytes)
// Mouse(Total 5 bytes): 02(ReportID 02) + Mouse Data (4 Bytes)
uint8_t recvStr[9];
uint8_t recvStrPtr = 0;
unsigned long Elsp;

void setup() {
  USBInit();
  Serial0_begin(115200);
  delay(1000);
  Serial0_print("start");
  Elsp=0;
}

void loop() {
  while (USBSerial_available()) {
    uint8_t serialChar = USBSerial_read();
    recvStr[recvStrPtr++] = serialChar;
    if (recvStrPtr == 10) {
      for (uint8_t i = 0; i < 9; i++) {
        Serial0_write(recvStr[i]);
      }      
      if (recvStr[0] == KeyboardReportID) { // Keyboard
        USB_EP3_send(recvStr, 9);
      }
      if (recvStr[0] == rMouseReportID) {
        USB_EP3_send(recvStr, 5); // Relative Mouse
      }
      if (recvStr[0] == aMouseReportID) {
        USB_EP3_send(recvStr, 7); // Absolute Mouse
      }      
      if (recvStr[0] == OnBoardLED) {
        set_pixel_for_GRB_LED(ledData, 0, recvStr[1], recvStr[2], recvStr[3]); 
        neopixel_show_P1_5(ledData, NUM_BYTES);  
      }



      recvStrPtr = 0;
    }
    Elsp=millis();
  }
  // If there is no data in 100ms, clear the receive buffer
  if (millis()-Elsp>100) {
      recvStrPtr = 0;
      Elsp=millis();
    }
}

如果USB CDC 收到以 aMouseReportID 起始的9个字节,那么会将数据直接转到绝对值鼠标端点然后发送给主机。

VS2019 编写一个上位机程序进行测试,代码如下:

// CDC_vKBMSTest.cpp : This file contains the 'main' function. Program execution begins and ends there.
//

#include <windows.h>
#include <SetupAPI.h>
#include <tchar.h>
#include <iostream>
#include <cstring>
#include <atlstr.h>

#pragma comment(lib, "Setupapi.lib")

#define MY_USB_PID_VID	_T("VID_1209&PID_C55C")
#define SCREENWIDTH 1920
#define SCREENHEIGHT 1200
int Port;

class ComPortException : public std::exception {
public:
	ComPortException(DWORD errorCode) : errorCode(errorCode) {}

	DWORD getErrorCode() const {
		return errorCode;
	}

private:
	DWORD errorCode;
};

// 对串口portName 发送数据
// 无法打开串口返回 1
// 无法设置串口参数 2
void SendToComPort(const int port, const char* data) {
	int Result = 0;
	HANDLE hCom = INVALID_HANDLE_VALUE;
	TCHAR portName[10]; // 用于存储 "COMp" 字符串

// 将整数 p 转换为 "COMp" 字符串
#ifdef UNICODE
	swprintf(portName, 10, _T("\\\\.\\COM%d"), port);
#else
	sprintf(portName, "COM%d", port);
#endif
	try {
		// 打开串口
		hCom = CreateFile(portName,
			GENERIC_READ | GENERIC_WRITE,
			0,
			NULL,
			OPEN_EXISTING,
			0,
			NULL);

		if (hCom == INVALID_HANDLE_VALUE) {
			throw ComPortException(GetLastError());
		}

		// 设置串口参数
		DCB dcb;
		SecureZeroMemory(&dcb, sizeof(DCB));
		dcb.DCBlength = sizeof(DCB);

		if (!GetCommState(hCom, &dcb)) {
			throw ComPortException(GetLastError());
		}

		dcb.BaudRate = CBR_115200;  // 波特率
		dcb.ByteSize = 8;         // 数据位
		dcb.StopBits = ONESTOPBIT; // 停止位
		dcb.Parity = NOPARITY;    // 校验位

		if (!SetCommState(hCom, &dcb)) {
			throw ComPortException(GetLastError());
		}

		// 设置超时参数
		COMMTIMEOUTS timeouts;
		timeouts.ReadIntervalTimeout = 50;
		timeouts.ReadTotalTimeoutConstant = 50;
		timeouts.ReadTotalTimeoutMultiplier = 10;
		timeouts.WriteTotalTimeoutConstant = 50;
		timeouts.WriteTotalTimeoutMultiplier = 10;

		if (!SetCommTimeouts(hCom, &timeouts)) {
			throw ComPortException(GetLastError());
		}

		// 发送数据
		DWORD bytesWritten;
		if (!WriteFile(hCom, data, 10, &bytesWritten, NULL)) {
			std::cerr << "Failed to write to COM port." << std::endl;
		}
		else {
			std::cout << "Successfully sent data to COM port: [" << port << "]" << std::endl;
		}

		for (int i = 0; i < 10; i++) {
			printf("%02x ", data[i]&0xFF);
		}
		printf("\n");
		// 关闭串口
		if (hCom != INVALID_HANDLE_VALUE) {
			CloseHandle(hCom);
		}
	}
	catch (const ComPortException& ex) {
		std::cerr << "Error: " << ex.getErrorCode() << std::endl;
		if (hCom != INVALID_HANDLE_VALUE) {
			CloseHandle(hCom);
		}
	}

}

/************************************************************************/
/* 根据USB描述信息字符串中读取
/************************************************************************/
int MTGetPortFromVidPid(CString strVidPid)
{
	// 获取当前系统所有使用的设备
	int					nPort = -1;
	int					nStart = -1;
	int					nEnd = -1;
	int					i = 0;
	CString				strTemp, strName;
	DWORD				dwFlag = (DIGCF_ALLCLASSES | DIGCF_PRESENT);
	HDEVINFO			hDevInfo = INVALID_HANDLE_VALUE;
	SP_DEVINFO_DATA		sDevInfoData;
	TCHAR				szDis[2048] = { 0x00 };// 存储设备实例ID
	TCHAR				szFN[MAX_PATH] = { 0x00 };// 存储设备实例属性
	DWORD				nSize = 0;

	// 准备遍历所有设备查找USB
	hDevInfo = SetupDiGetClassDevs(NULL, L"USB", NULL, dwFlag);
	if (INVALID_HANDLE_VALUE == hDevInfo)
		goto STEP_END;

	// 开始遍历所有设备
	memset(&sDevInfoData, 0x00, sizeof(SP_DEVICE_INTERFACE_DATA));
	sDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
	for (i = 0; SetupDiEnumDeviceInfo(hDevInfo, i, &sDevInfoData); i++)
	{
		nSize = 0;

		// 无效设备
		if (!SetupDiGetDeviceInstanceId(hDevInfo, &sDevInfoData, szDis, sizeof(szDis), &nSize))
			goto STEP_END;

		// 根据设备信息寻找VID PID一致的设备
		strTemp.Format(_T("%s"), szDis);
		strTemp.MakeUpper();
		if (strTemp.Find(strVidPid, 0) == -1)
			continue;

		// 查找设备属性
		nSize = 0;
		SetupDiGetDeviceRegistryProperty(hDevInfo, &sDevInfoData,
			SPDRP_FRIENDLYNAME,
			0, (PBYTE)szFN,
			sizeof(szFN),
			&nSize);

		// "XXX Virtual Com Port (COM7)"
		strName.Format(_T("%s"), szFN);
		_tprintf(_T("%s"), szFN);
		if (strName.IsEmpty())
			//goto STEP_END;
			continue;

		// 寻找串口信息
		nStart = strName.Find(_T("(COM"), 0);
		nEnd = strName.Find(_T(")"), 0);
		if (nStart == -1 || nEnd == -1)
			//goto STEP_END;
			continue;

		strTemp = strName.Mid(nStart + 4, nEnd - nStart - 4);
		nPort = _ttoi(strTemp);

	}
STEP_END:

	// 关闭设备信息集句柄
	if (hDevInfo != INVALID_HANDLE_VALUE)
	{
		SetupDiDestroyDeviceInfoList(hDevInfo);
		hDevInfo = INVALID_HANDLE_VALUE;
	}

	return nPort;
}

int ScreenX2Abs(int X) {
	return (X* 32768 / SCREENWIDTH);
}
int ScreenY2Abs(int Y) {
	return (Y  * 32768 / SCREENHEIGHT);
}

void ChangeColor(
	int X,
	int Y
) {
	char Data[10];

	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(X) & 0xFF;
	Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(Y) & 0xFF;
	Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 点击
	Data[2] = ScreenX2Abs(X) & 0xFF;
	Data[3] = (ScreenX2Abs(X) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(Y) & 0xFF;
	Data[5] = (ScreenY2Abs(Y) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);
}

void LedColor(
	int Color
) {
	char Data[10];

	memset(Data, 0, sizeof(Data));
	Data[0] = 4; // LED
	Data[1] = Color&0xFF; // 点击
	Data[2] = (Color>>8) & 0xFFF;
	Data[3] = (Color >>16) & 0xFF;

	SendToComPort(Port, (char*)&Data);
}

void DrawRect(
	int StartX,
	int StartY,
	int Width,
	int Height,
	int Dx,
	int Dy
) {
	char Data[10];

	//从左到右横线
	//line(StartX, StartY, StartX + Width, StartY);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX+Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	//从上到下,竖线
	//line(StartX + Width, StartY,StartX+Width,StartY+Height);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX+Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	//从右到左,横线
	//line(StartX + Width, StartY + Height,StartX+Dx,StartY+Height);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Width) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Width) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY+Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY+Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	//从下到上竖线
	//line(StartX + Dx, StartY + Height, StartX + Dx, StartY + Dy);
	memset(Data, 0, sizeof(Data));
	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Height) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Height) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 1; // 点击
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);

	Data[0] = 3; // 绝对值鼠标
	Data[1] = 0; // 抬起
	Data[2] = ScreenX2Abs(StartX + Dx) & 0xFF;
	Data[3] = (ScreenX2Abs(StartX + Dx) >> 8) & 0xFF;
	Data[4] = ScreenY2Abs(StartY + Dy) & 0xFF;
	Data[5] = (ScreenY2Abs(StartY + Dy) >> 8) & 0xFF;
	SendToComPort(Port, (char*)&Data);
	Sleep(100);
}

int main()
{

	printf("%d[%x]  %d[%x]\n", ScreenX2Abs(100), ScreenX2Abs(100), ScreenY2Abs(300), ScreenY2Abs(300));
	printf("Virutal KB MS Test\n");
	Port = MTGetPortFromVidPid(MY_USB_PID_VID);
	if (Port == -1) {
		printf("No device is found\n");
		goto EndProgram;
	}
	else {
		printf("Found COM%d\n",Port);
	}
	Sleep(5000);
	for (int i = 0; i < 8; i++) {
		DrawRect(100+20*i, 300+20*i, SCREENWIDTH - 200-20*i*2, SCREENHEIGHT - 500-20*i*2, 20, 20);
		if ((i % 3) == 0) {
			LedColor(0x01);
		}
		if ((i % 3) == 1) {
			LedColor(0x0100);
		}
		if ((i % 3) == 2) {
			LedColor(0x010000);
		}
	}
	
	ChangeColor(1329,125);
	for (int i = 8; i < 18; i++) {
		DrawRect(100 + 20 * i, 300 + 20 * i, SCREENWIDTH - 200 - 20 * i * 2, SCREENHEIGHT - 500 - 20 * i * 2, 20, 20);
		if ((i % 3) == 0) {
			LedColor(0x01);
		}
		if ((i % 3) == 1) {
			LedColor(0x0100);
		}
		if ((i % 3) == 2) {
			LedColor(0x010000);
		}
	}


EndProgram:
	printf("End\n");
	getchar();
}

这个代码的作用是在画笔中绘制蛇形线,在绘制过程中还有更换颜色的动作。

工作的视频: 【PVD 计划:绝对值鼠标测试】

参考:

1. https://mc.dfrobot.com.cn/thread-323494-1-1.html