Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea

This commit is contained in:
yuanyuanxiang
2026-04-19 19:55:01 +02:00
commit 5a325a202b
744 changed files with 235562 additions and 0 deletions

View File

@@ -0,0 +1,562 @@
# 下级功能限制方案设计
## 一、背景
当前的 UI 定制化UIBranding.h是编译时配置适用于开发商自己定制品牌。但在生成下级主控时上级可能希望限制下级可用的功能如禁止下级使用"生成Master"功能),这需要运行时配置机制。
### 需求场景
```
超级管理员(完整功能)
├─→ 生成主控A禁用钱包、生成Master
│ │
│ └─→ 主控A的下级继承禁用 + 可能更多限制)
└─→ 生成主控B禁用授权管理、ShellCode
```
### 设计目标
1. 生成主控时可选择禁用哪些功能
2. 功能限制固化到生成的程序中
3. 下级只能继承限制,不能解除
4. 与现有 UIBranding 编译时配置兼容
---
## 二、技术方案
### 2.1 存储位置
复用现有的 `g_UpperHash` 数组未使用空间,无需新增标记区域。
```
g_UpperHash[260] 当前布局:
├─ [0-99] MASTER_HASH_STR 标记(用于搜索定位)
├─ [100-163] 64字节上级哈希
└─ [164-259] 未使用96字节 ← 用于存储功能标志
```
### 2.2 扩展布局
```
g_UpperHash[260] 扩展布局:
├─ [0-99] MASTER_HASH_STR 标记
├─ [100-163] 64字节上级哈希
├─ [164-167] 版本标识 "FLG1"
├─ [168-175] MenuFlags 主菜单位图 (64位支持64个菜单项)
├─ [176-183] ToolbarFlags 工具栏位图 (64位)
├─ [184-191] ContextFlags 右键菜单位图 (64位)
└─ [192-259] 预留 (68字节)
```
### 2.3 向后兼容
| 场景 | [164-167] 值 | 行为 |
|------|-------------|------|
| 旧版本程序 | 全零 | 无运行时限制,仅编译时配置生效 |
| 新版本 + 无限制 | "FLG1" + 全零位图 | 所有功能可用 |
| 新版本 + 有限制 | "FLG1" + 非零位图 | 按位图隐藏功能 |
---
## 三、数据结构
### 3.1 功能标志结构体
```cpp
// FeatureFlags.h
#pragma once
#include <stdint.h>
#define FEATURE_FLAGS_VERSION "FLG1"
#define FEATURE_FLAGS_OFFSET 164 // g_UpperHash 中的偏移
#pragma pack(push, 1)
typedef struct FeatureFlags {
char Version[4]; // "FLG1" 版本标识
uint64_t MenuFlags; // 主菜单位图 (64位支持64个菜单项)
uint64_t ToolbarFlags; // 工具栏位图 (64位)
uint64_t ContextFlags; // 右键菜单位图 (64位)
char Reserved[68]; // 预留扩展
} FeatureFlags; // 4 + 8 + 8 + 8 + 68 = 96 bytes
#pragma pack(pop)
```
### 3.2 位图定义(完整映射表)
位图定义与 UIBranding.h 中的宏一一对应,便于实施时查找。
```cpp
// ===== MenuFlags 位定义 (uint64_t) =====
// 与 UIBranding.h 中 HIDE_MENU_* 宏对应
// 文件菜单 [0-3]
#define MF_SETTINGS (1ULL << 0) // HIDE_MENU_SETTINGS
#define MF_NOTIFY_SETTINGS (1ULL << 1) // HIDE_MENU_NOTIFY_SETTINGS
#define MF_WALLET (1ULL << 2) // HIDE_MENU_WALLET
#define MF_NETWORK (1ULL << 3) // HIDE_MENU_NETWORK
// 工具菜单 [4-10]
#define MF_INPUT_PASSWORD (1ULL << 4) // HIDE_MENU_INPUT_PASSWORD
#define MF_IMPORT_LICENSE (1ULL << 5) // HIDE_MENU_IMPORT_LICENSE
#define MF_RCEDIT (1ULL << 6) // HIDE_MENU_RCEDIT
#define MF_GEN_AUTH (1ULL << 7) // HIDE_MENU_GEN_AUTH
#define MF_GEN_MASTER (1ULL << 8) // HIDE_MENU_GEN_MASTER
#define MF_LICENSE_MGR (1ULL << 9) // HIDE_MENU_LICENSE_MGR
#define MF_V2_PRIVATEKEY (1ULL << 10) // HIDE_MENU_V2_PRIVATEKEY
// ShellCode子菜单 [11-19]
#define MF_SHELLCODE_C (1ULL << 11) // HIDE_MENU_SHELLCODE_C
#define MF_SHELLCODE_BIN (1ULL << 12) // HIDE_MENU_SHELLCODE_BIN
#define MF_SHELLCODE_LOAD_TEST (1ULL << 13) // HIDE_MENU_SHELLCODE_LOAD_TEST
#define MF_SHELLCODE_OBFS (1ULL << 14) // HIDE_MENU_SHELLCODE_OBFS
#define MF_SHELLCODE_OBFS_BIN (1ULL << 15) // HIDE_MENU_SHELLCODE_OBFS_BIN
#define MF_SHELLCODE_OBFS_TEST (1ULL << 16) // HIDE_MENU_SHELLCODE_OBFS_TEST
#define MF_SHELLCODE_AES_C (1ULL << 17) // HIDE_MENU_SHELLCODE_AES_C
#define MF_SHELLCODE_AES_BIN (1ULL << 18) // HIDE_MENU_SHELLCODE_AES_BIN
#define MF_SHELLCODE_AES_TEST (1ULL << 19) // HIDE_MENU_SHELLCODE_AES_TEST
// 参数菜单 [20-27]
#define MF_KBLOGGER (1ULL << 20) // HIDE_MENU_KBLOGGER
#define MF_LOGIN_NOTIFY (1ULL << 21) // HIDE_MENU_LOGIN_NOTIFY
#define MF_ENABLE_LOG (1ULL << 22) // HIDE_MENU_ENABLE_LOG
#define MF_PRIVACY_WALLPAPER (1ULL << 23) // HIDE_MENU_PRIVACY_WALLPAPER
#define MF_FILE_V2 (1ULL << 24) // HIDE_MENU_FILE_V2
#define MF_HOOK_WIN (1ULL << 25) // HIDE_MENU_HOOK_WIN
#define MF_RUN_AS_SERVICE (1ULL << 26) // HIDE_MENU_RUN_AS_SERVICE
#define MF_RUN_AS_USER (1ULL << 27) // HIDE_MENU_RUN_AS_USER
// 扩展菜单 [28-37]
#define MF_HISTORY_CLIENTS (1ULL << 28) // HIDE_MENU_HISTORY_CLIENTS
#define MF_BACKUP_DATA (1ULL << 29) // HIDE_MENU_BACKUP_DATA
#define MF_IMPORT_DATA (1ULL << 30) // HIDE_MENU_IMPORT_DATA
#define MF_RELOAD_PLUGINS (1ULL << 31) // HIDE_MENU_RELOAD_PLUGINS
#define MF_PLUGIN_REQUEST (1ULL << 32) // HIDE_MENU_PLUGIN_REQUEST
#define MF_FRPS_FOR_SUB (1ULL << 33) // HIDE_MENU_FRPS_FOR_SUB
#define MF_CHANGE_LANG (1ULL << 34) // HIDE_MENU_CHANGE_LANG
#define MF_CHOOSE_LANG_DIR (1ULL << 35) // HIDE_MENU_CHOOSE_LANG_DIR
#define MF_LOCATION_QQWRY (1ULL << 36) // HIDE_MENU_LOCATION_QQWRY
#define MF_LOCATION_IP2REGION (1ULL << 37) // HIDE_MENU_LOCATION_IP2REGION
// 帮助菜单 [38-42]
#define MF_IMPORTANT (1ULL << 38) // HIDE_MENU_IMPORTANT
#define MF_FEEDBACK (1ULL << 39) // HIDE_MENU_FEEDBACK
#define MF_WHAT_IS_THIS (1ULL << 40) // HIDE_MENU_WHAT_IS_THIS
#define MF_MASTER_TRAIL (1ULL << 41) // HIDE_MENU_MASTER_TRAIL
#define MF_REQUEST_AUTH (1ULL << 42) // HIDE_MENU_REQUEST_AUTH
// [43-63] 预留
// ===== ToolbarFlags 位定义 (uint64_t) =====
// 与 UIBranding.h 中 HIDE_TOOLBAR_* 宏对应,按索引顺序
#define TF_TERMINAL (1ULL << 0) // HIDE_TOOLBAR_TERMINAL
#define TF_PROCESS (1ULL << 1) // HIDE_TOOLBAR_PROCESS
#define TF_WINDOW (1ULL << 2) // HIDE_TOOLBAR_WINDOW
#define TF_DESKTOP (1ULL << 3) // HIDE_TOOLBAR_DESKTOP
#define TF_FILE (1ULL << 4) // HIDE_TOOLBAR_FILE
#define TF_AUDIO (1ULL << 5) // HIDE_TOOLBAR_AUDIO
#define TF_VIDEO (1ULL << 6) // HIDE_TOOLBAR_VIDEO
#define TF_SERVICE (1ULL << 7) // HIDE_TOOLBAR_SERVICE
#define TF_REGISTER (1ULL << 8) // HIDE_TOOLBAR_REGISTER
#define TF_KEYBOARD (1ULL << 9) // HIDE_TOOLBAR_KEYBOARD
#define TF_SETTINGS (1ULL << 10) // HIDE_TOOLBAR_SETTINGS
#define TF_BUILD (1ULL << 11) // HIDE_TOOLBAR_BUILD
#define TF_SEARCH (1ULL << 12) // HIDE_TOOLBAR_SEARCH
#define TF_HELP (1ULL << 13) // HIDE_TOOLBAR_HELP
// [14-63] 预留
// ===== ContextFlags 位定义 (uint64_t) =====
// 与 UIBranding.h 中 HIDE_CTX_* 宏对应
// 在线列表右键菜单 [0-16]
#define CF_MESSAGE (1ULL << 0) // HIDE_CTX_MESSAGE
#define CF_UPDATE (1ULL << 1) // HIDE_CTX_UPDATE
#define CF_DELETE (1ULL << 2) // HIDE_CTX_DELETE
#define CF_SHARE (1ULL << 3) // HIDE_CTX_SHARE
#define CF_PROXY (1ULL << 4) // HIDE_CTX_PROXY
#define CF_HOSTNOTE (1ULL << 5) // HIDE_CTX_HOSTNOTE
#define CF_VIRTUAL_DESKTOP (1ULL << 6) // HIDE_CTX_VIRTUAL_DESKTOP
#define CF_GRAY_DESKTOP (1ULL << 7) // HIDE_CTX_GRAY_DESKTOP
#define CF_REMOTE_DESKTOP (1ULL << 8) // HIDE_CTX_REMOTE_DESKTOP
#define CF_H264_DESKTOP (1ULL << 9) // HIDE_CTX_H264_DESKTOP
#define CF_AUTHORIZE (1ULL << 10) // HIDE_CTX_AUTHORIZE
#define CF_UNAUTHORIZE (1ULL << 11) // HIDE_CTX_UNAUTHORIZE
#define CF_ASSIGN_TO (1ULL << 12) // HIDE_CTX_ASSIGN_TO
#define CF_ADD_WATCH (1ULL << 13) // HIDE_CTX_ADD_WATCH
#define CF_LOGIN_NOTIFY (1ULL << 14) // HIDE_CTX_LOGIN_NOTIFY
#define CF_RUN_AS_ADMIN (1ULL << 15) // HIDE_CTX_RUN_AS_ADMIN
#define CF_UNINSTALL (1ULL << 16) // HIDE_CTX_UNINSTALL
#define CF_PRIVATE_SCREEN (1ULL << 17) // HIDE_CTX_PRIVATE_SCREEN
#define CF_REGROUP (1ULL << 18) // HIDE_CTX_REGROUP
#define CF_INJ_NOTEPAD (1ULL << 19) // HIDE_CTX_INJ_NOTEPAD
#define CF_PROXY_PORT (1ULL << 20) // HIDE_CTX_PROXY_PORT
#define CF_PROXY_PORT_STD (1ULL << 21) // HIDE_CTX_PROXY_PORT_STD
// 机器管理子菜单 [22-24]
#define CF_MACHINE_SHUTDOWN (1ULL << 22) // HIDE_CTX_MACHINE_SHUTDOWN
#define CF_MACHINE_REBOOT (1ULL << 23) // HIDE_CTX_MACHINE_REBOOT
#define CF_MACHINE_LOGOUT (1ULL << 24) // HIDE_CTX_MACHINE_LOGOUT
// 执行命令子菜单 [25-28]
#define CF_EXECUTE_DOWNLOAD (1ULL << 25) // HIDE_CTX_EXECUTE_DOWNLOAD
#define CF_EXECUTE_UPLOAD (1ULL << 26) // HIDE_CTX_EXECUTE_UPLOAD
#define CF_EXECUTE_TESTRUN (1ULL << 27) // HIDE_CTX_EXECUTE_TESTRUN
#define CF_EXECUTE_GHOST (1ULL << 28) // HIDE_CTX_EXECUTE_GHOST
// [29-63] 预留
```
### 3.3 访问接口
```cpp
// 获取功能标志(返回 nullptr 表示无限制)
inline const FeatureFlags* GetFeatureFlags() {
extern char g_UpperHash[];
const char* ptr = g_UpperHash + FEATURE_FLAGS_OFFSET;
if (memcmp(ptr, FEATURE_FLAGS_VERSION, 4) != 0)
return nullptr;
return (const FeatureFlags*)ptr;
}
// 检查菜单项是否被运行时禁用
inline bool IsMenuDisabled(uint64_t flag) {
const FeatureFlags* ff = GetFeatureFlags();
return ff && (ff->MenuFlags & flag);
}
// 检查工具栏按钮是否被运行时禁用
inline bool IsToolbarDisabled(uint64_t flag) {
const FeatureFlags* ff = GetFeatureFlags();
return ff && (ff->ToolbarFlags & flag);
}
// 检查右键菜单项是否被运行时禁用
inline bool IsContextDisabled(uint64_t flag) {
const FeatureFlags* ff = GetFeatureFlags();
return ff && (ff->ContextFlags & flag);
}
```
---
## 四、UI 设计
### 4.1 控件方案
由于功能项较多43+14+24=81项推荐使用 **Tab + CCheckListBox** 方案:
```
┌─ 下级功能限制 ────────────────────────────────────────┐
│ │
│ ┌──────────┬──────────┬──────────┐ │
│ │ 主菜单 │ 工具栏 │ 右键菜单 │ │
│ └──────────┴──────────┴──────────┘ │
│ ┌──────────────────────────────────────────────┐ │
│ │ ☑ 钱包 │ ▲ │
│ │ ☑ 生成Master │ █ │
│ │ ☑ 许可证管理 │ █ │
│ │ ☑ V2私钥 │ █ │
│ │ ☑ 生成授权 │ █ │
│ │ ☐ PE资源编辑 │ █ │
│ │ ☐ 备份数据 │ █ │
│ │ ☐ 导入数据 │ ▼ │
│ └──────────────────────────────────────────────┘ │
│ │
│ 勾选 = 对下级隐藏 ※灰色项为上级已禁用 │
│ │
│ [ 确定 ] [ 取消 ] │
└────────────────────────────────────────────────────────┘
```
### 4.2 控件说明
| 控件 | 类型 | 说明 |
|------|------|------|
| Tab控件 | CTabCtrl | 切换三类功能:主菜单/工具栏/右键菜单 |
| 列表控件 | CCheckListBox | 显示当前Tab的功能项支持勾选 |
### 4.3 列表项状态
| 状态 | 显示 | 说明 |
|------|------|------|
| 可用 | □ 正常文字 | 可自由勾选/取消 |
| 已继承 | ☑ 灰色文字 | 上级已禁用,不可取消 |
| 已编译时隐藏 | 不显示 | UIBranding.h 中已设为1无需再配置 |
### 4.4 交互逻辑
**默认状态**
- 所有功能默认启用(复选框不勾选 = 下级可用)
- 上级已限制的功能显示为灰色勾选(不可取消)
**操作流程**
1. 用户勾选要对下级**禁用**的功能
2. 点击确定,继续生成流程
---
## 五、实现步骤
### 5.1 新增文件
| 文件 | 说明 |
|------|------|
| `FeatureFlags.h` | 数据结构、位图定义、访问接口 |
| `FeatureLimitsDlg.h/cpp` | 功能限制对话框Tab + CCheckListBox |
| `IDD_FEATURE_LIMITS` | 对话框资源(.rc 文件中添加) |
### 5.2 修改文件
| 文件 | 修改内容 |
|------|---------|
| `2015RemoteDlg.cpp` | OnToolGenMaster 集成对话框和写入逻辑 |
| `2015RemoteDlg.cpp` | CreateSolidMenu 增加运行时检查 |
| `2015RemoteDlg.cpp` | CreateToolBar 增加运行时检查 |
| `2015RemoteDlg.cpp` | OnNMRClickOnline 增加运行时检查 |
| `generated_hash.h` | 模板更新,包含功能标志 |
### 5.3 开发顺序
```
Phase 1: 数据结构
├── 定义 FeatureFlags 结构体
├── 定义位图常量
└── 实现 GetFeatureFlags() 等接口
Phase 2: 写入逻辑
├── 修改 OnToolGenMaster
├── 在 g_UpperHash[164] 写入功能标志
└── 更新 GenerateHashHeaderFile
Phase 3: 读取逻辑
├── CreateSolidMenu 增加运行时检查
├── CreateToolBar 增加运行时检查
└── OnNMRClickOnline 增加运行时检查
Phase 4: UI 对话框
├── 创建对话框资源
└── 实现 CFeatureLimitsDlg
```
---
## 六、对话框初始化逻辑
### 6.1 列表项生成
对话框初始化时,需要根据编译时配置和继承限制来决定显示哪些项:
```cpp
void CFeatureLimitsDlg::InitMenuList()
{
// 定义菜单项配置表
struct MenuItem {
const char* name; // 显示名称
uint64_t flag; // 运行时标志位
int compileMacro; // 编译时宏值 (0或1)
};
static const MenuItem items[] = {
{ "钱包", MF_WALLET, HIDE_MENU_WALLET },
{ "生成Master", MF_GEN_MASTER, HIDE_MENU_GEN_MASTER },
{ "许可证管理", MF_LICENSE_MGR, HIDE_MENU_LICENSE_MGR },
// ... 其他项
};
const FeatureFlags* inherited = GetFeatureFlags();
for (const auto& item : items) {
// 编译时已隐藏的功能,不显示(下级根本没有这个功能)
if (item.compileMacro)
continue;
int index = m_ListMenu.AddString(item.name);
// 上级已禁用的功能,显示为勾选+禁用状态
// 使用高位标记继承状态(点击时检查)
if (inherited && (inherited->MenuFlags & item.flag)) {
m_ListMenu.SetCheck(index, TRUE);
m_ListMenu.SetItemData(index, item.flag | 0x8000000000000000ULL); // 高位=继承
} else {
m_ListMenu.SetItemData(index, item.flag);
}
}
}
// 在 OnCheckChange 中阻止取消继承项
void CFeatureLimitsDlg::OnCheckChange()
{
int index = m_ListMenu.GetCaretIndex();
if (index < 0) return;
uint64_t data = m_ListMenu.GetItemData(index);
if (data & 0x8000000000000000ULL) {
// 继承项不可取消,强制保持勾选
m_ListMenu.SetCheck(index, TRUE);
}
}
```
### 6.2 收集用户选择
```cpp
void CFeatureLimitsDlg::OnOK()
{
m_MenuFlags = 0;
// 收集所有勾选项的标志位
for (int i = 0; i < m_ListMenu.GetCount(); i++) {
if (m_ListMenu.GetCheck(i)) {
m_MenuFlags |= m_ListMenu.GetItemData(i);
}
}
// 工具栏和右键菜单同理...
CDialogEx::OnOK();
}
```
---
## 七、运行时检查逻辑
### 7.1 菜单隐藏(双重检查)
```cpp
// CreateSolidMenu 中
void CMy2015RemoteDlg::CreateSolidMenu()
{
// ... 现有代码 ...
// 编译时配置UIBranding.h
#if HIDE_MENU_WALLET
pMenu->DeleteMenu(ID_MAIN_WALLET, MF_BYCOMMAND);
#else
// 运行时配置(上级限制)
if (IsMenuDisabled(MF_WALLET))
pMenu->DeleteMenu(ID_MAIN_WALLET, MF_BYCOMMAND);
#endif
// 或者统一写法:
if (HIDE_MENU_WALLET || IsMenuDisabled(MF_WALLET))
pMenu->DeleteMenu(ID_MAIN_WALLET, MF_BYCOMMAND);
}
```
### 7.2 宏简化
```cpp
// 简化宏:编译时 OR 运行时
#define SHOULD_HIDE_MENU(compile_flag, runtime_flag) \
((compile_flag) || IsMenuDisabled(runtime_flag))
#define SHOULD_HIDE_TOOLBAR(compile_flag, runtime_flag) \
((compile_flag) || IsToolbarDisabled(runtime_flag))
#define SHOULD_HIDE_CTX(compile_flag, runtime_flag) \
((compile_flag) || IsContextDisabled(runtime_flag))
// 使用示例
if (SHOULD_HIDE_MENU(HIDE_MENU_WALLET, MF_WALLET))
pMenu->DeleteMenu(ID_MAIN_WALLET, MF_BYCOMMAND);
```
---
## 八、生成流程修改
### 8.1 OnToolGenMaster 修改
```cpp
void CMy2015RemoteDlg::OnToolGenMaster()
{
// ... 现有的密码、天数、深度输入 ...
// === 新增:功能限制对话框 ===
CFeatureLimitsDlg limitsDlg(this);
// 传入当前程序的限制(用于继承显示)
const FeatureFlags* inherited = GetFeatureFlags();
if (inherited) {
limitsDlg.SetInheritedFlags(inherited);
}
if (limitsDlg.DoModal() != IDOK)
return;
// ... 现有的文件读取逻辑 ...
// 现有代码:搜索 MASTER_HASH_STR 标记(用于写入 g_UpperHash
char str[100] = {}, markArr[] = { MASTER_HASH_STR };
memcpy(str, markArr, sizeof(markArr));
int upperHashOffset = MemoryFind(curEXE, str, size, sizeof(str));
if (upperHashOffset == -1) {
AfxMessageBox(_T("找不到标记位置,无法生成下级主控"));
delete[] curEXE;
return;
}
{
// 现有:写入上级哈希 [100-163]
memcpy(curEXE + upperHashOffset + 100, masterHash.c_str(), 64);
// === 新增:写入功能标志 [164-191] ===
FeatureFlags flags = {};
memcpy(flags.Version, FEATURE_FLAGS_VERSION, 4);
flags.MenuFlags = limitsDlg.m_MenuFlags;
flags.ToolbarFlags = limitsDlg.m_ToolbarFlags;
flags.ContextFlags = limitsDlg.m_ContextFlags;
memcpy(curEXE + upperHashOffset + FEATURE_FLAGS_OFFSET, &flags, sizeof(flags));
}
// ... 现有的保存和压缩逻辑 ...
}
```
**关键点**:功能标志写入到 `g_UpperHash` 搜索结果的偏移 164 处,与上级哈希(偏移 100使用同一个搜索结果。
### 8.2 GenerateHashHeaderFile 更新
```cpp
// 输出功能标志注释(便于下级开发商了解限制)
headerFile << "// 功能限制标志 (位于 g_UpperHash[164-191])\n";
if (flags.MenuFlags || flags.ToolbarFlags || flags.ContextFlags) {
headerFile << "// MenuFlags: 0x" << std::hex << std::setfill('0')
<< std::setw(16) << flags.MenuFlags << "\n";
headerFile << "// ToolbarFlags: 0x" << std::setw(16) << flags.ToolbarFlags << "\n";
headerFile << "// ContextFlags: 0x" << std::setw(16) << flags.ContextFlags
<< std::dec << "\n";
}
```
---
## 九、与 UIBranding 的关系
| 配置类型 | 作用对象 | 配置时机 | 可修改性 |
|---------|---------|---------|---------|
| UIBranding.h | 开发商自己 | 编译时 | 需重新编译 |
| FeatureFlags | 下级程序 | 生成时 | 固化到程序 |
**优先级**:编译时配置 > 运行时配置
如果编译时已隐藏某功能,运行时配置无效(已经没有该功能)。运行时配置只能在编译时未隐藏的功能中进一步限制。
---
## 十、测试要点
1. **向后兼容**:旧版本程序(无 FLG1 标记)正常运行
2. **继承正确**:下级无法解除上级的限制
3. **位图准确**:每个功能对应正确的位
4. **UI 响应**:勾选/取消正确反映到位图
5. **双重检查**:编译时和运行时配置均生效