目录
前言
一、Scancode Map 的规范
二、禁用 CtrlAltDel 的方法及其缺陷
三、编程实现和测试
3.1 C++ 实现的简易修改工具
3.2 C# 实现的窗口工具
四、总结
本文属于原创文章,转载请注明出处:
https://blog.csdn.net/qq_59075481/article/details/136104444。
前言
在 Ndr-LRPC Hook 和 WMsg Hook 等方法完善前,网络上公开的禁用 CtrlAltDel 键的方法是使用 "Scancode Map" 键盘扫描码映射表这个方法,本质上是利用微软提供的注册表设置来达到屏蔽的效果。这确实在前一阶段是较好的解决方案,所以在更新完前两种方案后,我不打算对这个方法避而不谈,相反,我觉得该方法可以用于更广泛的方面,甚至许多键盘快捷键修改程序就利用了类似的方法。本文将就具体的实现细节给出通用修改工具以及谈谈这种方法存在优缺点。工具有两个,一个是我重写的 C++ 简化实现,另一个是基于 C# 的开源工具(查阅资料时偶然发现)。
一、Scancode Map 的规范
Scancode Map 注册表项位于注册表如下路径:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Keyboard Layout
它是 Keyboard Layout 子键下名为"Scancode Map"的二进制值项,如果没有你可以新建一个。
这个值项可实现对键盘按键的映射。这里映射的意思可理解为“替换”,可将任意一键替换成其它键,或者禁用。
Scancode Map 注册表项具有类似下面的格式:
以 16 进制表示,可分为五个部分,为了方便讲解,相邻字节之间用逗号隔开。
"Scancode Map" = 00,00,00,00,00,00,00,00,02,00,00,00,01,00, 02,00,00,00,00,00
前 8 个字节: 表示版本信息号,一般为 0。
紧接着的 4 个字节: 表示映射键的总数 + 1。按照二进制的读写规则,低位在左,高位在右。02 00 00 00 这个数实际就是:00 00 00 02 ,该用例表示当前修改 1 个键。
紧接着的 2 个字节: 表示替换后按键的“扫描码”。如:ESC 键的扫描码是 01 ,所以就表示 01 00 。再比如左 Ctrl 键扫描码是 1D 00, 而右 Ctrl 键是 1D E0 。
紧接着的 2 个字节:表示原按键的“扫描码”,格式同上。
最后以四个 00 结束,(8个也行)。
键盘的按键扫描码可以参考下面这幅图(已经按照书写格式排列):
图片来源:https://www.bilibili.com/read/cv8001022/
二、禁用 CtrlAltDel 的方法及其缺陷
根据上面提供的信息,我们了解到需要禁用 Ctrl + Alt + Del 键,只禁用 Del 就可以了,不需要修改左键,原因是 Ctrl / Alt 需要经常用,而 Del 用的频率较少。
经查表,Del 键对应的扫描码是 53 E0,所以按照规则,我们只需要将 53 E0 映射为其他按键或者映射为空即可,比如下图:
这张图是注册表中修改过后的结果,圈出来的就是需要关注的的位置, 02 表示总数加一,连同后面 3 个 00,一共四个字节;后面两字节是替换的扫描码,我这里替换成数字 1(4F 00)。原键的扫描码是 53 E0。
这个方法优点就是实现简单。
当然这个方法缺陷很明显,不可避免的按键要被无差别禁用,Del 键却还有其他功能,所以会影响使用体验。
三、编程实现和测试
那么如何通过代码来实现修改呢?很简单,就是读取注册表和修改注册表,只不过需要按照字节序构建数据。
3.1 C++ 实现的简易修改工具
为了方便制作一个简易的测试工具,我为工具定义了 config.cfg 配置文件的规则,样例如下:
# 此处填写要修改的总数,当然程序写入注册表时,按照规则注册表的记录总数比用户实际修改的个数多加1,这在之前也介绍过。
[ReMapKeyNum]
3
# 修改的键的顺序
[MapKeyRank]
1
# 修改的键的原始扫描码,格式:十六进制,两个字符。
[MapKeyOriCode]
1d00
# 修改的键的新扫描码,格式:十六进制,两个字符。
[MapKeyNewCode]
1de0
[MapKeyRank]
2
[MapKeyOriCode]
3800
[MapKeyNewCode]
38e0
[MapKeyRank]
3
[MapKeyOriCode]
53e0
[MapKeyNewCode]
0000
配置文件放在任意位置均可,目前已经实现功能的参数列表如下:
- /enumerate :解析注册表中重映射按键的数据,该方法会逆编码原始按键名称(不过目前未实现区分左右按键)
- /setremap <configFilePath>:指定一个路径作为配置文件路径,配置文件按照规范书写,程序将解析配置文件并用该配置文件覆盖注册表按键映射表
- /queryActiveKey:动态查询键盘输入的按键的扫描码(不区分左右按键,这点需要改进)
- /deleteAllMap:删除整个注册表按键映射表,恢复系统默认配置(需要二次确认操作)。注意:该操作无法撤销删除
- /modifyDelKey:删除注册表按键映射表中指定的条目。该命令首先解析所有条目信息,随后通过输入要删除的 Item 编号即可实现删除
- /modifyInsertKey:在指定的 Item 编号之后添加新的数据条目。需要手动输入三个参数,第一个为原始按键扫描码,第二个为修改后的按键扫描码,第三个为 Item 编号
- /modifyAddKey:不需要指定位置编号,在结尾追加新的条目。
- /statusMap <bEnable>:启用或禁用重映射键注册表项(该操作通过将映射表标记为 DISABLED 来禁用整张表,所以操作可以恢复),status 参数为 0 表示禁用,为 1 表示启用
- /makebackup:备份注册表当前重映射表数据。该命令拷贝一个按照时间记录的注册表副本。
- /recoverMap <backupTime>:恢复按照时间标签备份的副本,该操作将覆盖当前的注册表按键映射表。
- /readbackup:读取所有注册表备份(按照时间标签列表显示)
完整代码如下(注意所有的修改除了备份数据其他操作对于大部分应用需要重启计算机生效):
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <Windows.h>
#include <iomanip>// 定义宏
#define REGISTRY_KEY L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layout"
#define REGISTRY_VALUE_NAME L"Scancode Map"
#define CONFIG_FILE_SECTION_REMAP_KEY_NUM "[ReMapKeyNum]"
#define CONFIG_FILE_SECTION_MAP_KEY_RANK "[MapKeyRank]"
#define CONFIG_FILE_SECTION_MAP_KEY_ORI_CODE "[MapKeyOriCode]"
#define CONFIG_FILE_SECTION_MAP_KEY_NEW_CODE "[MapKeyNewCode]"
#define BACKUP_KEY_PREFIX L"Scancode_Map_Back_"
#define DESABLED_KEY_PREFIX L"_DISABLED"// 全局变量用于记录按键状态
int remainingKeys = 0;
int remainingRounds = 0;
int numKeys = 0;
std::vector<std::pair<std::string, DWORD>> keyNameValuePairs;// 解析注册表中的重映射信息
bool ParseRemapInfo(std::vector<std::pair<WORD, WORD>>& remapInfo) {HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_READ, &hKey) == ERROR_SUCCESS) {DWORD bufferSize = 0;DWORD dataType;if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, &dataType, NULL, &bufferSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::vector<BYTE> buffer(bufferSize);if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, NULL, buffer.data(), &bufferSize) == ERROR_SUCCESS) {DWORD totalRemappedKeys = *(DWORD*)(buffer.data() + 8) - 1;for (DWORD i = 0; i < totalRemappedKeys; ++i) {WORD remappedScanCode = *(WORD*)(buffer.data() + 12 + i * 4);WORD originalScanCode = *(WORD*)(buffer.data() + 14 + i * 4);remapInfo.emplace_back(originalScanCode, remappedScanCode);}}RegCloseKey(hKey);return true;}else {RegCloseKey(hKey);return false;}}return false;
}// 枚举当前重映射的按键并逆向转换扫描码为键名
void EnumerateRemappedKeys(std::vector<std::pair<WORD, WORD>> remapInfo) {std::cout << "Remapped keys:\n";CHAR remappedKeyName[30] = { 0 }, originalKeyName[30] = { 0 };WORD remappedScanCode = 0, originalScanCode = 0;int newKeyret = 0, oldKeyret = 0;for (size_t i = 0; i < remapInfo.size(); ++i) {newKeyret = 0, oldKeyret = 0;memset(remappedKeyName, 0, sizeof(remappedKeyName));memset(originalKeyName, 0, sizeof(originalKeyName));originalScanCode = remapInfo[i].first;remappedScanCode = remapInfo[i].second;newKeyret = GetKeyNameTextA(remappedScanCode << 16,remappedKeyName, sizeof(remappedKeyName) / sizeof(*remappedKeyName));oldKeyret = GetKeyNameTextA(originalScanCode << 16,originalKeyName, sizeof(originalKeyName) / sizeof(*originalKeyName));// 逆向转换扫描码为键名std::cout << "Item " << i + 1 << ":\n";std::cout << " Remapped Scan Code: 0x" << std::hex << remappedScanCode << "\n";std::cout << " Remapped Key Name: " << (newKeyret > 0 ? remappedKeyName : "(null)") << "\n";std::cout << " Original Scan Code: 0x" << std::hex << originalScanCode << "\n";std::cout << " Original Key Name: " << (oldKeyret > 0 ? originalKeyName : "(null)") << "\n";}
}// 根据配置文件修改或添加重映射的按键
void ModifyRemappedKeys(const std::string& configFile) {std::ifstream file(configFile);if (!file.is_open()) {std::cerr << "Failed to open config file: " << configFile << std::endl;return;}std::string line;int totalRemappedKeys = 0;WORD tempCode = 0, theCode = 0;std::vector<WORD> originalScanCodes, newScanCodes;while (std::getline(file, line)) {if (line.empty() || line[0] == '#') // 绕过空行和注释continue;if (line.find(CONFIG_FILE_SECTION_REMAP_KEY_NUM) != std::string::npos) {// 循环绕过多行注释或空行do{std::getline(file, line); // 读取包含总重映射按键数的下一行}while (line.empty() || line[0] == '#');std::istringstream iss(line);iss >> totalRemappedKeys;originalScanCodes.reserve(totalRemappedKeys);newScanCodes.reserve(totalRemappedKeys);}else if (line.find(CONFIG_FILE_SECTION_MAP_KEY_ORI_CODE) != std::string::npos) {do {std::getline(file, line); // 读取包含原始扫描码的行} while (line.empty() || line[0] == '#');std::istringstream iss(line);std::string hexCode;iss >> hexCode;tempCode = static_cast<WORD>(std::stoi(hexCode, nullptr, 16));// stoi 读取时默认小端序,会导致数据颠倒,需要恢复一下theCode = ((tempCode & 0xFF) << 8) | ((tempCode >> 8) & 0xFF);originalScanCodes.push_back(theCode);}else if (line.find(CONFIG_FILE_SECTION_MAP_KEY_NEW_CODE) != std::string::npos) {do {std::getline(file, line); // 读取包含新扫描码的行} while (line.empty() || line[0] == '#');std::istringstream iss(line);std::string hexCode;iss >> hexCode;tempCode = static_cast<WORD>(std::stoi(hexCode, nullptr, 16));// stoi 读取时默认小端序,会导致数据颠倒,需要恢复一下theCode = ((tempCode & 0xFF) << 8) | ((tempCode >> 8) & 0xFF);newScanCodes.push_back(theCode);}}if (originalScanCodes.size() != totalRemappedKeys || newScanCodes.size() != totalRemappedKeys) {std::cerr << "Invalid configuration file format." << std::endl;return;}// 创建基于配置数据的缓冲区std::vector<BYTE> buffer(20 + totalRemappedKeys * 4, 0); // 头部(20 字节)+ 重映射按键数据*(DWORD*)(buffer.data() + 8) = totalRemappedKeys + 1; // 记录个数 = 实际个数 + 1(规则)for (int i = 0; i < totalRemappedKeys; ++i) {std::cout << "OriKeyCode: 0x" << std::hex << originalScanCodes[i] <<"\t NewKeyCode: 0x" << std::hex << newScanCodes[i] << "\n";// 写入数据到向量结构中*(WORD*)(buffer.data() + 12 + i * 4) = newScanCodes[i];*(WORD*)(buffer.data() + 14 + i * 4) = originalScanCodes[i];}// 修改注册表中的重映射按键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {if (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), buffer.size()) == ERROR_SUCCESS) {std::cout << "Scancode Map updated successfully.\n";}else {std::cerr << "Failed to update Scancode Map.\n";}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key.\n";}
}// 钩子过程函数,处理键盘输入消息
LRESULT CALLBACK KeyboardHookProc(int nCode, WPARAM wParam, LPARAM lParam) {if (nCode == HC_ACTION) {KBDLLHOOKSTRUCT* kbStruct = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN) {CHAR keyName[256];if (GetKeyNameTextA(kbStruct->scanCode << 16, keyName, sizeof(keyName)) > 0) {keyNameValuePairs.emplace_back(keyName, kbStruct->scanCode);if (--remainingKeys == 0) {std::cout << "Round completed.\n";if (--remainingRounds == 0) {std::cout << "Maximum rounds reached.\n";std::cout << "Continuous Input:\n";for (const auto& pair : keyNameValuePairs) {std::cout << pair.first << ": 0x" << std::hex << pair.second << "\n";}PostQuitMessage(0); // 退出消息循环}else {std::cout << "Continuous Input so far:\n";for (const auto& pair : keyNameValuePairs) {std::cout << pair.first << ": 0x" << std::hex << pair.second << "\n";}keyNameValuePairs.clear();remainingKeys = numKeys;std::cout << "\nPlease press " << numKeys << " keys per round.\n";}}}}}return CallNextHookEx(NULL, nCode, wParam, lParam);
}// 监视键盘输入以查询给定键的扫描码
void QueryScanCodeForKey() {// 键入循环参数do {std::cout << "Please press numbers of keys per round, " <<"and a maximum of rounds (for at least 1 key for 1 round)." << std::endl;std::cin >> numKeys >> remainingRounds;} while (numKeys < 1 || remainingRounds < 1);std::cout << "Monitoring " << numKeys << " keys per round, for a maximum of " << remainingRounds << " rounds.\n";remainingKeys = numKeys;// 安装键盘输入的钩子HHOOK keyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL, KeyboardHookProc, NULL, 0);// 消息循环以保持钩子活动MSG msg;while (GetMessage(&msg, NULL, 0, 0)) {TranslateMessage(&msg);DispatchMessage(&msg);}// 卸载钩子UnhookWindowsHookEx(keyboardHook);
}// 从注册表中删除所有重映射按键
void DeleteAllRemappedKeys() {// 二次确认std::cout << "Are you sure you want to delete all remapped keys? This action cannot be undone. (yes/no)\n";std::string confirmation;std::cin >> confirmation;if (confirmation != "yes") {std::cout << "Action cancelled.\n";return;}// 删除注册表中的重映射按键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {LONG result = RegDeleteValue(hKey, REGISTRY_VALUE_NAME);if (result == ERROR_SUCCESS || result == ERROR_FILE_NOT_FOUND) {std::cout << "All remapped keys deleted successfully.\n";}else {std::cerr << "Failed to delete remapped keys.\n";}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key.\n";}
}void BackupScancodeMap() {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_WRITE | KEY_READ, &hKey) == ERROR_SUCCESS) {// 获取当前 Scancode Map 值的大小DWORD dataSize = 0;DWORD dataType;if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {// 获取当前时间作为备份文件名的一部分SYSTEMTIME sysTime;GetLocalTime(&sysTime);std::wstringstream backupKeyNameStream;backupKeyNameStream << BACKUP_KEY_PREFIX << std::setw(4) << std::setfill(L'0') << sysTime.wYear << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wMonth << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wDay << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wHour << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wMinute << L"_"<< std::setw(2) << std::setfill(L'0') << sysTime.wSecond;std::wstring backupKeyName = backupKeyNameStream.str();std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, REGISTRY_VALUE_NAME, NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 创建新的值项来存储备份数据LONG result = RegSetValueEx(hKey, backupKeyName.c_str(), 0, REG_BINARY, buffer.data(), dataSize);if (result == ERROR_SUCCESS) {std::wcout << L"Backup of Scancode Map value successful. Backup key name: " << backupKeyName << std::endl;// 询问用户是否删除原始键值项std::wcout << L"Do you want to delete the original registry key value? (Y/N): ";wchar_t response;std::wcin >> response;if (response == L'Y' || response == L'y') {if (RegDeleteValue(hKey, REGISTRY_VALUE_NAME) == ERROR_SUCCESS) {std::wcout << L"Original registry key value deleted successfully." << std::endl;}else {std::wcerr << L"Failed to delete original registry key value." << std::endl;}}else {std::wcout << L"Original registry key value not deleted." << std::endl;}}else {std::cerr << "Failed to write Scancode Map value to backup key: " << result << std::endl;}}else {std::cerr << "Failed to read registry value." << std::endl;}}else {std::cerr << "No Scancode Map value found in the registry." << std::endl;}// 关闭键RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 枚举并读取所有备份的 Scancode Map 值的键名称
void EnumerateAndPrintBackupKeys() {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {// 枚举所有值项的名称DWORD index = 0;WCHAR subKeyName[MAX_PATH];DWORD subKeyNameSize = MAX_PATH;while (RegEnumValue(hKey, index, subKeyName, &subKeyNameSize, NULL, NULL, NULL, NULL) == ERROR_SUCCESS) {// 检查值项的名称的前缀,如果是备份值项则输出时间信息if (wcsncmp(subKeyName, BACKUP_KEY_PREFIX, wcslen(BACKUP_KEY_PREFIX)) == 0) {std::wcout << L"Backup time: " << subKeyName << std::endl;}// 重置键名缓冲区大小subKeyNameSize = MAX_PATH;// 移动到下一个值项index++;}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 还原指定时间的 Scancode Map 值备份
void RestoreBackupScancodeMap(const std::wstring& backupTime) {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE | KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) {// 构造备份值项的名称std::wstring backupKeyName = BACKUP_KEY_PREFIX + backupTime;DWORD dataSize = 0;DWORD dataType;// 获取备份值项的大小if (RegQueryValueEx(hKey, backupKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {// 读取备份值项std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, backupKeyName.c_str(), NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 将备份值项的数据写入 Scancode Mapif (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), dataSize) == ERROR_SUCCESS) {std::wcout << L"Restored Scancode Map from backup created at: " << backupTime << std::endl;}else {std::cerr << "Failed to restore Scancode Map from backup." << std::endl;}}}else {std::cerr << "Backup key not found or not a valid REG_BINARY value." << std::endl;}RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 更新重映射信息到注册表
bool UpdateRemapInfo(const std::vector<std::pair<WORD, WORD>>& remapInfo) {std::vector<BYTE> buffer(20 + remapInfo.size() * 4, 0);*(DWORD*)(buffer.data() + 8) = remapInfo.size() + 1;for (size_t i = 0; i < remapInfo.size(); ++i) {*(WORD*)(buffer.data() + 12 + i * 4) = remapInfo[i].second;*(WORD*)(buffer.data() + 14 + i * 4) = remapInfo[i].first;}HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_SET_VALUE, &hKey) == ERROR_SUCCESS) {if (RegSetValueEx(hKey, REGISTRY_VALUE_NAME, 0, REG_BINARY, buffer.data(), buffer.size()) == ERROR_SUCCESS) {RegCloseKey(hKey);return true;}RegCloseKey(hKey);}return false;
}// 删除重映射信息中的指定项
void DeleteRemapItemByIndex(std::vector<std::pair<WORD, WORD>>& remapInfo) {// 枚举原有数据EnumerateRemappedKeys(remapInfo);std::cout << "Enter the numbers of items you want to delete (e.g., 1 2 3), separated by spaces: ";std::string input;std::getline(std::cin, input);std::istringstream iss(input);std::vector<int> deleteIndices;int index;while (iss >> index) {deleteIndices.push_back(index - 1); // 索引从1开始,转换为从0开始}std::vector<std::pair<WORD, WORD>> updatedRemapInfo;for (size_t i = 0; i < remapInfo.size(); ++i) {if (std::find(deleteIndices.begin(), deleteIndices.end(), i) == deleteIndices.end()) {updatedRemapInfo.push_back(remapInfo[i]);}}if (UpdateRemapInfo(updatedRemapInfo)) {std::cout << "Remapped items deleted successfully.\n";}else {std::cerr << "Failed to delete remapped items.\n";}
}// 添加重映射按键
void AddRemapItemByIndex(std::vector<std::pair<WORD, WORD>>& remapInfo,std::pair<WORD, WORD> NewMapNode, size_t dwIdToInsert) {// 参数范围校验if (dwIdToInsert < 1 || dwIdToInsert > remapInfo.size() ){std::cerr << "Error invalid parameters.\n";return;}// 检查是否有重复的映射for (const auto& pair : remapInfo) {if (pair.first == NewMapNode.first || pair.second == NewMapNode.second) {std::cerr << "Error Duplicate remapped item.\n";return;}}// 向 remapInfo 中索引为 dwIdToInsert 前面插入元素 NewMapNode// 索引为 dwIdToInsert-1 后面插入等同于在索引为 dwIdToInsert 前插入 auto it = remapInfo.begin() + dwIdToInsert;remapInfo.insert(it, NewMapNode);// 利用新的结构信息更新注册表if (UpdateRemapInfo(remapInfo)) {std::cout << "Remapped items add successfully.\n";}else {std::cerr << "Failed to insert remapped items.\n";}
}// 禁用 Remap 注册表项
void ChangeScancodeMapStatus(BOOL bEnable) {// 打开注册表键HKEY hKey;if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, REGISTRY_KEY, 0, KEY_WRITE | KEY_READ, &hKey) == ERROR_SUCCESS) {// 获取当前 Scancode Map 值的大小DWORD dataSize = 0;DWORD dataType;// 构造新的名称std::wstringstream disabledKeyNameStream;std::wstringstream checkKeyNameStream;disabledKeyNameStream << REGISTRY_VALUE_NAME << (bEnable ? L"" : DESABLED_KEY_PREFIX);std::wstring disabledKeyName = disabledKeyNameStream.str();checkKeyNameStream << REGISTRY_VALUE_NAME << (bEnable ? DESABLED_KEY_PREFIX : L"");std::wstring checkKeyName = checkKeyNameStream.str();if (RegQueryValueEx(hKey, checkKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::vector<BYTE> buffer(dataSize);if (RegQueryValueEx(hKey, checkKeyName.c_str(), NULL, NULL, buffer.data(), &dataSize) == ERROR_SUCCESS) {// 创建新的值项来存储备份数据LONG result = RegSetValueEx(hKey, disabledKeyName.c_str(), 0, REG_BINARY, buffer.data(), dataSize);if (result == ERROR_SUCCESS) {std::wcout << (bEnable ? L"Enable" : L"Disable")<< L" Scancode Map value successful. New key name: " << disabledKeyName << std::endl;// 删除原始键值项if (RegDeleteValue(hKey, checkKeyName.c_str()) == ERROR_SUCCESS) {std::wcout << L"Original registry key value deleted successfully." << std::endl;}else {std::wcerr << L"Failed to delete original registry key value." << std::endl;RegDeleteValue(hKey, disabledKeyName.c_str());}}else {std::cerr << "Failed to write Scancode Map value to disabled key: " << result << std::endl;}}else {std::cerr << "Failed to read registry value." << std::endl;}}else {if (RegQueryValueEx(hKey, disabledKeyName.c_str(), NULL, &dataType, NULL, &dataSize) == ERROR_SUCCESS && dataType == REG_BINARY) {std::wcout << L"Remapping keys were already "<< (bEnable ? L"enabled" : L"disabled")<< L" earlier." << std::endl;}else {std::cerr << "No Scancode Map value found in the registry." << std::endl;}}// 关闭键RegCloseKey(hKey);}else {std::cerr << "Failed to open registry key." << std::endl;}
}// 将 char* 类型的字符串转换为 std::wstring
std::wstring ConvertToWideString(const char* str) {int size = MultiByteToWideChar(CP_UTF8, 0, str, -1, NULL, 0);if (size == 0) {// 转换失败return L"";}std::wstring result(size, L'\0');MultiByteToWideChar(CP_UTF8, 0, str, -1, &result[0], size);return result;
}int main(int argc, char* argv[]) {if (argc < 2) {std::cerr << "Usage: " << argv[0] << " <option> [configFile]\n";return 1;}std::string option = argv[1];if (option == "/enumerate") { // 解析目前注册表中重映射键值的数据std::vector<std::pair<WORD, WORD>> remapInfo;if (ParseRemapInfo(remapInfo)) {EnumerateRemappedKeys(remapInfo);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/setremap") { // 向注册表中覆盖写入重映射键值的数据if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /setremap <configFilePath>\n";return 1;}std::string configFile = argv[2];ModifyRemappedKeys(configFile);}else if (option == "/queryActiveKey") { // 动态查询按键的扫描码QueryScanCodeForKey();}else if (option == "/deleteAllMap") // 删除所有重映射键数据{DeleteAllRemappedKeys();}else if (option == "/modifyDelKey") { // 删除指定的重映射键数据std::vector<std::pair<WORD, WORD>> remapInfo;if (ParseRemapInfo(remapInfo)) {DeleteRemapItemByIndex(remapInfo);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/modifyInsertKey") // 在指定的元素后面添加新的按键映射{std::vector<std::pair<WORD, WORD>> remapInfo;WORD oldScanCode = 0, newScanCode = 0;size_t index = 0;if (ParseRemapInfo(remapInfo)) { // 解析注册表参数// 枚举原有数据EnumerateRemappedKeys(remapInfo);// 选择插入的位置和数据std::cout << "Enter the Original Key scan code: ";std::cin >> std::hex >> oldScanCode;std::cout << "Enter the Key scan code you wannt to replace to: ";std::cin >> std::hex >> newScanCode;std::cout << "Enter the number of item you wannt to insert after: ";std::cin >> index;// 调整字节顺序oldScanCode = ((oldScanCode & 0xFF00) >> 8) | ((oldScanCode & 0x00FF) << 8);newScanCode = ((newScanCode & 0xFF00) >> 8) | ((newScanCode & 0x00FF) << 8);// 写入数据AddRemapItemByIndex(remapInfo,std::make_pair(oldScanCode, newScanCode), index);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/modifyAddKey") // 在映射表的末尾追加按键映射{std::vector<std::pair<WORD, WORD>> remapInfo;WORD oldScanCode = 0, newScanCode = 0;size_t index = 0;if (ParseRemapInfo(remapInfo)) { // 解析注册表参数// 选择插入的位置和数据std::cout << "Enter the Original Key scan code: ";std::cin >> std::hex >> oldScanCode;std::cout << "Enter the Key scan code you wannt to replace to: ";std::cin >> std::hex >> newScanCode;index = remapInfo.size(); // 在结尾插入// 调整字节顺序oldScanCode = ((oldScanCode & 0xFF00) >> 8) | ((oldScanCode & 0x00FF) << 8);newScanCode = ((newScanCode & 0xFF00) >> 8) | ((newScanCode & 0x00FF) << 8);// 写入数据AddRemapItemByIndex(remapInfo,std::make_pair(oldScanCode, newScanCode), index);}else {std::cerr << "Failed to parse remap info.\n";}}else if (option == "/statusMap") // 启用或者禁用重映射键(可恢复,1表示启用,0表示禁用){if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /statusMap <bEnable>\n";return 1;}if (std::string(argv[2]) == "1"){std::cout << "Try to enable ReMap Key.\n";ChangeScancodeMapStatus(TRUE);}else if (std::string(argv[2]) == "0"){std::cout << "Try to disable ReMap Key.\n";ChangeScancodeMapStatus(FALSE);}}else if (option == "/makebackup") { // 备份当前注册表重映射键数据BackupScancodeMap();}else if (option == "/recoverMap") { // 按照备份日期还原数据if (argc != 3) {std::cerr << "Usage: " << argv[0] << " /recoverMap <backupTime>\n";return 1;}std::wstring backupTime = ConvertToWideString(argv[2]);if (backupTime == L"")return 1;RestoreBackupScancodeMap(backupTime);}else if (option == "/readbackup") { // 读取备份文件列表EnumerateAndPrintBackupKeys();}else {std::cerr << "Invalid option.\n";return 1;}return 0;
}
程序需要管理员权限,设置清单文件即可:
测试截图如下:
3.2 C# 实现的窗口工具
Medo 制作的开源工具 Scancode Map 是一个不错的选择,它具有完备的查询、修改、删除等功能,我的程序一定程度上参考了他的思路。
这是该工具的界面视图:
原文链接为:Scancode Map 1.11 – Medo's Home Page。
网盘下载链接:https://pan.baidu.com/s/1Dxn8R3GEdol59ObYAMTLHw?pwd=vtq3
提取码:vtq3 (里面有我重新编译好的文件在 /source/bin 内,并且包含我写的控制台程序)
官网程序下载链接:https://www.medo64.com/download/scancodemap111.exe。
源代码 Github 链接:Release Most recent build · medo64/ScancodeMap · GitHub。
四、总结
本文简单介绍了使用 "Scancode Map" 键盘扫描码映射表禁用 CtrlAltDel 键的方法。浅谈了该方法的利弊。修改注册表屏蔽该按键的方法还有另外一个稳定的方法,就是利用 Disable 项隐藏相应的选项,这个方法也是以前讨论最多的方法,比按键扫描码用的还广泛,还有一个就是进程冻结法。但是都有缺点,比如可以注册表方法是可以轻易恢复的;进程冻结存在不稳定性,冻结后容易让进程陷入 RPC 死锁导致无法恢复。
【保留备用】
本文属于原创文章,转载请注明出处:
https://blog.csdn.net/qq_59075481/article/details/136104444。
发布于:2024.02.13,更新于:2024.02.13.