键盘重映射禁用 CtrlAltDel 键的利弊

目录

前言

一、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 Map
                if (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
Scancode Map 截图 2

原文链接为: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.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/385553.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PySQLRecon:一款功能强大的MSSQL安全测试工具

关于PySQLRecon PySQLRecon是一款功能强大的MSSQL安全测试工具&#xff0c;该工具基于SQLRecon实现其功能&#xff0c;可以帮助广大红队研究人员针对MSSQL执行攻击性安全测试。 环境配置 由于该工具基于Python 3开发&#xff0c;因此我们首先需要在本地设备上安装并配置好Pyt…

鸿蒙小案例-你画我猜

鸿蒙小案例-你画我猜 1.准备组件(组件布局) 2.实现跟随鼠标画笔画出图案功能 3.实现复制上面的画笔的图案功能 4.其他小功能1.组件的准备 画布的组件官方给的API是Canvas&#xff0c;需要传递一个参数CanvasRenderingContext2D 直接搜索API 使用官方案例 private settings: …

小技巧 | 如何将win11回退至win10

当你不小心点错将电脑的win10系统更新成win11系统了&#xff08;但没有完全更新&#xff09;&#xff0c;你会怎么做呢&#xff1f; 1.在“开始”菜单界面点击“设置”选项 2.点击“更新和安全”选项 3.在“更新历史记录”中点击“恢复选项” 4.点击“返回”选项 会弹出以下界面…

three.js 细一万倍教程 从入门到精通(二)

目录 三、全面认识three.js物体 3.1、掌握几何体顶点_UV_法向属性 3.2、BufferGeometry设置顶点创建矩形 3.3、生成酷炫三角形科技物体 四、详解材质与纹理 4.1、初识材质与纹理 4.2、详解纹理偏移_旋转_重复 偏移 旋转 重复 4.3、设置纹理显示算法与mipmap mapFil…

【动态规划】:泰波那契模型_解码方法

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本专栏是关于各种算法的解析&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入门到精通 数据结构专栏&…

【网工】华为设备命令学习(综合实验一)

实验要求和实验成果如图所示。 LSW2不需要其他配置&#xff0c;其下就一台设备&#xff0c;不需要区分。 LSW3配置如下&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]un in en //关闭系统提示信息 Info: Information …

LeetCode、72. 编辑距离【中等,二维DP】

文章目录 前言LeetCode、72. 编辑距离【中等&#xff0c;二维DP】题目链接与分类二维DP 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容…

VTK Python PyQt 监听键盘 控制 Actor 移动 变色

KeyPressInteractorStyle 在vtk 中有时我们需要监听 键盘或鼠标做一些事&#xff1b; 1. 创建 Actor&#xff1b; Sphere vtk.vtkSphereSource() Sphere.SetRadius(10)mapper vtk.vtkPolyDataMapper() mapper.SetInputConnection(Sphere.GetOutputPort()) actor vtk.vtkAc…

Vue核心基础6:Vue内置指令、自定义指令、生命周期

1 Vue中的内置指令 <script>const vm new Vue({el: #root,data: {n: 1,m: 100,name: Vue,str: <h3>你好</h3>}})</script> 1.1 v-text <div v-text"name"></div>1.2 v-html <div v-html"str"></div> …

Spring Boot 笔记 012 创建接口_添加文章分类

1.1.1 实体类添加校验 package com.geji.pojo;import jakarta.validation.constraints.NotEmpty; import lombok.Data;import java.time.LocalDateTime;Data public class Category {private Integer id;//主键IDNotEmptyprivate String categoryName;//分类名称NotEmptypriva…

中小学信息学奥赛CSP-J认证 CCF非专业级别软件能力认证-入门组初赛模拟题第一套(完善程序题)

CCF认证CSP-J入门组模拟测试题第一套 三、完善程序题 第一题 九宫格 请完善下面的程序,将1~9个数字分别填人3x3的九宫格中,第一行的三个数字组成一个三位数。要使第二行的三位数是第一行的2倍,第三行的三位数是第一行的3倍且每个格子里的数字都不能重复,现在要求输出所有的填…

python输出字符 2022年12月青少年电子学会等级考试 中小学生python编程等级考试二级真题答案解析

目录 python输出字符 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 七、 推荐资料 1、蓝桥杯比赛 2、考级资料 3、其它资料 python输出字符 2022年12月 python编程等级考试级编程题 一、题目要求 …

《CSS 简易速速上手小册》第2章:CSS 布局与定位(2024 最新版)

文章目录 2.1 Flexbox&#xff1a;灵活的布局解决方案2.1.1 基础知识2.1.2 重点案例&#xff1a;创建一个响应式导航菜单2.1.3 拓展案例 1&#xff1a;卡片布局2.1.4 拓展案例 2&#xff1a;中心对齐的登录表单 2.2 Grid 布局&#xff1a;网格系统的魔力2.2.1 基础知识2.2.2 重…

渗透测试练习题解析 3(CTF web)

1、[网鼎杯 2020 朱雀组]phpweb 1 考点&#xff1a;反序列化漏洞利用 进入靶场&#xff0c;查看检查信息&#xff0c;发现存在两个参数 func 和 p 查看页面源代码 payload&#xff1a;funcfile_get_contents&pphp://filter/resourceindex.php 整理后&#xff0c;就是 PHP 代…

数据结构与算法:单链表

朋友们大家好&#xff0c;本节来到数据结构与算法的新内容&#xff1a;单链表 在上篇文章中&#xff0c;我们知道顺序表通常需要预分配一个固定大小的内存空间&#xff0c; 通常以二倍的大小进行增容&#xff0c;可能会造成空间的浪费&#xff0c;本篇文章我们介绍的链表可以解…

去除vue自带的边距

使用vue时发现总有去不掉的外边距&#xff0c;在index.vue里面怎样设置样式都不管用 查阅资料后发现要在vue项目自带的index.html文件内添加下面的样式代码才行 <style>*{margin: 0;padding: 0;}body,html{margin: 0;padding: 0;} </style>

二、DataX安装

DataX安装 一、简介二、系统要求三、部署 一、简介 官方地址&#xff1a;https://github.com/alibaba/DataX/blob/master/userGuid.md 二、系统要求 LinuxJDK(1.8以上&#xff0c;推荐1.8) Centos7.9的java1.8安装命令&#xff1a;yum install java-1.8.0-openjdk.x86_64 Py…

【新书推荐】7.6节 相对基址加变址寻址方式

本节内容&#xff1a;基址加变址寻址方式。 ■基址加变址寻址方式&#xff1a;指令操作数为内操作数&#xff0c;操作数地址使用[基址寄存器变址寄存器]表示。 7.6.1 基址加变址寻址方式 基址加变址寻址方式的操作数在存储器中&#xff0c;操作数的有效地址由基地址寄存器&am…

day 20(补2.5)

fread 函数&#xff1a; 今日练习 C语言面试题5道~ 1. static 有什么用途&#xff1f;&#xff08;请至少说明两种&#xff09; 1) 限制变量的作用域 2) 设置变量的存储域 2. 引用与指针有什么区别&#xff1f; 1) 引用必须被初始化&#xff0c;指针不必。 2) 引用初始…

幻兽帕鲁服务器原来的存档不想玩了,怎么清档?如何重来?

如果需要备份原存档的话&#xff0c;就先把存档导出来备份。或者手动去服务器文件里找到游戏存档文件夹&#xff0c;保存下载。 如无需备份原存档&#xff0c;则可以直接使用幻兽帕鲁应用模板&#xff0c;来重装服务器的操作系统。 方法很简单&#xff1a; 详细教程地…