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

614
docs/AgentManual.md Normal file
View File

@@ -0,0 +1,614 @@
# YAMA 代理商运营手册
> 下级授权管理与 FRP 代理配置
---
## 目标读者
- 有下级客户需要管理的代理商
- 需要为下级生成授权
- 需要提供 FRP 代理服务
> 基础功能请参阅[日常使用手册](UserManual.md)
---
## 第一部分:代理商角色
### 1. 您的角色定位
作为代理商,您在授权链中扮演中间角色:
```
您的上级(为您提供授权)
您(代理商)
您的下级(您为他们提供授权和服务)
```
**您的职责:**
- 管理您的下级客户
- 为下级生成和分发授权
- 可选:为下级提供 FRP 代理服务
- 提供技术支持和售后服务
### 2. 权限范围
您为下级授权时,受以下限制:
| 限制项 | 规则 |
|--------|------|
| 有效期 | 不超过您自己的有效期 |
| 并发数限制 | 不超过上级为您设置的并发数限制 |
| 功能权限 | 不超过您被授予的权限 |
**示例:**
- 您的有效期到 2026-12-31
- 您可以为下级设置最长到 2026-12-31 的有效期
- 上级为您设置的并发数限制是 500
- 您为下级设置的并发数限制不能超过 500
### 3. 收益模式参考
常见的代理商收益模式:
| 模式 | 说明 |
|------|------|
| 按授权收费 | 每个授权收取一次性费用 |
| 按并发数收费 | 根据分配的并发数定价 |
| 按服务期限收费 | 月付/季付/年付不同价格 |
| FRP 增值服务 | 为无服务器的下级提供 FRP 代理 |
| 技术支持服务 | 提供部署、运维支持 |
---
## 第二部分:下级管理流程
### 4. 发展下级的标准流程
```
步骤 1下级联系您表达需求
步骤 2下级下载并运行 YAMA.exe
步骤 3下级发送序列号给您
步骤 4您评估需求生成授权
步骤 5您将 *.lic 文件发送给下级
步骤 6下级导入授权开始使用
```
### 5. 获取下级序列号
#### 5.1 指导下级获取序列号
指导下级完成以下步骤:
1. 下载并运行 YAMA.exe首次运行会提示未授权属正常现象
2. 点击菜单 **其他****申请授权**
3. 首次会显示使用条款,确认本软件仅限合法正当使用
4. 点击"确认"后显示序列号并复制
> **提示**:导入授权后,该菜单会变为 **其他** → **序列号**
**序列号格式:** `XXXX-XXXX-XXXX-XXXX`16 位十六进制,分 4 组)
#### 5.2 序列号传递建议
| 方式 | 建议 |
|------|------|
| 微信/QQ | 直接复制文本发送 |
| 邮件 | 主题注明"YAMA序列号" |
| 截图 | 确保序列号完整清晰 |
**重要**:序列号是生成授权的必要信息,请下级准确提供。
### 6. 评估下级需求
在生成授权前,了解下级的需求:
| 问题 | 影响 |
|------|------|
| 预计受管端数量 | 决定分配的并发数 |
| 计划使用期限 | 决定授权有效期 |
| 有无公网服务器 | 决定是否需要分配 FRP |
---
## 第三部分:生成授权
### 7. 打开授权生成界面
点击菜单 **工具****口令生成**
### 8. 填写授权信息
#### 8.1 基本信息
| 字段 | 说明 | 示例 |
|------|------|------|
| 序列号 | 下级提供的设备序列号 | b40f-638f-ebc8-6d54 |
| 备注 | 便于识别的说明 | "张三-华东区代理" |
**备注建议格式:** `客户名-地区/用途`
#### 8.2 有效期设置
| 字段 | 说明 |
|------|------|
| 起始日期 | 授权生效日期(可以是今天或将来某天) |
| 结束日期 | 授权过期日期(不超过您的有效期) |
**常见期限类型:**
| 类型 | 期限 | 适用场景 |
|------|------|---------|
| 试用期 | 7-15 天 | 新客户体验 |
| 月付 | 1 个月 | 短期需求 |
| 季付 | 3 个月 | 一般客户 |
| 半年付 | 6 个月 | 稳定客户 |
| 年付 | 12 个月 | 长期合作 |
#### 8.3 并发数设置
设置下级可同时管理的受管端数量上限。
**规划建议:**
- 小型客户10-50
- 中型客户50-200
- 大型客户200+
- 预留 10-20% 余量应对增长
### 9. 分配 FRP 端口
#### 9.1 何时需要分配 FRP
在以下情况为下级分配 FRP
- 下级没有公网服务器
- 下级希望简化网络配置
- 下级网络环境复杂
#### 9.2 分配方式
**自动分配(推荐):**
1. 勾选"FRP 代理"选项
2. 系统自动从可用端口池分配
3. 无需手动指定端口号
**手动指定:**
1. 勾选"FRP 代理"选项
2. 取消"自动分配"
3. 输入指定端口号
**注意**:手动指定需确保端口在您配置的范围内且未被占用。
#### 9.3 记录分配信息
建议维护一份分配记录表:
| 下级 | 序列号 | 端口 | 并发数 | 有效期 | 备注 |
|------|-------|------|--------|--------|------|
| 张三 | b40f-... | 20001 | 100 | 2026-12-31 | 华东区 |
| 李四 | a12c-... | 20002 | 50 | 2026-06-30 | 试用 |
### 10. 生成授权
1. 确认所有信息无误
2. 点击"生成"按钮
3. 授权信息保存到本地数据库
### 11. 发送给下级
有两种方式将授权发送给下级:
#### 方式 A在线发送推荐
如果下级使用您通过 **工具****主控生成** 分发的程序:
1. 下级程序已硬编码您的地址,启动后会自动连接到您
2. 您在主机列表中可以看到已连接但未授权的下级
3. 右键点击该主机 → **发送授权**
4. 选择已生成的授权记录
5. 通知下级重启程序
**下级重启流程(如授权包含 FRP**
- 第一次重启:验证授权,提示"授权成功",同时收到 FRP 配置
- 第二次重启:应用 FRP 配置,开始使用 FRP 代理
**优点:** 无需传输文件,操作简便,适合大量下级管理
#### 方式 B离线发送lic 文件)
如果下级无法先连接到您(如网络原因):
1. 在授权生成界面点击"导出"按钮
2. 生成 `*.lic` 文件(建议命名:`客户名_日期.lic`
3. 通过微信/QQ/邮件发送给下级
4. 附带导入说明:
```
请按以下步骤导入授权:
1. 运行 YAMA.exe
2. 点击菜单"工具" → "导入口令..."
3. 选择 *.lic 文件
4. 重启程序使授权生效
如有问题请联系我。
```
---
## 第四部分FRP 代理服务
> 为没有公网服务器的下级提供代理服务
### 12. FRP 工作原理
当下级没有公网服务器时,可以通过您的 FRP 服务实现连接:
```
下级的受管端
│ 连接
您的 FRP 服务器(公网)
│ 转发
下级的 YAMA 程序
```
**数据流向:**
1. 受管端连接您的服务器的指定端口(如 20001
2. FRP 服务将流量转发给下级的 YAMA
3. 下级 YAMA 通过 FRP 客户端接收数据
### 13. 配置 FRP 服务
#### 13.1 系统要求
| 要求 | 说明 |
|------|------|
| 操作系统 | Windows 10 / Server 2016 或更高版本 |
| 架构 | 必须是 64 位系统 |
| 网络 | 服务器具有公网 IP |
**注意**32 位系统或 Windows 7/Server 2012 等旧系统不支持 FRP 功能。
#### 13.2 打开配置界面
点击菜单 **扩展****下级FRP代理设置**
#### 13.3 启用服务
勾选 **"启用为下级提供 FRP 代理"**
#### 13.4 选择 FRPS 模式
**方式 A本地运行 FRPS推荐**
勾选"FRPS 运行在本机"
| 优点 | 说明 |
|------|------|
| 简单 | 程序自动管理 FRPS |
| 集成 | 无需额外部署 |
| 便捷 | 配置自动生效 |
**方式 B使用外部 FRPS**
如果您已有独立的 FRPS 服务器:
1. 取消勾选"FRPS 运行在本机"
2. 填写 FRPS 服务器地址
3. 填写 FRPS 端口
4. 填写认证 Token需与 FRPS 配置一致)
#### 13.5 设置端口和密钥
| 设置项 | 说明 | 建议值 |
|--------|------|-------|
| FRPS 端口 | FRPS 服务监听端口 | 7000 |
| 认证 Token | 安全密钥 | 使用复杂随机字符串 |
**Token 建议:** 使用 16 位以上包含字母数字的随机字符串。
#### 13.6 设置分配范围
| 设置项 | 说明 | 建议值 |
|--------|------|-------|
| 起始端口 | 分配给下级的最小端口 | 20000 |
| 结束端口 | 分配给下级的最大端口 | 29999 |
**容量计算:** 端口范围 20000-29999 可容纳 10000 个下级。
### 14. 开放防火墙
FRP 服务需要开放以下端口:
| 端口 | 用途 |
|------|------|
| 7000FRPS 端口) | FRP 服务监听 |
| 20000-29999分配范围 | 下级映射端口 |
#### 14.1 Windows 防火墙
1. 打开"Windows Defender 防火墙"
2. 点击"高级设置"
3. 选择"入站规则" → "新建规则"
4. 选择"端口" → "TCP"
5. 输入端口:`7000, 20000-29999`
6. 选择"允许连接"
7. 完成
#### 14.2 云服务器安全组
以阿里云/腾讯云为例:
1. 登录云控制台
2. 找到安全组配置
3. 添加入站规则:
- 协议TCP
- 端口7000
- 端口20000-29999
4. 保存配置
### 15. 验证 FRP 服务
#### 15.1 检查运行状态
如果选择"本地运行 FRPS"
- 状态栏应显示 FRP 服务状态
- 可查看日志文件 `Bin/frps.log`
#### 15.2 测试下级连接
1. 为测试下级生成授权(包含 FRP
2. 下级导入授权
3. 确认下级状态栏显示 FRP 连接成功
4. 下级生成受管程序并测试
---
## 第五部分:授权管理
### 16. 查看已发放授权
点击菜单 **工具****授权管理**
授权列表显示以下信息:
| 列名 | 说明 |
|------|------|
| 序列号 | 下级设备序列号 |
| 备注 | 自定义说明 |
| 有效期 | 起始日期 - 结束日期 |
| 并发数 | 分配的并发上限 |
| FRP 端口 | 分配的 FRP 端口(如有) |
| 状态 | 有效/过期/禁用 |
**筛选功能:**
- 按状态筛选:有效/过期/全部
- 按关键词搜索:序列号、备注
### 17. 授权续期
当下级授权即将过期时:
1. 在授权列表中找到目标授权
2. 双击或点击"编辑"
3. 修改结束日期
4. 保存更改
**下级更新授权:**
- 下级重启程序即可自动获取新的有效期(通过网络验证)
- 通常**无需重新发送 lic 文件**
> **注意**只有当下级的序列号SN发生变化时才需要重新生成并发送授权文件。
### 18. 授权撤销
#### 18.1 何时需要撤销
- 下级未按时付费
- 下级违反使用协议
- 下级主动要求取消
#### 18.2 撤销方式
**方式一:禁用授权**
1. 在授权列表选中目标
2. 点击"禁用"
3. 下级下次验证时授权失效
**方式二:删除授权**
1. 在授权列表选中目标
2. 点击"删除"
3. 授权记录从数据库移除
#### 18.3 撤销后的影响
- 下级程序在下次联网验证时失效
- 已连接的受管端保持连接直到断开
- 下级无法生成新的受管程序
### 19. 端口回收
当下级不再使用 FRP 时:
1. 记录下级占用的端口号
2. 撤销或删除该下级授权
3. 端口自动回到可用池
4. 可分配给新下级使用
---
## 第六部分:日常运维
### 20. 监控下级状态
#### 20.1 定期检查
建议定期检查:
- 下级授权到期情况
- FRP 端口使用率
- 异常连接情况
#### 20.2 日志查看
FRP 相关日志位置:
- `Bin/frps.log` - FRPS 服务日志
- 包含下级连接/断开记录
### 21. 常见问题处理
| 下级反馈 | 可能原因 | 处理方法 |
|---------|---------|---------|
| 无法导入授权 | 序列号不匹配 | 确认序列号,重新生成 |
| 授权显示无效 | 有效期问题 | 检查日期设置 |
| FRP 连接失败 | 防火墙/网络 | 检查端口开放 |
| 授权已过期 | 到期未续费 | 续期后下级重启程序即可 |
| 并发数超限 | 超过分配限制 | 升级授权或释放连接 |
| 受管端连不上 | 地址端口错误 | 检查下级设置 |
### 22. 备份与恢复
#### 22.1 定期备份
建议备份以下数据:
| 数据 | 位置 | 说明 |
|------|------|------|
| 配置信息 | 注册表 `HKCU\Software\YAMA` | 可导出为 .reg 文件 |
| 授权数据库 | `%APPDATA%\YAMA\licenses.db` | SQLite 数据库 |
**备份频率:** 每周或每次重要操作后
**注册表导出方法:**
1. 运行 `regedit`
2. 导航到 `HKEY_CURRENT_USER\Software\YAMA`
3. 右键 → 导出 → 保存为 .reg 文件
#### 22.2 恢复方法
1. 停止 YAMA 程序
2. 双击 .reg 文件导入注册表配置
3.`licenses.db` 复制到 `%APPDATA%\YAMA\`
4. 重新启动程序
5. 验证数据恢复正确
---
## 第七部分:进阶功能
### 23. 下级成为代理商
如果您的授权支持,您的下级也可以发展自己的下级:
```
您(一级代理)
└── 下级 A二级代理
├── A 的下级 1
└── A 的下级 2
```
**前提条件:**
- 您的授权支持多级
- 为下级开通代理权限
### 24. 批量管理
#### 24.1 批量生成
如需为多个下级生成授权:
1. 收集所有序列号
2. 逐个生成授权
3. 批量导出文件
4. 分别发送
#### 24.2 批量续期
对于到期的授权:
1. 筛选即将过期的授权
2. 逐个编辑延长有效期
3. 通知下级重启程序以更新授权
> 续期无需重新发送 lic 文件,下级重启即可通过网络获取新的有效期。
### 25. 数据统计
建议自行记录统计以下数据:
| 统计项 | 用途 |
|--------|------|
| 下级数量 | 业务规模 |
| 端口使用率 | 资源规划 |
| 到期预警 | 续费提醒 |
| 收入记录 | 财务管理 |
---
## 附录
### A. 定价建议参考
定价时可考虑以下因素:
| 因素 | 说明 |
|------|------|
| 并发数档位 | 10/50/100/500/不限 |
| 有效期 | 月/季/半年/年 |
| FRP 服务 | 是否包含、带宽限制 |
| 技术支持 | 基础/高级/VIP |
### B. 常用话术模板
**获取序列号:**
```
请运行 YAMA.exe点击菜单"其他" → "申请授权"。
首次需确认使用条款,之后即可查看序列号。
将序列号发送给我即可。
```
**发送授权文件:**
```
附件是您的授权文件,请按以下步骤导入:
1. 运行 YAMA.exe
2. 点击菜单"工具" → "导入口令..."
3. 选择附件中的 *.lic 文件
4. 关闭并重启程序
5. 检查状态栏确认授权生效
如有问题随时联系我。
```
**续期通知:**
```
您的 YAMA 授权将于 [日期] 到期。
如需续期,请在到期前联系我。
续期后重启程序即可自动更新,无需重新导入文件。
```
### C. 技术支持
如遇到本手册未涵盖的问题:
| 渠道 | 联系方式 |
|------|---------|
| QQ | 962914132 |
| Telegram | [@doge_grandfather](https://t.me/doge_grandfather) |
---
## 相关文档
- [快速部署指南](QuickStart.md) - 首次部署
- [多级网络搭建指南](NetworkSetup.md) - 网络架构详解
- [日常使用手册](UserManual.md) - 远程管理功能
- [定制化开发指南](CustomizationGuide.md) - 二次开发(技术型客户)

157
docs/BuildScript.md Normal file
View File

@@ -0,0 +1,157 @@
# SimpleRemoter 编译脚本
命令行编译脚本,支持完整编译和增量编译。
## 环境要求
- Visual Studio 2019 或 2022
- v142 工具集 (MSVC v142 - VS 2019 C++ build tools)
- MFC 库 (C++ MFC for v142 build tools)
脚本自动检测以下 VS 安装路径:
- `C:\Program Files\Microsoft Visual Studio\18\*` (VS2019 Insiders)
- `C:\Program Files*\Microsoft Visual Studio\2019\*`
- `C:\Program Files*\Microsoft Visual Studio\2022\*`
## 脚本文件
| 文件 | 说明 |
|------|------|
| `build.ps1` | PowerShell 主脚本 |
| `build.cmd` | 批处理快捷入口 |
## 命令参数
### PowerShell (`build.ps1`)
```powershell
.\build.ps1 [-Config <配置>] [-Platform <平台>] [-ServerOnly] [-Clean] [-Publish]
```
| 参数 | 可选值 | 默认值 | 说明 |
|------|--------|--------|------|
| `-Config` | `Release`, `Debug` | `Release` | 编译配置 |
| `-Platform` | `x64`, `x86`, `all` | `x64` | 目标平台 |
| `-ServerOnly` | 开关 | 否 | 仅编译主控程序,跳过依赖项 |
| `-Clean` | 开关 | 否 | 清理后重新编译 |
| `-Publish` | 开关 | 否 | 发版模式:重编译所有 + UPX 压缩 |
### 批处理 (`build.cmd`)
```cmd
build [release|debug] [x64|x86|all] [server|clean|publish]
```
- 参数顺序任意
- 不区分大小写
## 使用示例
### PowerShell
```powershell
# 完整编译 (Release x64)
.\build.ps1
# 编译 x86 + x64 两个平台
.\build.ps1 -Platform all
# Debug 模式编译
.\build.ps1 -Config Debug
# 仅编译主控程序 (跳过依赖项,适合快速重编译)
.\build.ps1 -ServerOnly
# 清理后重新编译
.\build.ps1 -Clean
# 组合使用
.\build.ps1 -Config Debug -Platform all -Clean
```
### 批处理
```cmd
build # 完整编译 Release x64
build server # 仅主控程序
build debug x86 # Debug x86
build release all clean # 清理后编译所有平台
build publish # 发版模式
```
## 编译流程
### 完整编译 (默认)
```
Step 1: 编译客户端项目 (始终 Release, x86 + x64)
├── ServerDll.dll ← ClientDll_vs2015.vcxproj
├── ghost.exe ← ghost_vs2015.vcxproj
├── TestRun.exe ← TestRun_vs2015.vcxproj
├── TinyRun.dll ← TinyRun.vcxproj
└── SCLoader.exe ← SCLoader.vcxproj
Step 2: 编译主控程序 (使用 -Config 指定的配置)
└── Yama.exe ← 2015Remote_vs2015.vcxproj
```
**注意:** 客户端项目始终使用 Release 模式编译,因为主控程序资源中引用的是 `Release\` 目录下的文件。`-Config Debug` 只影响主控程序本身。
主控程序 Yama 会将 Step 1 编译的 DLL/EXE 内嵌到资源中,因此**首次编译必须执行完整编译**。
### 发版模式 (-Publish)
```
Step 1: 重新编译所有客户端项目 (Release x86 + x64)
Step 2: 重新编译主控程序 (Release x64)
Step 3: UPX 压缩 (upx --best Yama_x64.exe)
```
发版模式会:
- 强制 Release x64 配置
- 强制 Clean 重新编译(确保所有文件是最新的)
- 使用 UPX 压缩最终输出(约 70% 压缩率)
UPX 路径:`server/2015Remote/res/3rd/upx.exe`
### 仅主控 (-ServerOnly)
跳过 Step 1直接编译 Yama。适用于
- 依赖项已编译完成
- 仅修改了主控程序代码
## 输出文件
| 平台 | 输出路径 |
|------|---------|
| x64 | `Bin\Yama_x64.exe` |
| x86 | `Bin\Yama_x86.exe` |
客户端项目输出:
| 项目 | x86 输出 | x64 输出 |
|------|---------|---------|
| ServerDll | `Release\ServerDll.dll` | `x64\Release\ServerDll.dll` |
| ghost | `Release\ghost.exe` | `x64\Release\ghost.exe` |
| TestRun | `Release\TestRun.exe` | `x64\Release\TestRun.exe` |
| TinyRun | `Release\TinyRun.dll` | `x64\Release\TinyRun.dll` |
| SCLoader | `Release\SCLoader.exe` | `x64\Release\SCLoader.exe` |
## 常见问题
### v142 build tools cannot be found
安装 VS2019 构建工具:
1. 打开 Visual Studio Installer
2. 修改 VS2022 或安装 VS2019
3. 勾选 `MSVC v142 - VS 2019 C++ x64/x86 build tools`
### MFC libraries are required
安装 MFC 库:
1. 打开 Visual Studio Installer
2. 勾选 `C++ MFC for v142 build tools (x86 and x64)`
### MSBuild not found
确保安装了 Visual Studio 2019 或 2022且包含 C++ 桌面开发工作负载。

272
docs/CrashProtection.md Normal file
View File

@@ -0,0 +1,272 @@
# 崩溃保护与 MTBF 统计设计文档
## 概述
本文档描述服务端代理进程的崩溃保护机制和可靠性统计功能。
## 功能目标
1. **崩溃保护**:防止代理进程连续崩溃导致的无限重启循环
2. **崩溃统计**:记录崩溃次数、时间、退出代码等信息
3. **MTBF 计算**收集数据用于计算平均故障间隔时间Mean Time Between Failures
---
## 一、崩溃保护机制
### 1.1 触发条件
| 参数 | 值 | 说明 |
|------|-----|------|
| 窗口期 | 5 分钟 | `CRASH_WINDOW_MS = 300000` |
| 阈值 | 3 次 | `CRASH_THRESHOLD = 3` |
| 快速崩溃定义 | 30 秒内退出 | `FAST_CRASH_TIME_MS = 30000` |
**触发逻辑**:在 5 分钟窗口期内,代理进程发生 3 次快速崩溃(启动后 30 秒内异常退出),则触发崩溃保护。
### 1.2 判定标准
只有同时满足以下条件才计入崩溃:
- 退出代码 ≠ 0正常退出不计入
- 运行时间 < 30 秒(长时间运行后退出不计入)
**设计原则**:程序正常退出必须返回 0任何非 0 退出代码都视为异常退出。包括:
- Windows 异常(如 `0xC0000005` ACCESS_VIOLATION
- 自定义错误码(如 `1001` CONFIG_ERROR
- 其他非零退出码
### 1.3 保护行为
触发崩溃保护后:
1. 服务停止自动重启代理进程
2. 写入 `AgentCrashProtected = 1` 到配置
3. 下次程序启动时检测到此标志,切换到普通运行模式
4. 弹出提示框告知用户
### 1.4 状态持久化
崩溃窗口状态持久化到注册表,确保服务重启后状态不丢失:
配置 section: `[crash]`
| 配置键 | 类型 | 说明 |
|--------|------|------|
| `winCount` | int | 窗口期内崩溃次数 |
| `winStart` | string (uint64) | 窗口起始时间 (GetTickCount64) |
**服务重启时的恢复逻辑**
```
加载保存的状态
检查 savedFirstCrashTime <= 当前时间?
├─ 否 → 系统重启过,清除状态
└─ 是 → 检查是否仍在 5 分钟窗口内?
├─ 否 → 窗口已过期,清除状态
└─ 是 → 恢复状态,继续计数
```
**注意**`GetTickCount64()` 在系统重启后会重置为 0因此如果保存的时间戳大于当前时间说明系统重启过需要清除状态重新开始。
---
## 二、崩溃统计
### 2.1 记录内容
每次崩溃时记录以下信息section: `[crash]`
| 配置键 | 类型 | 说明 | 示例 |
|--------|------|------|------|
| `count` | int | 总崩溃次数 | `15` |
| `lastTime` | string | 最后崩溃时间 | `"2024-01-15 10:30:45"` |
| `lastCode` | string | 最后退出代码 | `"0xC0000005 (ACCESS_VIOLATION)"` |
| `lastRunMs` | string (uint64) | 最后运行时间(ms) | `"25000"` |
### 2.2 退出代码描述
常见的 Windows 异常代码:
| 代码 | 描述 |
|------|------|
| `0xC0000005` | ACCESS_VIOLATION访问违规 |
| `0xC0000094` | INTEGER_DIVIDE_BY_ZERO整数除零 |
| `0xC00000FD` | STACK_OVERFLOW栈溢出 |
| `0xC0000409` | STACK_BUFFER_OVERRUN栈缓冲区溢出 |
| `0xC000001D` | ILLEGAL_INSTRUCTION非法指令 |
| `0x80000003` | BREAKPOINT断点 |
正常退出:
| 代码 | 描述 |
|------|------|
| `0` | NORMAL正常退出 |
自定义错误代码1000-1999
| 代码 | 描述 |
|------|------|
| `1001` | CONFIG_ERROR配置错误 |
| `1002` | NETWORK_ERROR网络错误 |
| `1003` | AUTH_FAILED认证失败 |
| `1004` | RESTART_REQUEST请求重启 |
| `1005` | MANUAL_STOP手动停止 |
---
## 三、MTBF 统计
### 3.1 数据收集
| 配置键 | 类型 | 说明 |
|--------|------|------|
| `starts` | int | 启动次数 |
| `totalRunMs` | string (uint64) | 累计运行时间(ms) |
| `count` | int | 崩溃次数 |
**注意**`totalRunMs``lastRunMs` 使用字符串存储 64 位整数,避免 32 位整数的限制(最大约 49 天)。
### 3.2 计算公式
**MTBF平均故障间隔时间**
```
MTBF = 累计运行时间 / 崩溃次数
```
**失败率Failure Rate**
```
失败率 = 崩溃次数 / 启动次数
```
### 3.3 示例
假设:
- 启动次数100 次
- 崩溃次数5 次
- 累计运行时间30 天
计算结果:
- MTBF = 30 天 / 5 = 6 天(平均每 6 天发生一次崩溃)
- 失败率 = 5 / 100 = 5%(每次启动有 5% 概率崩溃)
---
## 四、回调函数设计
### 4.1 回调类型
| 回调 | 触发时机 | 参数 |
|------|---------|------|
| `onAgentStart` | 代理进程启动成功 | processId, sessionId |
| `onAgentExit` | 代理进程退出(任何原因) | exitCode, runtimeMs |
| `onCrash` | 检测到快速崩溃 | exitCode, runtimeMs |
| `onCrashWindowChange` | 崩溃窗口状态变化 | crashCount, firstCrashTime |
| `onCrashProtection` | 触发崩溃保护 | 无 |
### 4.2 调用顺序
正常退出:
```
onAgentExit(exitCode=0, runtime)
```
快速崩溃:
```
onAgentExit(exitCode, runtime)
onCrash(exitCode, runtime)
[如果 count >= 3]
onCrashProtection()
onCrashWindowChange(count, startTime)
```
---
## 五、配置键汇总
Section: `[crash]`
| 配置键 | 类型 | 用途 | 持久性 |
|--------|------|------|--------|
| `count` | int | 总崩溃次数 | 永久 |
| `lastTime` | string | 最后崩溃时间 | 永久 |
| `lastCode` | string | 最后退出代码 | 永久 |
| `lastRunMs` | string | 最后运行时间 | 永久 |
| `totalRunMs` | string | 累计运行时间 | 永久 |
| `starts` | int | 启动次数 | 永久 |
| `winCount` | int | 窗口期崩溃次数 | 临时* |
| `winStart` | string | 窗口起始时间 | 临时* |
| `protected` | int | 崩溃保护标志 | 一次性** |
\* 临时:窗口期过期或系统重启后自动清除
\** 一次性:程序启动时读取并清除
---
## 六、文件改动
| 文件 | 改动内容 |
|------|---------|
| `CrashReport.h` | 配置键定义、退出代码常量、MTBF 计算函数 |
| `ServerSessionMonitor.h` | 回调类型定义、监控器结构体 |
| `ServerSessionMonitor.cpp` | 崩溃检测逻辑、回调调用 |
| `ServerServiceWrapper.cpp` | 回调实现、状态持久化/恢复 |
| `2015Remote.cpp` | 启动时检查崩溃保护标志 |
---
## 七、使用场景示例
### 场景 1连续崩溃
```
T=00:00 代理启动 (startCount=1)
T=00:05 代理崩溃 (crashCount=1, 窗口开始)
T=00:10 代理重启 (startCount=2)
T=00:15 代理崩溃 (crashCount=2)
T=00:20 代理重启 (startCount=3)
T=00:25 代理崩溃 (crashCount=3, 触发保护!)
→ 服务停止重启代理
→ 写入 AgentCrashProtected=1
T=00:30 用户手动启动程序
→ 检测到保护标志,切换到普通模式
→ 弹出提示框
```
### 场景 2服务重启后恢复
```
T=00:00 代理崩溃 (crashCount=1)
T=00:30 代理崩溃 (crashCount=2, 保存到注册表)
T=01:00 服务重启(非系统重启)
→ 加载状态: count=2, elapsed=1分钟
→ 仍在5分钟窗口内恢复状态
T=01:30 代理崩溃 (crashCount=3, 触发保护!)
```
### 场景 3窗口过期
```
T=00:00 代理崩溃 (crashCount=1)
T=02:00 代理崩溃 (crashCount=2)
T=08:00 代理崩溃
→ 距离第一次崩溃已超过5分钟
→ 重置窗口: crashCount=1, 新窗口开始
```
---
## 八、设计决策记录
| 问题 | 决策 | 理由 |
|------|------|------|
| 窗口期时长 | 5 分钟,不可配置 | 足够检测连续崩溃,避免误判 |
| 崩溃阈值 | 3 次,不可配置 | 平衡敏感度和容错 |
| 快速崩溃定义 | 30 秒 | 覆盖大多数初始化场景 |
| 系统重启处理 | 清除窗口状态 | 系统重启后问题可能已修复 |
| 统计数据重置 | 暂不实现 | 需求不明确,后续按需添加 |
| 日志记录 | 使用 Mprintf | 与现有日志系统一致 |
| 非零退出码 | 都算崩溃 | 正常退出必须返回 0 |
| lastRunMs 类型 | string (uint64) | 支持超过 24 天的运行时间 |

280
docs/CustomCursor_Design.md Normal file
View File

@@ -0,0 +1,280 @@
# 自定义光标支持设计方案
**状态:已实现**
## 概述
扩展远程桌面的光标支持,使其能够显示应用程序自定义光标和动画光标,而不仅仅是 16 种标准系统光标。
## 现有实现
### 数据流
```
客户端 (ScreenCapture.h):
getCurrentCursorIndex() → 0-15 或 -1
帧数据: [算法(1)] [光标位置(8)] [光标索引(1)] [图像数据...]
服务端 (ScreenSpyDlg.cpp):
cursorIndex == -1 → 回退到标准箭头
cursorIndex 0-15 → 使用 m_CursorInfo.getCursorHandle(index)
```
### 限制
- 仅支持 16 种标准系统光标 (IDC_ARROW, IDC_HAND, IDC_IBEAM 等)
- 自定义光标显示为箭头
- 动画光标不支持
## 设计方案
### 方案概述
采用**独立命令传输 + 哈希缓存 + 节流机制**
1. 新增 `CMD_CURSOR_IMAGE` 命令传输光标位图
2. 使用位图哈希避免重复传输
3. 节流机制防止动画光标过载
4. 光标索引 `-2` 表示使用已缓存的自定义光标
### 光标索引定义
| 索引值 | 含义 |
|-------|------|
| 0-15 | 标准系统光标(现有) |
| -1 (255) | 不支持,显示箭头(现有,向后兼容) |
| -2 (254) | 使用缓存的自定义光标(新增) |
### CMD_CURSOR_IMAGE 数据格式
```
字节偏移 大小 字段
────────────────────────────
0 1 CMD_CURSOR_IMAGE (命令字节)
1 4 hash (位图哈希,用于去重)
5 2 hotspotX (热点X坐标)
7 2 hotspotY (热点Y坐标)
9 1 width (光标宽度最大255)
10 1 height (光标高度最大255)
11 N BGRA位图数据 (N = width * height * 4)
典型大小: 32x32光标 = 11 + 4096 = 4107 字节
```
### 客户端逻辑 (ScreenCapture.h / ScreenManager.cpp)
```cpp
// 新增状态变量
DWORD m_lastCursorHash = 0; // 上次发送的光标哈希
DWORD m_lastCursorSendTime = 0; // 上次发送时间
const DWORD CURSOR_THROTTLE = 50; // 最小发送间隔 (ms)
// 每帧处理逻辑
int cursorIndex = getCurrentCursorIndex();
if (cursorIndex == -1) {
// 非标准光标,获取位图
CursorBitmapInfo info;
if (GetCurrentCursorBitmap(&info)) {
DWORD hash = CalculateBitmapHash(info);
DWORD now = GetTickCount();
// 哈希变化且超过节流时间 → 发送光标图像
if (hash != m_lastCursorHash &&
(now - m_lastCursorSendTime) > CURSOR_THROTTLE) {
SendCursorImage(info, hash);
m_lastCursorHash = hash;
m_lastCursorSendTime = now;
}
cursorIndex = -2; // 使用自定义光标
}
}
// 帧数据中写入 cursorIndex
```
### 服务端逻辑 (ScreenSpyDlg.cpp)
```cpp
// 新增成员变量
HCURSOR m_hCustomCursor = NULL; // 缓存的自定义光标
DWORD m_customCursorHash = 0; // 当前光标哈希
// 处理 CMD_CURSOR_IMAGE
case CMD_CURSOR_IMAGE: {
DWORD hash = *(DWORD*)(buffer + 1);
if (hash != m_customCursorHash) {
WORD hotX = *(WORD*)(buffer + 5);
WORD hotY = *(WORD*)(buffer + 7);
BYTE width = buffer[9];
BYTE height = buffer[10];
LPBYTE bitmapData = buffer + 11;
// 销毁旧光标
if (m_hCustomCursor) {
DestroyCursor(m_hCustomCursor);
}
// 创建新光标
m_hCustomCursor = CreateCursorFromBitmap(
hotX, hotY, width, height, bitmapData);
m_customCursorHash = hash;
}
break;
}
// 处理帧数据中的光标索引
BYTE cursorIndex = buffer[2 + sizeof(POINT)];
HCURSOR cursor;
if (cursorIndex == 254) { // -2
cursor = m_hCustomCursor ? m_hCustomCursor : LoadCursor(NULL, IDC_ARROW);
} else if (cursorIndex == 255) { // -1
cursor = LoadCursor(NULL, IDC_ARROW);
} else {
cursor = m_CursorInfo.getCursorHandle(cursorIndex);
}
```
### 获取光标位图实现
```cpp
struct CursorBitmapInfo {
WORD hotspotX;
WORD hotspotY;
BYTE width;
BYTE height;
std::vector<BYTE> bgraData; // width * height * 4
};
bool GetCurrentCursorBitmap(CursorBitmapInfo* info) {
CURSORINFO ci = { sizeof(CURSORINFO) };
if (!GetCursorInfo(&ci) || ci.flags != CURSOR_SHOWING)
return false;
ICONINFO iconInfo;
if (!GetIconInfo(ci.hCursor, &iconInfo))
return false;
info->hotspotX = (WORD)iconInfo.xHotspot;
info->hotspotY = (WORD)iconInfo.yHotspot;
BITMAP bm;
GetObject(iconInfo.hbmColor ? iconInfo.hbmColor : iconInfo.hbmMask,
sizeof(BITMAP), &bm);
info->width = (BYTE)min(bm.bmWidth, 255);
info->height = (BYTE)min(bm.bmHeight, 255);
// 获取 BGRA 位图数据
// ... (使用 GetDIBits)
// 清理
DeleteObject(iconInfo.hbmColor);
DeleteObject(iconInfo.hbmMask);
return true;
}
```
### 哈希计算
```cpp
DWORD CalculateBitmapHash(const CursorBitmapInfo& info) {
// 使用 FNV-1a 或简单的累加哈希
DWORD hash = 2166136261; // FNV offset basis
const BYTE* data = info.bgraData.data();
size_t len = info.bgraData.size();
for (size_t i = 0; i < len; i += 16) { // 每16字节采样一次加速
hash ^= data[i];
hash *= 16777619; // FNV prime
}
return hash;
}
```
## 数据流图
```
┌─────────────────────────────────────────────────────────────────┐
│ 客户端 │
├─────────────────────────────────────────────────────────────────┤
│ getCurrentCursorIndex() │
│ │ │
│ ├─ 0-15 ──→ 帧数据: cursorIndex = 0-15 │
│ │ │
│ └─ -1 ──→ GetCurrentCursorBitmap() │
│ │ │
│ ├─ 计算哈希 │
│ │ │
│ ├─ 哈希变化 && 超过节流? │
│ │ │ │
│ │ ├─ Yes ──→ 发送 CMD_CURSOR_IMAGE │
│ │ │ 更新 lastHash, lastTime │
│ │ │ │
│ │ └─ No ──→ 跳过发送 │
│ │ │
│ └─ 帧数据: cursorIndex = -2 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 服务端 │
├─────────────────────────────────────────────────────────────────┤
│ 收到 CMD_CURSOR_IMAGE: │
│ └─ hash != m_customCursorHash? │
│ ├─ Yes ──→ CreateCursorFromBitmap() → m_hCustomCursor │
│ └─ No ──→ 忽略 (已缓存) │
│ │
│ 收到帧数据: │
│ └─ cursorIndex: │
│ ├─ 0-15 ──→ m_CursorInfo.getCursorHandle(index) │
│ ├─ -1 ──→ LoadCursor(IDC_ARROW) (向后兼容) │
│ └─ -2 ──→ m_hCustomCursor │
└─────────────────────────────────────────────────────────────────┘
```
## 动画光标支持
本方案自然支持动画光标:
| 机制 | 说明 |
|-----|------|
| **自动检测** | 动画每帧位图哈希不同,自动触发传输 |
| **节流保护** | 50ms 节流 ≈ 最高 20fps防止过载 |
| **带宽控制** | 32x32 动画 @ 10fps ≈ 40 KB/s可接受 |
## 修改文件清单
| 文件 | 修改内容 |
|-----|---------|
| `common/commands.h` | 新增 `CMD_CURSOR_IMAGE = 75` |
| `client/CursorInfo.h` | 新增 `GetCurrentCursorBitmap()`, `CalculateBitmapHash()` |
| `client/ScreenCapture.h` | 发送逻辑:检测、节流、发送 CMD_CURSOR_IMAGE |
| `client/ScreenManager.cpp` | 处理 CMD_CURSOR_IMAGE如果服务端发给客户端需要 |
| `server/2015Remote/ScreenSpyDlg.h` | 新增 `m_hCustomCursor`, `m_customCursorHash` |
| `server/2015Remote/ScreenSpyDlg.cpp` | 处理 CMD_CURSOR_IMAGE更新光标显示逻辑 |
## 向后兼容性
- 旧客户端:继续发送 `-1`,服务端显示箭头(现有行为)
- 新客户端 + 旧服务端:发送 `-2` 和 CMD_CURSOR_IMAGE旧服务端忽略新命令`-2` 当作 `-1` 处理
- 新客户端 + 新服务端:完整自定义光标支持
## 可调参数
```cpp
// client/ScreenCapture.h
const DWORD CURSOR_THROTTLE = 50; // 节流间隔 (ms)
// 50ms → 最高 20fps动画流畅
// 100ms → 最高 10fps省带宽
const BYTE MAX_CURSOR_SIZE = 64; // 最大光标尺寸
// 超过此尺寸的光标将被缩放或回退到标准光标
```
## 测试用例
1. **标准光标** - 箭头、手形、I形等应使用索引 0-15
2. **自定义静态光标** - 应用程序自定义光标,只传输一次
3. **动画光标** - Windows 忙碌动画,应持续更新(受节流限制)
4. **快速切换** - 频繁切换光标,节流应生效
5. **大尺寸光标** - 超过 64x64 的光标,应正确处理或回退

589
docs/CustomizationGuide.md Normal file
View File

@@ -0,0 +1,589 @@
# YAMA 定制化开发指南
> 面向技术型客户的二次开发与品牌定制
---
## 目标读者
- 有一定编程基础的客户
- 希望打造自己品牌的服务商
- 承接远程解决方案外包的开发者
---
## 第一部分:定制化概述
### 1. 可定制内容
| 类别 | 可定制项 | 难度 | 需要重编译 |
|------|---------|------|-----------|
| 品牌外观 | 程序名称、图标、版本信息 | 简单 | 是 |
| 菜单控制 | 隐藏/显示特定菜单项 | 简单 | 是 |
| 工具栏 | 隐藏/显示特定按钮 | 简单 | 是 |
| 右键菜单 | 隐藏/显示特定功能 | 简单 | 是 |
| 界面文字 | 修改翻译/添加语言 | 中等 | 否 |
| 功能定制 | 添加新功能、修改行为 | 高级 | 是 |
### 2. 定制方式
#### 2.1 配置文件定制(推荐)
修改 `UIBranding.h` 文件重新编译。
**适合场景:**
- 品牌名称修改
- 隐藏不需要的功能
- 快速生成定制版本
#### 2.2 资源替换定制
替换图标、图片等资源文件。
**适合场景:**
- 更换程序图标
- 自定义启动画面
#### 2.3 语言文件定制
修改语言文件实现界面文字定制。
**适合场景:**
- 修改界面用语
- 添加新语言支持
#### 2.4 源码级定制
修改源代码实现深度定制。
**适合场景:**
- 添加新功能
- 修改现有行为
- 深度集成
---
## 第二部分:品牌定制
### 3. UIBranding.h 配置文件
品牌定制的核心是修改 `server/2015Remote/UIBranding.h` 文件。
**文件位置:** `server/2015Remote/UIBranding.h`
**编码要求:** UTF-8 with BOMMSVC 要求)
#### 3.1 快速定制4 项核心设置)
最少只需修改以下 4 项即可完成基本品牌定制:
```cpp
// 程序名称(窗口标题)
#define BRAND_APP_NAME "YourBrand"
// 启动画面 Logo建议大写
#define BRAND_SPLASH_NAME "YOURBRAND"
// 托盘图标提示
#define BRAND_TRAY_TIP "YourBrand 远程管理"
// 版权信息
#define BRAND_COPYRIGHT "Copyright (C) 2024 YourCompany"
```
#### 3.2 完整品牌设置
| 配置项 | 说明 | 字符限制 |
|--------|------|---------|
| `BRAND_APP_NAME` | 窗口标题、关于对话框 | 可用中文 |
| `BRAND_VERSION` | 版本号(建议 X.Y.Z 格式) | 可用中文 |
| `BRAND_SPLASH_NAME` | 启动画面 Logo 文字 | 可用中文 |
| `BRAND_TRAY_TIP` | 托盘图标悬停提示 | 可用中文 |
| `BRAND_COPYRIGHT` | 版权声明 | 可用中文 |
| `BRAND_SERVICE_DISPLAY` | Windows 服务显示名 | 可用中文 |
| `BRAND_LICENSE_DESC` | 许可证文件描述 | 可用中文 |
#### 3.3 文件和系统标识
以下配置有字符限制,仅支持 ASCII
| 配置项 | 说明 | 限制 |
|--------|------|------|
| `BRAND_DUMP_PREFIX` | 崩溃转储文件前缀 | 仅 ASCII |
| `BRAND_EXE_NAME` | 默认 EXE 文件名 | 仅 ASCII |
| `BRAND_LICENSE_PREFIX` | 许可证文件前缀 | 仅 ASCII |
| `BRAND_DB_NAME` | 数据库文件名 | 仅 ASCII |
| `BRAND_DATA_FOLDER` | 数据目录名 | 仅 ASCII |
| `BRAND_SERVICE_NAME` | Windows 服务名(无空格) | 仅 ASCII |
| `BRAND_REGISTRY_KEY` | 注册表键名(无空格) | 仅 ASCII |
**示例:**
```cpp
#define BRAND_DUMP_PREFIX "MyRemote"
#define BRAND_EXE_NAME "MyRemote.exe"
#define BRAND_LICENSE_PREFIX "MyRemote"
#define BRAND_SERVICE_NAME "MyRemoteService"
#define BRAND_REGISTRY_KEY "MyRemote"
```
### 4. 隐藏菜单项
通过设置宏为 `1` 可以隐藏对应菜单项。
> **完整列表**:以下仅列出常用配置项,完整列表请查看 `UIBranding.h` 文件。
#### 4.1 主菜单项(常用)
```cpp
// 文件菜单
#define HIDE_MENU_SETTINGS 0 // 参数设置
#define HIDE_MENU_WALLET 0 // 钱包
#define HIDE_MENU_NETWORK 0 // 网络
// 工具菜单
#define HIDE_MENU_INPUT_PASSWORD 0 // 输入密码
#define HIDE_MENU_IMPORT_LICENSE 0 // 导入许可证
#define HIDE_MENU_GEN_AUTH 0 // 生成授权
#define HIDE_MENU_GEN_MASTER 0 // 生成Master
#define HIDE_MENU_LICENSE_MGR 0 // 许可证管理
// 参数菜单
#define HIDE_MENU_KBLOGGER 0 // 键盘记录
#define HIDE_MENU_LOGIN_NOTIFY 0 // 登录通知
// 扩展菜单
#define HIDE_MENU_FRPS_FOR_SUB 0 // 下级FRP
```
#### 4.2 使用示例
隐藏高级功能,创建简化版本:
```cpp
// 隐藏 ShellCode 相关功能
#define HIDE_MENU_SHELLCODE_C 1
#define HIDE_MENU_SHELLCODE_BIN 1
#define HIDE_MENU_SHELLCODE_LOAD_TEST 1
#define HIDE_MENU_SHELLCODE_OBFS 1
#define HIDE_MENU_SHELLCODE_AES_C 1
#define HIDE_MENU_SHELLCODE_AES_BIN 1
// 隐藏开发相关功能
#define HIDE_MENU_V2_PRIVATEKEY 1
#define HIDE_MENU_RCEDIT 1
```
### 5. 隐藏工具栏按钮(常用)
```cpp
#define HIDE_TOOLBAR_TERMINAL 0 // 终端管理
#define HIDE_TOOLBAR_PROCESS 0 // 进程管理
#define HIDE_TOOLBAR_DESKTOP 0 // 桌面管理
#define HIDE_TOOLBAR_FILE 0 // 文件管理
#define HIDE_TOOLBAR_AUDIO 0 // 语音管理
#define HIDE_TOOLBAR_VIDEO 0 // 视频管理
#define HIDE_TOOLBAR_SERVICE 0 // 服务管理
#define HIDE_TOOLBAR_REGISTER 0 // 注册表管理
#define HIDE_TOOLBAR_KEYBOARD 0 // 键盘记录
#define HIDE_TOOLBAR_SETTINGS 0 // 参数设置
#define HIDE_TOOLBAR_BUILD 0 // 生成服务端
```
### 6. 隐藏右键菜单项(常用)
```cpp
// 远程桌面选项
#define HIDE_CTX_VIRTUAL_DESKTOP 0 // 虚拟桌面
#define HIDE_CTX_GRAY_DESKTOP 0 // 灰度桌面
#define HIDE_CTX_REMOTE_DESKTOP 0 // 远程桌面
#define HIDE_CTX_H264_DESKTOP 0 // H264桌面
// 系统操作
#define HIDE_CTX_MACHINE_SHUTDOWN 0 // 关机
#define HIDE_CTX_MACHINE_REBOOT 0 // 重启
#define HIDE_CTX_MACHINE_LOGOUT 0 // 注销
// 执行命令
#define HIDE_CTX_EXECUTE_DOWNLOAD 0 // 下载执行
#define HIDE_CTX_EXECUTE_UPLOAD 0 // 上传执行
```
---
## 第三部分:视觉定制
### 7. 图标替换
#### 7.1 图标文件位置
图标资源位于 `server/2015Remote/res/` 目录:
| 文件 | 用途 | 尺寸要求 |
|------|------|---------|
| `2015Remote.ico` | 主程序图标 | 多尺寸 ICO |
| `tray_online.ico` | 托盘在线图标 | 16x16, 32x32 |
| `tray_offline.ico` | 托盘离线图标 | 16x16, 32x32 |
#### 7.2 图标格式要求
ICO 文件应包含多种尺寸:
- 16x16小图标、托盘
- 32x32标准图标
- 48x48大图标
- 256x256高清图标
**推荐工具:**
- IcoFX
- GIMP
- 在线 ICO 生成器
#### 7.3 替换步骤
1. 准备新图标文件
2. 备份原有图标
3. 将新图标复制到 `res/` 目录,保持文件名一致
4. 重新编译程序
### 8. 启动画面定制
启动画面的 Logo 文字由 `BRAND_SPLASH_NAME` 配置:
```cpp
#define BRAND_SPLASH_NAME "YOURBRAND"
```
**建议:**
- 使用大写字母,视觉效果更好
- 保持简短(建议 8 个字符以内)
- 可以使用中文
---
## 第四部分:界面文字定制
### 9. 语言文件机制
YAMA 使用 INI 格式的语言文件实现多语言支持。
#### 9.1 语言文件位置
**源代码位置:**
```
server/2015Remote/lang/
├── en_US.ini # 英文翻译
├── zh_TW.ini # 繁体中文翻译
└── (其他语言)
```
**运行时位置:** 程序会从程序同目录的 `lang/` 文件夹读取语言文件。
**发布时:**`lang/` 文件夹与程序放在同一目录。
#### 9.2 文件格式
```ini
; 注释行
简体中文原文=翻译文本
另一条原文=另一条翻译
```
**编码要求:** GB2312不是 UTF-8
#### 9.3 添加翻译
由于语言文件必须使用 GB2312 编码,建议使用以下方法添加翻译:
**方法 A使用支持 GB2312 编码的编辑器**
1. 使用 Notepad++、VS Code安装编码插件等编辑器
2. 打开语言文件,确认编码为 GB2312
3. 添加新的翻译条目
4. 保存时确保保持 GB2312 编码
**方法 B使用 PowerShell 命令**
```powershell
# 追加单条翻译
$gb2312 = [System.Text.Encoding]::GetEncoding('GB2312')
$content = "新功能=New Feature`r`n"
[System.IO.File]::AppendAllText('lang\en_US.ini', $content, $gb2312)
```
> **注意**:直接用记事本编辑可能导致编码错误,请使用专业编辑器。
### 10. 创建新语言包
1. 复制 `en_US.ini` 作为模板
2. 重命名为新语言代码(如 `ja_JP.ini`
3. 翻译所有条目
4. 保存为 GB2312 编码
---
## 第五部分:开发环境
### 11. 环境要求
| 组件 | 版本要求 |
|------|---------|
| Visual Studio | 2019 或 2022 |
| Windows SDK | 10.0.19041.0 或更高 |
| C++ 标准 | C++17 |
| 工具集 | v142 (MSVC v142) |
| MFC 库 | C++ MFC for v142 build tools |
### 12. 项目结构
```
SimpleRemoter/
├── server/2015Remote/ # 主控程序源码
│ ├── UIBranding.h # 品牌配置
│ ├── 2015RemoteDlg.cpp # 主对话框
│ ├── SettingDlg.cpp # 设置页面
│ ├── BuildDlg.cpp # 生成对话框
│ ├── res/ # 资源文件
│ └── lang/ # 语言文件
├── client/ # 受管端源码
├── common/ # 公共代码
├── linux/ # Linux 客户端
└── Bin/ # 编译输出
```
### 13. 关键文件说明
| 文件 | 功能 |
|------|------|
| `UIBranding.h` | 品牌和功能开关配置 |
| `2015RemoteDlg.cpp` | 主窗口、菜单、状态栏 |
| `SettingDlg.cpp` | 设置对话框 |
| `BuildDlg.cpp` | 生成受管程序对话框 |
| `CPasswordDlg.cpp` | 授权生成对话框 |
| `FrpsForSubDlg.cpp` | FRP 代理配置 |
| `ScreenSpyDlg.cpp` | 远程桌面窗口 |
| `FileManagerDlg.cpp` | 文件管理窗口 |
| `TerminalDlg.cpp` | 远程终端窗口 |
---
## 第六部分:编译与发布
### 14. 编译脚本
使用提供的编译脚本:
```powershell
# 完整编译(先编译依赖项,再编译主控)
.\build.ps1
# 仅编译主控程序(跳过依赖项,适合快速重编译)
.\build.ps1 -ServerOnly
# 发版模式(重编译所有 + UPX 压缩)
.\build.ps1 -Publish
# Debug 模式
.\build.ps1 -Config Debug
# 编译 32 位和 64 位
.\build.ps1 -Platform all
# 清理后重编译
.\build.ps1 -Clean
```
### 15. 编译输出
| 输出文件 | 位置 |
|---------|------|
| 64 位主控 | `Bin/Yama_x64.exe` |
| 32 位主控 | `Bin/Yama_x86.exe` |
### 16. 发布准备
#### 16.1 发布检查清单
- [ ] 修改 `UIBranding.h` 中的品牌信息
- [ ] 替换程序图标(如需要)
- [ ] 使用 Release 配置编译
- [ ] 使用 `-Publish` 进行 UPX 压缩
- [ ] 测试所有功能正常
- [ ] 测试授权导入功能
- [ ] 测试受管程序生成
#### 16.2 文件清单
发布时需要包含:
```
YourBrand/
├── YourBrand.exe # 主程序(重命名)
└── lang/ # 语言文件(可选)
├── en_US.ini
└── zh_TW.ini
```
---
## 第七部分:常见定制场景
### 17. 场景一:简单品牌替换
**需求:** 更换程序名称和图标
**步骤:**
1. 修改 `UIBranding.h` 中的品牌名称
2. 替换 `res/` 目录中的图标文件
3. 编译:`.\build.ps1 -Publish`
4. 重命名输出文件
### 18. 场景二:功能精简版
**需求:** 创建只保留基本功能的精简版
**步骤:**
1.`UIBranding.h` 中隐藏高级功能:
```cpp
// 隐藏开发相关
#define HIDE_MENU_SHELLCODE_C 1
#define HIDE_MENU_SHELLCODE_BIN 1
#define HIDE_MENU_V2_PRIVATEKEY 1
#define HIDE_MENU_RCEDIT 1
// 隐藏高级功能
#define HIDE_TOOLBAR_AUDIO 1
#define HIDE_TOOLBAR_VIDEO 1
#define HIDE_TOOLBAR_REGISTER 1
#define HIDE_TOOLBAR_KEYBOARD 1
```
2. 编译发布
### 19. 场景三:多品牌版本
**需求:** 为不同客户生成不同品牌的版本
**方案:**
1. 创建多个 `UIBranding.h` 副本(如 `UIBranding_ClientA.h`
2. 编译前复制对应配置文件覆盖 `UIBranding.h`
3. 编译并重命名输出
4. 或编写脚本自动化此过程
---
## 第八部分:最佳实践
### 20. 版本管理建议
#### 20.1 分支策略
```
main (或 master) # 上游原版
├── custom/base # 基础定制
├── custom/clientA # 客户 A 定制
└── custom/clientB # 客户 B 定制
```
#### 20.2 保持同步
定期从上游合并更新:
```bash
git fetch upstream
git merge upstream/main
# 解决冲突后提交
```
### 21. 定制文档
建议记录定制内容:
| 记录项 | 说明 |
|--------|------|
| 品牌配置 | 修改了哪些 `UIBranding.h` 配置 |
| 隐藏功能 | 隐藏了哪些菜单/按钮 |
| 图标变更 | 替换了哪些图标文件 |
| 源码修改 | 如有源码修改,记录位置和原因 |
### 22. 测试清单
每次定制后测试:
- [ ] 程序能正常启动
- [ ] 品牌信息显示正确
- [ ] 图标显示正确
- [ ] 授权导入功能正常
- [ ] 受管程序生成正常
- [ ] 远程桌面功能正常
- [ ] 文件管理功能正常
- [ ] 隐藏的功能确实不显示
---
## 附录
### A. 配置项速查表
| 配置项 | 文件 | 说明 |
|--------|------|------|
| 程序名称 | `UIBranding.h` | `BRAND_APP_NAME` |
| 版本号 | `UIBranding.h` | `BRAND_VERSION` |
| 启动 Logo | `UIBranding.h` | `BRAND_SPLASH_NAME` |
| 托盘提示 | `UIBranding.h` | `BRAND_TRAY_TIP` |
| 版权信息 | `UIBranding.h` | `BRAND_COPYRIGHT` |
| 隐藏菜单 | `UIBranding.h` | `HIDE_MENU_*` |
| 隐藏工具栏 | `UIBranding.h` | `HIDE_TOOLBAR_*` |
| 隐藏右键菜单 | `UIBranding.h` | `HIDE_CTX_*` |
| 界面翻译 | `lang/*.ini` | 键值对 |
### B. 资源文件清单
| 资源 | 位置 | 格式 |
|------|------|------|
| 主程序图标 | `res/2015Remote.ico` | ICO |
| 托盘图标 | `res/tray_*.ico` | ICO |
| 工具栏图标 | `res/toolbar.bmp` | BMP |
### C. 常见问题
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 编译报错 | VS 版本/工具集 | 确认使用 VS2019+ 和 v142 工具集 |
| 中文乱码 | 文件编码错误 | UIBranding.h 需 UTF-8 with BOM |
| 图标不显示 | 格式不正确 | 确认 ICO 包含多尺寸 |
| 翻译不生效 | 编码错误 | 语言文件需 GB2312 编码 |
| 菜单仍显示 | 未重编译 | 修改 UIBranding.h 后需重编译 |
### D. 注意事项
**请勿修改以下配置**(会导致功能异常):
```cpp
// 修改会导致所有已发放许可证失效
#define BRAND_LICENSE_MAGIC "YAMA"
// 修改会导致单实例检测失败
#define BRAND_EVENT_PREFIX "YAMA"
// 修改会导致超管密码配置失效
#define BRAND_ENV_VAR "YAMA_PWD"
```
---
## 技术支持
| 渠道 | 联系方式 |
|------|---------|
| QQ | 962914132 |
| Telegram | [@doge_grandfather](https://t.me/doge_grandfather) |
---
## 相关文档
- [快速部署指南](QuickStart.md) - 基础部署
- [多级网络搭建指南](NetworkSetup.md) - 网络架构
- [日常使用手册](UserManual.md) - 功能使用
- [代理商运营手册](AgentManual.md) - 授权管理

233
docs/FILE_TRANSFER_V2.md Normal file
View File

@@ -0,0 +1,233 @@
# V2 文件/文本传输功能清单
## 一、文件传输
| 方向 | 触发方式 | 断点续传 | 进度显示 | 备注 |
|------|----------|:--------:|:--------:|------|
| 服务端 → 客户端 | 文件管理器拖放/Ctrl+V | ✅ | ✅ | 支持大文件(>4GB) |
| 客户端 → 服务端 | 文件管理器下载 | ✅ | ✅ | 支持大文件(>4GB) |
| 客户端A → 客户端B (C2C) | 远程桌面 Ctrl+C/Ctrl+V | ✅ | ✅ | 自动捕获目标目录 |
## 二、文本传输
| 方向 | 触发方式 | 说明 |
|------|----------|------|
| 服务端 → 客户端 | 远程桌面剪贴板同步 | `COMMAND_SCREEN_SET_CLIPBOARD` |
| 客户端 → 服务端 | 远程桌面剪贴板同步 | `COMMAND_SCREEN_GET_CLIPBOARD` |
| 客户端A → 客户端B (C2C) | 远程桌面 Ctrl+C/Ctrl+V | 自动粘贴 |
## 三、C2C 传输流程
```
┌─────────────┐ Ctrl+V ┌─────────────┐ CLIPBOARD_V2 ┌─────────────┐
│ 远程桌面B │ ───────────────>│ 服务端 │ ──────────────────>│ 远程桌面A │
│ (目标) │ │ │ │ (源) │
└─────────────┘ └─────────────┘ └─────────────┘
▲ │ │
│ │ C2C_PREPARE │
│<──────────────────────────────┘ │
│ │
│ ┌─────────────┐ │
│ SEND_FILE_V2/C2C_TEXT │ 服务端 │ SEND_FILE_V2/C2C_TEXT │
│<────────────────────────│ (转发) │<──────────────────────────┘
│ └─────────────┘
```
## 四、协议命令
| 命令 | 值 | 用途 |
|------|:--:|------|
| `COMMAND_SEND_FILE_V2` | 85 | V2文件数据包 |
| `COMMAND_FILE_RESUME` | 86 | 断点续传响应 |
| `COMMAND_CLIPBOARD_V2` | 87 | C2C剪贴板请求 |
| `COMMAND_FILE_QUERY_RESUME` | 88 | 断点续传查询 |
| `COMMAND_C2C_PREPARE` | 89 | C2C准备接收通知 |
| `COMMAND_C2C_TEXT` | 90 | C2C文本传输 |
| `COMMAND_FILE_COMPLETE_V2` | 91 | V2文件完成校验包 |
| `COMMAND_C2C_PREPARE_RESP` | 92 | C2C准备响应返回目标目录 |
## 五、核心特性
- **断点续传**: 基于 transferID + 文件特征匹配,支持传输中断后恢复
- **大文件支持**: 使用 uint64_t 存储文件大小和偏移突破4GB限制
- **自动目标目录**: C2C传输自动捕获目标客户端当前焦点窗口的目录
- **自动粘贴**: C2C文本传输完成后自动模拟 Ctrl+V 完成粘贴
- **版本协商**: 能力位 `CLIENT_CAP_V2` + 版本日期双重检测
- **文件完整性校验**: 传输完成后发送 SHA-256 校验包,接收端自动验证
- **服务端开关**: 参数菜单可控制是否启用 V2默认关闭
- **自动清理**: 状态文件超过 7 天自动删除
## 5.1 文件完整性校验
每个文件传输完成后,发送端计算并发送 `COMMAND_FILE_COMPLETE_V2` 校验包:
```cpp
struct FileCompletePacketV2 {
uint8_t cmd; // = 91
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint8_t sha256[32]; // SHA-256 哈希
}; // 69 bytes
```
**工作流程**:
1. 发送端在发送文件数据同时计算 SHA-256
2. 文件最后一个块发送完成后,发送 `FILE_COMPLETE_V2` 校验包
3. 接收端在接收数据时同步计算 SHA-256流式计算无需重读文件
4. 收到校验包后对比哈希值,输出校验结果
**支持的传输方向**:
| 方向 | 校验位置 |
|------|----------|
| 客户端 → 服务端 | 服务端校验 |
| 服务端 → 客户端 | 客户端校验 |
| 客户端A → 客户端B (C2C) | 服务端转发客户端B校验 |
**校验失败处理**:
- 校验失败时自动删除损坏文件
- 日志输出期望和实际的 SHA-256 哈希值
**断点续传与校验**:
- 断点续传时无法使用流式 SHA-256缺少历史数据
- 自动切换为从文件重新计算完整 SHA-256
## 六、相关文件
| 文件 | 说明 |
|------|------|
| `common/commands.h` | 协议命令定义 |
| `common/file_upload.h/cpp` | V2传输核心实现 |
| `client/KernelManager.cpp` | 客户端主连接处理 |
| `client/ScreenManager.cpp` | 客户端远程桌面会话处理 |
| `server/2015Remote/2015RemoteDlg.cpp` | 服务端主逻辑 |
| `server/2015Remote/ScreenSpyDlg.cpp` | 服务端远程桌面会话处理 |
| `server/2015Remote/CDlgFileSend.cpp` | 文件传输进度对话框 |
## 七、使用说明
### C2C 文件传输
1. 打开两个远程桌面窗口源A和目标B
2. 在源A上选中文件按 Ctrl+C
3. 按 X 切换到目标B
4. 按 Ctrl+V文件自动传输到B当前目录
### C2C 文本传输
1. 在源A上复制文本Ctrl+C
2. 按 X 切换到目标B
3. 在目标B的应用中按 Ctrl+V文本自动粘贴
### 断点续传
- 传输中断后,重新执行相同操作
- 系统自动检测已传输部分,从断点继续
- 状态文件保存在 `%TEMP%\FileTransfer\`
- 状态文件超过 7 天自动删除
### 状态文件说明
| 后缀 | 用途 | 说明 |
|------|------|------|
| `.resume` | 断点续传状态 | 记录 transferID、文件列表、已接收字节数 |
| `.recv` | C2C 接收方目录 | 记录目标客户端当前焦点窗口目录 |
| `.send` | C2C 发送方目录 | 记录源客户端剪贴板文件路径 |
所有状态文件统一存放在 `%TEMP%\FileTransfer\` 目录,文件名格式为 `{transferID}.{后缀}`
---
## 八、代码审查报告
### 8.1 审查范围
| 文件 | 改动行数 | 审查结果 |
|------|:--------:|:--------:|
| `common/commands.h` | +16 | ✅ |
| `common/file_upload.h` | +288 | ✅ |
| `SimplePlugins/file_upload.cpp` | 核心实现 | ✅ |
| `client/KernelManager.cpp` | +294 | ✅ |
| `client/ScreenManager.cpp` | +360 | ✅ |
| `server/2015Remote/2015RemoteDlg.cpp` | +698 | ✅ |
| `server/2015Remote/CDlgFileSend.cpp` | +116 | ✅ |
| `server/2015Remote/context.h` | +34 | ✅ |
### 8.2 代码质量评估
#### 优点
1. **协议设计清晰** - 结构体使用 `#pragma pack(push, 1)` 确保跨平台对齐,注释详尽
2. **版本兼容性** - V1/V2 协议共存,旧客户端仍可使用
3. **能力协商机制完善** - `CLIENT_CAP_V2` 能力位 + 版本日期双重检测
4. **断点续传设计** - `transferID + fileIndex` 组合键唯一标识,状态持久化
5. **线程安全** - 使用 `std::mutex` 保护共享状态(`g_fileStatesV2Mtx` 等)
6. **智能指针** - `std::unique_ptr` 管理内存RAII 处理文件句柄
7. **SHA-256 校验** - 使用 Windows bcrypt API流式计算
#### 待改进项
| 优先级 | 问题 | 位置 | 建议 |
|:------:|------|------|------|
| 🟠 中 | 路径穿越未验证 | `file_upload.cpp:712` | `GetRelativePath` 添加 `..` 检查 |
| 🟠 中 | 变量命名误导 | `file_upload.cpp:2463` | `utf8Name` 实际是 ANSI应改名 |
| 🟡 低 | 重复代码 | `KernelManager.cpp` / `ScreenManager.cpp` | C2C_TEXT 处理逻辑可提取公共函数 |
### 8.3 安全审查
| 检查项 | 状态 | 说明 |
|--------|:----:|------|
| 缓冲区溢出 | ✅ | 有长度校验 `if (len < sizeof(...) + nameLength + dataLength)` |
| 认证校验 | ✅ | 使用 hash + hmac 验证 |
| 路径穿越 | ⚠️ | `GetRelativePath` 未检查 `..` 序列(实际风险低) |
| 客户端ID伪造 | ✅ | 通过 `FindHost` 验证客户端存在 |
| 文件句柄泄漏 | ✅ | RAII 析构函数处理 |
| 整数溢出 | ✅ | 使用 `uint64_t` 支持大文件 |
**路径穿越风险分析**:
- `GetRelativePath` 未过滤 `..` 序列
- 实际风险较低C2C 由服务端键盘钩子触发,非任意用户输入
- 建议后续版本加固
### 8.4 边界条件测试建议
| 场景 | 测试点 |
|------|--------|
| 大文件 | >4GB 文件传输和断点续传 |
| 网络抖动 | 传输中断后恢复 |
| 并发传输 | 多个 C2C 同时进行 |
| 客户端重启 | `.resume` 文件恢复 + SHA-256 重算 |
| 目录结构 | 深层嵌套目录创建 |
| 空文件 | 0 字节文件传输和校验 |
---
## 九、提交评估
### 9.1 总体评估:✅ 可发布
| 指标 | 状态 | 说明 |
|------|:----:|------|
| 功能完整性 | ✅ | C2C/断点续传/SHA-256 校验均已实现 |
| 编译通过 | ✅ | 无编译错误 |
| 向后兼容 | ✅ | V1 协议仍可用,能力协商正确 |
| 崩溃风险 | ✅ | 无明显崩溃点 |
| 数据安全 | ✅ | HMAC 认证、长度校验完善 |
### 9.2 问题分类
| 严重程度 | 数量 | 是否阻塞提交 |
|:--------:|:----:|:------------:|
| 🔴 致命 | 0 | - |
| 🟠 中等 | 2 | 否 |
| 🟡 低危 | 1 | 否 |
### 9.3 后续改进建议
1. **路径安全加固** - 在 `GetRelativePath` 添加 `..` 过滤
2. **重复代码重构** - 提取 C2C_TEXT 处理为公共函数
3. **变量命名修正** - `utf8Name``ansiName`
4. **日志级别控制** - 添加 verbose 模式开关
---
*审查日期: 2026-02-27*

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. **双重检查**:编译时和运行时配置均生效

555
docs/FileTransferV2_Plan.md Normal file
View File

@@ -0,0 +1,555 @@
# 文件传输 V2 协议方案
> 支持 C2C客户端到客户端传输 + 断点续传
## 实施进度
| 阶段 | 内容 | 状态 | 完成日期 |
|------|------|------|---------|
| Phase 1 | V2 结构体 + 接口定义 | ✅ 完成 | 2026-02-23 |
| Phase 2 | V2 基础收发(不含 C2C | ✅ 完成 | 2026-02-23 |
| Phase 3 | 断点续传 | ✅ 完成 | 2026-02-23 |
| Phase 4 | C2C | ✅ 完成 | 2026-02-23 |
| Phase 5 | SHA-256 文件校验 | ✅ 完成 | 2026-02-26 |
| Phase 6 | 能力协商 + 服务端开关 + 缓存整合 | ✅ 完成 | 2026-02-27 |
---
## 一、命令设计
```cpp
// common/commands.h
// 老命令(保持不变)
COMMAND_SEND_FILE = 68, // V1 文件传输
// 新命令V2 独立)
COMMAND_SEND_FILE_V2 = 85, // V2 文件传输(支持 C2C + 断点续传)
COMMAND_FILE_RESUME = 86, // V2 断点续传控制
COMMAND_CLIPBOARD_V2 = 87, // V2 剪贴板请求C2C
COMMAND_FILE_QUERY_RESUME = 88, // V2 断点续传查询
COMMAND_C2C_PREPARE = 89, // C2C 准备接收通知
COMMAND_C2C_TEXT = 90, // C2C 文本传输
COMMAND_FILE_COMPLETE_V2 = 91, // V2 文件完成校验SHA-256
COMMAND_C2C_PREPARE_RESP = 92, // C2C 准备响应(返回目标目录)
// 客户端能力位
#define CLIENT_CAP_V2 0x0001 // 支持 V2 文件传输
// 功能引入日期
#define FILE_TRANSFER_V2_DATE "Feb 27 2026"
```
---
## 二、结构体设计
```cpp
// common/file_upload.h
#pragma pack(push, 1)
// ==================== V1 协议(不改动)====================
struct FileChunkPacket {
uint8_t cmd; // = 68
uint32_t fileIndex;
uint32_t totalNum;
uint64_t fileSize;
uint64_t offset;
uint64_t dataLength;
uint64_t nameLength;
}; // 41 bytes
// ==================== V2 协议(新增,独立)====================
struct FileChunkPacketV2 {
uint8_t cmd; // = 85
uint64_t transferID; // 传输会话ID
uint64_t srcClientID; // 源客户端 (0=主控端)
uint64_t dstClientID; // 目标客户端 (0=主控端)
uint32_t fileIndex; // 文件编号
uint32_t totalFiles; // 总文件数
uint64_t fileSize; // 文件大小
uint64_t offset; // 偏移
uint64_t dataLength; // 数据长度
uint64_t nameLength; // 文件名长度
uint16_t flags; // 标志位
uint16_t checksum; // CRC16
uint8_t reserved[8]; // 预留
}; // 81 bytes
enum FileFlagsV2 : uint16_t {
FFV2_NONE = 0x0000,
FFV2_LAST_CHUNK = 0x0001,
FFV2_RESUME_REQ = 0x0002,
FFV2_RESUME_RESP = 0x0004,
FFV2_CANCEL = 0x0008,
FFV2_DIRECTORY = 0x0010,
FFV2_COMPRESSED = 0x0020,
FFV2_ERROR = 0x0040,
};
struct FileResumePacketV2 {
uint8_t cmd; // = 86
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t flags;
uint16_t rangeCount;
// FileRangeV2 ranges[rangeCount];
}; // 51 bytes + ranges
struct FileRangeV2 {
uint64_t offset;
uint64_t length;
}; // 16 bytes
struct ClipboardRequestV2 {
uint8_t cmd; // = 87
uint64_t srcClientID;
uint64_t dstClientID;
uint64_t transferID;
char hash[64];
char hmac[16];
}; // 105 bytes
struct C2CPreparePacket {
uint8_t cmd; // = 89
uint64_t transferID;
uint64_t srcClientID; // 发送方客户端ID
}; // 17 bytes
struct C2CPrepareRespPacket {
uint8_t cmd; // = 92
uint64_t transferID;
uint64_t srcClientID; // 原始发送方客户端ID
uint16_t pathLength;
// char path[pathLength]; // UTF-8 目标目录
}; // 19 bytes + path
struct FileCompletePacketV2 {
uint8_t cmd; // = 91
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t fileIndex;
uint64_t fileSize;
uint8_t sha256[32]; // SHA-256 哈希
}; // 69 bytes
#pragma pack(pop)
```
---
## 三、接口设计
```cpp
// common/file_upload.h
// ==================== V1 接口(保持不变)====================
int FileBatchTransferWorker(...);
int RecvFileChunk(...);
// ==================== V2 接口(新增)====================
struct TransferOptionsV2 {
uint64_t transferID; // 0=自动生成
uint64_t srcClientID;
uint64_t dstClientID;
bool enableResume; // 启用断点续传
};
int FileBatchTransferWorkerV2(
const std::vector<std::string>& files,
const std::string& targetDir,
void* user,
OnTransformV2 f,
OnFinish finish,
const std::string& hash,
const std::string& hmac,
const TransferOptionsV2& options
);
int RecvFileChunkV2(
char* buf, size_t len,
void* user,
OnFinish f,
const std::string& hash,
const std::string& hmac,
uint64_t myClientID
);
// 断点续传
uint64_t GenerateTransferID();
bool SaveResumeState(uint64_t transferID, ...);
bool LoadResumeState(uint64_t transferID, ...);
void CleanupResumeState(uint64_t transferID);
std::vector<uint64_t> GetPendingTransfers();
// 文件完整性校验
bool HandleFileCompleteV2(
const char* buf, size_t len,
uint64_t myClientID
);
```
---
## 四、持久化设计
### 4.1 状态文件位置
所有状态文件统一存放在:
- Windows: `%TEMP%\FileTransfer\`
- Linux: `/tmp/FileTransfer/`
### 4.2 状态文件类型
| 后缀 | 用途 | 文件名格式 |
|------|------|-----------|
| `.resume` | 断点续传状态 | `{transferID}.resume` |
| `.recv` | C2C 接收方目标目录 | `{transferID}.recv` |
| `.send` | C2C 发送方文件路径 | `{transferID}.send` |
### 4.3 自动清理
- 状态文件超过 7 天自动删除 (`RESUME_EXPIRE_DAYS = 7`)
- 清理在 `GetPendingTransfers()` 时触发
- 三种后缀文件均会被清理
### 4.4 .resume 文件结构
```cpp
struct ResumeFileHeader {
uint32_t magic; // = 0x52455355 "RESU"
uint64_t transferID;
uint64_t srcClientID;
uint64_t dstClientID;
uint32_t totalFiles;
char targetDir[260];
};
struct ResumeFileEntry {
uint64_t fileSize;
uint64_t receivedBytes;
uint16_t rangeCount;
char fileName[260];
// FileRangeV2 ranges[rangeCount];
};
```
### 4.5 .recv / .send 文件结构
简单文本文件,存储 UTF-8 编码的目录路径或文件路径列表(每行一个)。
---
## 五、服务端路由
```cpp
// 2015RemoteDlg.cpp
case COMMAND_SEND_FILE: {
// V1 逻辑(保持不变)
}
case COMMAND_SEND_FILE_V2: {
// V2 逻辑(独立)
FileChunkPacketV2* pkt = (FileChunkPacketV2*)szBuffer;
if (pkt->dstClientID == 0) {
HandleLocalReceiveV2(...);
} else {
// C2C 转发
ForwardToClientV2(...);
}
}
case COMMAND_FILE_RESUME: {
// V2 断点续传
}
```
---
## 六、版本检测
### 6.1 能力位协商
```cpp
// common/commands.h
#define CLIENT_CAP_V2 0x0001 // 支持 V2 文件传输
// LOGIN_INFOR 构造函数
sprintf_s(moduleVersion, "%s-%04X", DLL_VERSION, CLIENT_CAP_V2);
// 结果示例: "Feb 27 2026-0001"
```
### 6.2 服务端检测
```cpp
// 双重检测:能力位 + 版本日期
bool SupportsFileTransferV2(context* ctx) {
// 1. 检查服务端开关
if (!g_2015RemoteDlg || !g_2015RemoteDlg->m_bEnableFileV2) return false;
// 2. 检查能力位
if (ctx->SupportsFileV2()) return true;
// 3. 兼容旧版:检查版本日期
CString version = ctx->GetClientData(ONLINELIST_VERSION);
return IsDateGreaterOrEqual(version, FILE_TRANSFER_V2_DATE);
}
// C2C 要求双方都支持 V2
if (!SupportsFileTransferV2(src) || !SupportsFileTransferV2(dst)) {
// 提示版本不支持
}
```
### 6.3 服务端开关
| 菜单位置 | 默认值 | 配置键 |
|---------|--------|--------|
| 参数 → 文件传输V2 | 关闭 | `settings/EnableFileV2` |
- 未勾选时使用 V1 协议
- 勾选后根据客户端能力选择 V1/V2
- 配置通过 `THIS_CFG` 持久化
---
## 七、行为矩阵
| 场景 | 源版本 | 目标版本 | 协议 | 结果 |
|------|--------|---------|------|------|
| 本地→远程 | - | V1 | V1 | ✅ 正常 |
| 本地→远程 | - | V2 | V2 | ✅ 正常 + 断点续传 |
| 远程→本地 | V1 | - | V1 | ✅ 正常 |
| 远程→本地 | V2 | - | V2 | ✅ 正常 + 断点续传 |
| C2C | V1 | V1 | - | ❌ 不支持 |
| C2C | V1 | V2 | - | ❌ 不支持(提示升级) |
| C2C | V2 | V1 | - | ❌ 不支持(提示升级) |
| C2C | V2 | V2 | V2 | ✅ 正常 + 断点续传 |
---
## 八、改动文件清单
| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `common/commands.h` | 修改 | 新增命令字、能力位 |
| `common/file_upload.h` | 修改 | 新增 V2 结构体和接口 |
| `SimplePlugins/file_upload.cpp` | 修改 | V2 实现、缓存整合、自动清理 |
| `client/ScreenManager.cpp` | 修改 | 新增 case 分支 |
| `client/KernelManager.cpp` | 修改 | 处理 V2 命令 |
| `server/2015Remote/context.h` | 修改 | 新增能力位列、SupportsFileV2() |
| `server/2015Remote/2015RemoteDlg.h` | 修改 | 新增 m_bEnableFileV2、函数声明 |
| `server/2015Remote/2015RemoteDlg.cpp` | 修改 | case 分支、菜单处理、能力解析 |
| `server/2015Remote/resource.h` | 修改 | 新增菜单ID |
| `server/2015Remote/2015Remote.rc` | 修改 | 新增菜单项 |
| `server/2015Remote/CDlgFileSend.cpp` | 修改 | C2C 校验包转发 |
| `server/2015Remote/ScreenSpyDlg.cpp` | 修改 | 剪贴板同步 |
---
## 九、实施记录
### Phase 1: V2 结构体 + 接口定义 ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `common/commands.h`
- 新增 `FEATURE_FILE_V2 = "Mar 1 2026"`
- 新增 `COMMAND_SEND_FILE_V2 = 85`
- 新增 `COMMAND_FILE_RESUME = 86`
- 新增 `COMMAND_CLIPBOARD_V2 = 87`
- [x] `common/file_upload.h`
- 新增 `FileFlagsV2` 枚举
- 新增 `FileChunkPacketV2` 结构体 (81 bytes)
- 新增 `FileRangeV2` 结构体 (16 bytes)
- 新增 `FileResumePacketV2` 结构体 (51 bytes + ranges)
- 新增 `ClipboardRequestV2` 结构体 (105 bytes)
- 新增 `FileErrorV2` 错误码枚举
- 新增 `TransferOptionsV2` 结构体
- 新增 `FileBatchTransferWorkerV2()` 接口声明
- 新增 `RecvFileChunkV2()` 接口声明
- 新增断点续传相关接口声明
---
### Phase 2: V2 基础收发 ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `SimplePlugins/file_upload.cpp` - 实现 `FileBatchTransferWorkerV2()`
- [x] `SimplePlugins/file_upload.cpp` - 实现 `RecvFileChunkV2()`
- [x] `SimplePlugins/file_upload.cpp` - 实现 `GenerateTransferID()`
- [x] `client/ScreenManager.cpp` - 新增 `COMMAND_SEND_FILE_V2` case
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 新增 `COMMAND_SEND_FILE_V2` case
---
### Phase 3: 断点续传 ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `SimplePlugins/file_upload.cpp` - `.resume` 文件头结构体定义
- [x] `SimplePlugins/file_upload.cpp` - 实现 `GetResumeDir()` / `GetResumeFilePath()`
- [x] `SimplePlugins/file_upload.cpp` - 实现 `SaveResumeState()` 保存传输状态
- [x] `SimplePlugins/file_upload.cpp` - 实现 `LoadResumeState()` 恢复传输状态
- [x] `SimplePlugins/file_upload.cpp` - 实现 `CleanupResumeState()` 清理状态文件
- [x] `SimplePlugins/file_upload.cpp` - 实现 `GetPendingTransfers()` 枚举未完成传输
- [x] `SimplePlugins/file_upload.cpp` - `RecvFileChunkV2()` 中定期自动保存状态
- [x] `client/ScreenManager.cpp` - `OnReconnect()` 中检测并恢复未完成传输
- [x] `client/ScreenManager.cpp` - 处理 `COMMAND_FILE_RESUME` 续传控制
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 处理/转发 `COMMAND_FILE_RESUME`
**实现细节**:
- `.resume` 文件存储位置: `%TEMP%\FileTransfer\{transferID}.resume`
- 自动保存触发条件: 每 5 秒或每 1MB 新数据
- 重连时自动恢复本地状态,等待数据继续传输
- 所有文件完成后自动清理 `.resume` 文件
---
### Phase 4: C2C ✅
**状态**: 已完成
**完成时间**: 2026-02-23
**改动文件**:
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 键盘钩子新增静态变量 `remoteCtrlCTime`
- [x] `server/2015Remote/2015RemoteDlg.cpp` - Ctrl+C 时区分本地/远程,记录时间
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 键盘钩子分支[3]远程A→远程B
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 发送 `COMMAND_CLIPBOARD_V2` 触发 C2C
- [x] `client/ScreenManager.cpp` - 处理 `COMMAND_CLIPBOARD_V2`,获取剪贴板并发送到目标
- [x] `SimplePlugins/file_upload.cpp` - 新增 C2C 文件跟踪 (`g_c2cReceivedFiles`)
- [x] `SimplePlugins/file_upload.cpp` - 新增 `SetFilesToClipboard()` 函数
- [x] `SimplePlugins/file_upload.cpp` - `RecvFileChunkV2()` 完成后设置剪贴板
**实现细节**:
- 远程A 按 Ctrl+C → 记录 `operateWnd``remoteCtrlCTime`
- 切换到远程B 按 Ctrl+V → 检测 `operateWnd != dlg` 且时间有效
- 服务端发送 `COMMAND_CLIPBOARD_V2` 到源客户端A
- 客户端A 获取剪贴板文件,使用 V2 协议发送到客户端B
- 客户端B 接收文件,传输完成后自动设置到剪贴板 (CF_HDROP)
---
### Phase 5: SHA-256 文件校验 ✅
**状态**: 已完成
**完成时间**: 2026-02-26
**改动文件**:
- [x] `common/commands.h` - 新增 `COMMAND_FILE_COMPLETE_V2 = 91`
- [x] `common/file_upload.h` - 新增 `FileCompletePacketV2` 结构体 (69 bytes)
- [x] `SimplePlugins/file_upload.cpp` - 实现 `SHA256Context` 类 (Windows bcrypt API)
- [x] `SimplePlugins/file_upload.cpp` - `FileRecvStateV2` 新增 `sha256Ctx` 流式计算
- [x] `SimplePlugins/file_upload.cpp` - 实现 `HandleFileCompleteV2()` 校验函数
- [x] `SimplePlugins/file_upload.cpp` - `FileBatchTransferWorkerV2()` 发送完成后发送校验包
- [x] `client/KernelManager.cpp` - 处理 `COMMAND_FILE_COMPLETE_V2`
- [x] `client/ScreenManager.cpp` - 处理 `COMMAND_FILE_COMPLETE_V2`
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 处理/转发 `COMMAND_FILE_COMPLETE_V2`
- [x] `server/2015Remote/CDlgFileSend.cpp` - 处理/转发 C2C 校验包
**实现细节**:
- 使用 Windows bcrypt API (`BCRYPT_SHA256_ALGORITHM`) 计算 SHA-256
- 接收端在写入数据时同步更新 SHA-256流式计算无需重读文件
- 每个文件传输完成后,发送端发送 `FILE_COMPLETE_V2` 校验包
- 接收端收到校验包后对比本地计算的哈希值
- C2C 场景下服务端负责转发校验包到目标客户端
**支持的传输方向**:
| 方向 | 状态 |
|------|------|
| 客户端 → 服务端 | ✅ 已测试 |
| 服务端 → 客户端 | ✅ 已测试 |
| 客户端A → 客户端B (C2C) | ✅ 已测试 |
---
### Phase 5.1: SHA-256 校验 Bug 修复 ✅
**状态**: 已完成
**完成时间**: 2026-02-26
**修复的问题**:
| 问题 | 原因 | 修复 |
|------|------|------|
| C2C 校验包未转发 | `CDlgFileSend` 未检查 `dstClientID` | 检查并转发到目标客户端 |
| 断点续传校验失败 | 从 `.resume` 恢复时 `sha256Valid` 仍为 true | 恢复时设为 false强制从文件重算 |
| 已完成状态被复用 | 内存匹配未排除已完成的状态 | 增加 `receivedBytes < fileSize` 条件 |
| 不同目录文件被误匹配 | C2C 续传按文件名匹配,忽略目标目录 | 匹配时包含目标目录 |
**改动文件**:
- [x] `server/2015Remote/CDlgFileSend.cpp` - C2C 校验包转发
- [x] `SimplePlugins/file_upload.cpp` - 断点续传时设 `sha256Valid=false`
- [x] `SimplePlugins/file_upload.cpp` - 内存状态匹配跳过已完成
- [x] `SimplePlugins/file_upload.cpp` - C2C 续传匹配包含目标目录
**设计决策**:
- C2C 断点续传匹配时包含目标目录,避免跨目录误匹配
- 断点续传时从文件重新计算 SHA-256无法恢复流式上下文
- 校验失败自动删除损坏文件
---
### Phase 6: 能力协商 + 服务端开关 + 缓存整合 ✅
**状态**: 已完成
**完成时间**: 2026-02-27
**改动内容**:
1. **能力位协商**
- `LOGIN_INFOR` 构造函数在版本字符串后附加能力位(格式:`Feb 27 2026-0001`
- `context.h` 新增 `ONLINELIST_CAPABILITIES` 列、`SupportsFileV2()` 方法
- `AddList()` 解析能力位并存储到列表项
2. **服务端开关**
- 参数菜单新增 "文件传输V2" 选项(`ID_PARAM_FILE_V2`
- `m_bEnableFileV2` 类成员控制 V2 开关
- 配置通过 `THIS_CFG` 持久化到 `settings/EnableFileV2`
- `SupportsFileTransferV2()` 统一判断函数
3. **缓存目录整合**
- 原目录:
- `%TEMP%\FileTransfer\` (.resume)
- `%LOCALAPPDATA%\ServerD11\c2c_recv_targets\` (.target)
- `%LOCALAPPDATA%\ServerD11\c2c_targets\` (.target)
- 整合后:`%TEMP%\FileTransfer\` 统一存放 `.resume``.recv``.send`
- 移除 `GetC2CTargetDir()` 函数
- `CleanupExpiredStateFiles()` 处理三种文件类型
4. **自动清理**
- `RESUME_EXPIRE_DAYS = 7`
- `GetPendingTransfers()` 时触发清理
- 检查 `ftLastWriteTime` 判断过期
**改动文件**:
- [x] `common/commands.h` - 新增 `CLIENT_CAP_V2`
- [x] `server/2015Remote/context.h` - 新增 `ONLINELIST_CAPABILITIES``SupportsFileV2()`
- [x] `server/2015Remote/2015RemoteDlg.h` - 新增 `m_bEnableFileV2`、声明 `SupportsFileTransferV2()`
- [x] `server/2015Remote/2015RemoteDlg.cpp` - 实现菜单处理、能力解析、判断函数
- [x] `server/2015Remote/resource.h` - 新增 `ID_PARAM_FILE_V2`
- [x] `server/2015Remote/2015Remote.rc` - 新增菜单项
- [x] `SimplePlugins/file_upload.cpp` - 整合缓存目录、自动清理

261
docs/MultiLayerLicense.md Normal file
View File

@@ -0,0 +1,261 @@
# 多层授权方案
## 概述
SimpleRemoter 采用**多层授权架构**,专为软件分销场景设计。本文档分为两部分:
- **普通用户**:请阅读 [第一部分](#第一部分普通用户指南)
- **代理商/开发者**:请阅读 [第二部分](#第二部分代理商开发者指南)
> **重要**:使用本软件前,请务必阅读 [合法合规使用](#合法合规使用) 章节。
---
# 第一部分:普通用户指南
本部分面向最终用户,帮助您快速了解如何使用授权。
## 快速开始
### 激活步骤
1. **获取授权**:从您的服务商获取授权信息
2. **导入授权**:将授权信息填入软件设置
3. **自动激活**:软件将自动完成验证
4. **开始使用**:激活成功后即可正常使用
### 日常使用
- **自动续期**:授权到期前系统会自动处理,无需您操作
- **离线可用**:已激活的设备在有效期内可离线使用
- **设备绑定**:授权与您的设备绑定,更换设备需联系服务商
## 常见问题
### 授权到期后会怎样?
软件会自动尝试续期。如果您的服务商已续费,授权将自动延长,无需任何操作。
### 更换电脑后授权还能用吗?
授权与设备绑定,更换设备需要联系您的服务商重新授权。
### 网络断开时能否使用?
可以。已激活的设备在有效期内可正常离线使用。首次激活需要网络连接。
### 并发数是什么意思?
并发数是您的授权允许同时管理的受管设备数量上限。例如,并发数为 256 表示最多同时管理 256 台设备。
### 遇到问题怎么办?
请联系您的服务商(即向您提供授权的人/公司)获取支持。
---
# 第二部分:代理商/开发者指南
本部分面向第一层用户(代理商、系统集成商、独立开发者),帮助您了解如何独立运营和管理下级用户。
## 核心优势
### 完全独立运营
作为第一层用户,您将获得**完全独立**的运营能力:
| 特性 | 说明 |
|------|------|
| **离线验证** | 您的 V2 授权支持完全离线验证,无需连接上级服务器 |
| **离线续期** | 续期也可通过离线方式完成,不依赖网络 |
| **零回调设计** | 您的服务器不会与上级服务器产生任何通信 |
| **完全自主** | 可自由进行二次开发、定制化修改,打造专属版本 |
| **独立控制** | 您的所有下级用户只连接到您的服务器,由您完全掌控 |
| **独立定价** | 自主决定客户授权价格和有效期 |
```
┌───────────────────────────────────────────────────────────┐
│ 独立运营架构 │
├───────────────────────────────────────────────────────────┤
│ │
│ 上级开发商 您的服务器 │
│ │ │ │
│ │ 一次性授权 │ 完全独立 │
│ │ (可离线完成) │ 无需连接上级 │
│ ▼ ▼ │
│ ┌───────┐ ┌───────┐ │
│ │ 开发商 │ ──── 离线授权 ──▶ │ 您 │ │
│ └───────┘ └───┬───┘ │
│ │ │
│ │ 您的客户 │
│ │ 只连接您的服务器 │
│ ▼ │
│ ┌───────────────┐ │
│ │ 您的客户群 │ │
│ │ (完全由您控制) │ │
│ └───────────────┘ │
│ │
└───────────────────────────────────────────────────────────┘
```
> **承诺**:我们尊重您的独立性。您获得授权后,您的业务数据、客户信息完全由您自己掌控,不会经过我们的服务器。
## 授权体系
### 层级结构
```
超级管理员 (软件开发商)
│ 签发 V2 授权 (支持离线验证)
第一层 (您 - 代理商/开发者)
│ 签发 V1 授权
第二层及以下 (您的客户)
```
### V1 与 V2 授权区别
| 类型 | 验证方式 | 适用对象 | 特点 |
|------|----------|----------|------|
| V2 授权 | 离线验证 (数字签名) | 第一层用户 | 无需联网,完全独立 |
| V1 授权 | 在线验证 (HMAC) | 最终用户 | 需连接您的服务器验证 |
### 授权参数
| 参数 | 说明 |
|------|------|
| 序列号 (SN) | 设备唯一标识,自动生成 |
| 有效期 | 授权的起止日期 |
| 并发数 | 允许同时连接的受管设备数量 |
| 授权码 | 加密的授权凭证 |
| 数字证书 | 上级签发的信任凭证 (Authorization) |
## 快速上手
1. **获取代理授权**:从软件开发商获取 V2 授权凭证
2. **离线激活**:导入授权后即可离线使用
3. **配置服务器**:部署您自己的授权验证服务
4. **生成客户授权**:使用授权工具为客户创建 V1 授权
5. **分发给客户**:客户配置后将只连接到您的服务器
## 适用场景
- **软件代理商**:分销软件,赚取差价
- **系统集成商**:整合到您的解决方案中
- **独立开发者**:基于本软件进行定制开发
- **企业 IT 部门**:内部部署,统一管理
## 安全特性
### 密码学保障
采用 **ECDSA P-256** 椭圆曲线数字签名算法:
- 与 HTTPS、主流数字货币使用相同级别的加密技术
- 目前没有已知的有效攻击方法
- 暴力破解在计算上不可行
### 多层验证机制
```
验证流程:
设备绑定检查 ──────▶ 失败 ──▶ 拒绝
▼ 通过
时效性检查 ────────▶ 失败 ──▶ 拒绝
▼ 通过
数字签名验证 ──────▶ 失败 ──▶ 拒绝
▼ 通过
授权链完整性检查 ──▶ 失败 ──▶ 拒绝
▼ 通过
授权成功
```
### 防护能力
| 攻击方式 | 防护措施 | 效果 |
|----------|----------|------|
| 伪造授权 | 数字签名验证 | 无法伪造 |
| 复制授权 | 设备绑定 | 无法复制到其他设备 |
| 修改有效期 | 签名保护 | 修改后签名失效 |
| 替换验证模块 | 模块互锁 | 触发异常检测 |
### 给潜在破解者的说明
> 破解本软件所需的时间和技术成本,远超购买正版授权的费用。与其花费大量精力在可能失败的尝试上,不如购买正版,获得稳定可靠的使用体验和技术支持。
## 代理商常见问题
### 我的授权会被撤销吗?
正常合规使用情况下不会。授权撤销仅针对严重违规行为(如用于违法用途)。
### 续期如何操作?
支持离线续期。您可以通过离线方式获取新的授权凭证,无需服务器联网。
### 客户更换设备怎么处理?
您可以使用授权工具为客户重新生成授权,绑定到新设备。
### 我的客户数据安全吗?
完全安全。您的客户只连接到您的服务器,所有数据由您掌控,不经过任何第三方。
---
# 合法合规使用
**本章节适用于所有用户,请务必阅读。**
## 使用须知
本软件为**远程管理工具**,旨在帮助用户合法管理自有设备或经授权的设备。使用时必须遵守:
1. **合法授权**:仅在您拥有或获得明确授权的设备上使用
2. **知情同意**:确保被管理设备的所有者/使用者知晓并同意
3. **遵守法律**:遵守您所在地区关于远程访问、数据隐私的法律法规
4. **正当用途**:仅用于合法的 IT 管理、技术支持、设备监控等目的
## 禁止行为
以下行为被严格禁止,违反者将承担全部法律责任:
- 未经授权访问他人计算机或网络
- 用于窃取数据、监视他人隐私
- 传播恶意软件或进行网络攻击
- 任何违反当地法律法规的行为
## 免责声明
1. **工具中立性**:本软件是中立的技术工具,其用途取决于使用者。开发者提供的是合法的远程管理解决方案。
2. **使用者责任**:用户对使用本软件的行为及其后果承担全部责任。开发者不对任何滥用、非法使用或不当使用导致的损失负责。
3. **法律合规**:购买和使用本软件即表示您确认将仅用于合法目的,并承诺遵守所有适用的法律法规。
4. **代理商责任**:代理商在分发授权时,有责任告知最终用户上述使用须知。
5. **技术支持范围**:我们仅为合法合规的使用场景提供技术支持。
> **警告**:非法入侵计算机信息系统在大多数国家和地区属于刑事犯罪,可能面临严重的法律后果,包括罚款和监禁。
---
# 技术支持
如有授权相关问题:
- **最终用户**:请联系您的服务商
- **代理商/开发者**:请联系软件开发商
---
*本文档仅为概要说明,具体实现细节受知识产权保护。*
*使用本软件即表示您已阅读、理解并同意本文档所述的所有条款。*

668
docs/NetworkSetup.md Normal file
View File

@@ -0,0 +1,668 @@
# YAMA 多级网络搭建指南
> 构建总控 → 二级 → 受管端的监控网络
---
## 目标读者
- 需要搭建多级监控网络的客户
- 有多个下级代理/分支机构需要管理
- 需要统一管理和分级授权
---
## 第一部分:架构概述
### 1. 网络拓扑
```
您(总控/一级)
├── 二级控制端 A ──→ 受管端 A1, A2, A3...
├── 二级控制端 B ──→ 受管端 B1, B2, B3...
└── 二级控制端 C ──→ 受管端 C1, C2, C3...
```
### 2. 角色说明
| 角色 | 职责 | 权限 |
|------|------|------|
| 总控(您) | 管理二级、分配授权、提供 FRP 代理 | 完整权限 |
| 二级控制端 | 管理受管端、使用远程功能 | 受限于您的授权 |
| 受管端 | 被远程管理的电脑 | 无主动权限 |
### 3. 授权链
授权采用链式传递机制:
```
您的上级 → 授权给您
您 → 授权给二级
二级(可选)→ 授权给三级
```
**关键限制:**
- 下级有效期不能超过您的有效期
- 下级的并发数限制不能超过上级为您设置的限制
- 下级的功能权限不超过您的权限
---
## 第二部分:准备工作
### 4. 服务器规划
根据您的资源和需求,选择以下方案之一:
#### 方案 A集中式推荐新手
```
您的服务器(公网)
│ [FRPS 服务 :7000]
│ [映射端口 :20001, :20002, ...]
├── 二级 A通过 FRP 端口 20001 接收受管端连接)
├── 二级 B通过 FRP 端口 20002 接收受管端连接)
└── 二级 C通过 FRP 端口 20003 接收受管端连接)
```
**特点:**
- 您有 1 台公网服务器
- 所有二级通过 FRP 连接您的服务器
- 二级无需公网 IP
- 您负责维护 FRP 服务
**适用场景:** 二级客户没有技术能力或公网服务器
#### 方案 B分布式
```
您的服务器 二级 A 服务器 二级 B 服务器
│ │ │
│ [监听端口] [监听端口]
│ │ │
└─────授权─────→ A B
│ │
受管端 A1-An 受管端 B1-Bn
```
**特点:**
- 您和各二级都有自己的公网服务器
- 各二级直接使用自己的公网 IP
- 性能更好,不依赖您的服务器
**适用场景:** 二级有自己的服务器资源
#### 方案 C混合式
```
您的服务器
│ [FRPS 服务]
├── 二级 A ─(FRP)─┘ ← 无服务器,使用 FRP
└── 二级 B有服务器← 直接使用自己的 IP
受管端 B1-Bn
```
**特点:** 灵活配置,根据各二级实际情况分配
### 5. 端口规划
建议预先规划端口分配:
| 用途 | 建议端口 | 说明 |
|------|---------|------|
| 主控监听 | 6543 | 受管端连接端口 |
| FRPS 服务 | 7000 | FRP 服务器监听 |
| 二级映射范围 | 20000-29999 | 为各二级分配的端口 |
**容量估算:**
- 端口范围 20000-29999 可容纳约 10000 个二级
- 实际建议每台服务器不超过 500 个二级(带宽考虑)
### 6. 信息收集
在开始配置之前,收集各二级的信息:
| 信息 | 用途 | 获取方式 |
|------|------|---------|
| 设备序列号 | 生成授权 | 二级运行 YAMA 后获取 |
| 计划并发数 | 授权配置 | 与二级协商确定 |
| 有无公网服务器 | 决定是否需要 FRP | 询问二级 |
| 使用期限 | 授权有效期 | 与二级协商确定 |
---
## 第三部分:配置 FRP 服务
> 如果所有二级都有公网服务器,可跳过本部分
### 7. 系统要求
FRP 服务功能需要满足以下条件:
| 要求 | 说明 |
|------|------|
| 操作系统 | Windows 10 / Server 2016 或更高版本 |
| 架构 | 64 位 |
| 网络 | 服务器具有公网 IP |
> **注意**32 位系统或 Windows 7/Server 2012 等旧系统不支持 FRP 功能。
### 8. 启用 FRP 代理功能
#### 8.1 打开配置界面
点击菜单栏 **扩展****下级FRP代理设置**
#### 8.2 启用服务
勾选 **"启用为下级提供 FRP 代理"**
#### 8.3 选择 FRPS 模式
**方式 A本地运行 FRPS推荐**
勾选 **"FRPS 运行在本机"**
| 优点 | 说明 |
|------|------|
| 简单 | 无需额外部署 FRPS |
| 集成 | 程序自动管理 FRPS 进程 |
| 免配置 | 无需手动配置 FRPS |
**方式 B使用外部 FRPS**
如果您已有 FRPS 服务器:
1. 取消勾选 "FRPS 运行在本机"
2. 填写 FRPS 服务器地址
3. 填写 FRPS 端口
4. 填写认证 Token需与 FRPS 配置一致)
#### 8.4 设置端口和认证
| 设置项 | 说明 | 建议值 |
|--------|------|-------|
| FRPS 端口 | FRPS 服务监听端口 | 7000 |
| 认证 Token | FRPS 认证密钥 | 使用复杂字符串 |
#### 8.5 设置分配范围
| 设置项 | 说明 | 建议值 |
|--------|------|-------|
| 起始端口 | 分配给二级的端口起始 | 20000 |
| 结束端口 | 分配给二级的端口结束 | 29999 |
每个二级占用 1 个端口,端口在范围内自动递增分配。
### 9. 保存并启动
1. 点击"确定"保存配置
2. 如选择本地 FRPS程序会自动启动 FRPS 服务
3. 状态栏中间会显示 FRPS 信息(如 `FRPS :7000`
> **显示优先级**:如果您同时有上级配置的 FRP 和本机 FRPS状态栏优先显示上级配置的 FRP 地址。
### 10. 开放防火墙
FRPS 服务需要开放以下端口:
| 端口 | 协议 | 用途 |
|------|------|------|
| 7000或您配置的端口 | TCP | FRPS 服务端口 |
| 20000-29999或您配置的范围 | TCP | 二级映射端口 |
**Windows 防火墙设置:**
1. 打开"Windows Defender 防火墙" → "高级设置"
2. "入站规则" → "新建规则"
3. 选择"端口" → "TCP"
4. 输入端口:`7000, 20000-29999`
5. 选择"允许连接" → 完成
**云服务器安全组:**
在云控制台添加入站规则:
- TCP 7000FRPS
- TCP 20000-29999映射范围
---
## 第四部分:为二级生成授权
### 11. 获取二级序列号
#### 11.1 二级操作步骤
指导二级完成以下操作:
1. 下载并运行 YAMA.exe首次运行会提示未授权属正常现象
2. 点击菜单 **其他****申请授权**
3. 首次会显示使用条款,确认本软件仅限合法正当使用
4. 点击"确认"后显示序列号
5. 复制序列号并发送给您
> **提示**:导入授权后,该菜单会变为 **其他** → **序列号**
**序列号格式:** `XXXX-XXXX-XXXX-XXXX`
#### 11.2 序列号传递建议
- 微信/QQ直接复制发送
- 邮件:注明"YAMA 序列号"
- 截图:包含完整序列号
### 12. 打开授权生成界面
点击菜单栏 **工具****口令生成**
### 13. 填写授权信息
#### 13.1 基本信息
| 字段 | 说明 | 示例 |
|------|------|------|
| 序列号 | 二级提供的设备序列号 | b40f-638f-ebc8-6d54 |
| 备注 | 便于识别的说明(可选) | "张三 - 华东区" |
#### 13.2 有效期设置
| 字段 | 说明 |
|------|------|
| 起始日期 | 授权生效日期 |
| 结束日期 | 授权过期日期 |
**常见期限设置:**
| 类型 | 期限 | 说明 |
|------|------|------|
| 试用 | 7-15 天 | 短期验证 |
| 月付 | 1 个月 | 按月收费 |
| 季付 | 3 个月 | 按季收费 |
| 年付 | 12 个月 | 按年收费 |
> **重要**:结束日期不能超过您自己授权的有效期。
#### 13.3 并发数设置
设置二级可同时管理的受管端数量。
**注意事项:**
- 为二级设置的并发数限制不能超过上级为您设置的限制
- 可根据二级需求灵活分配
### 14. 分配 FRP 端口(如需要)
如果二级需要使用您的 FRP 代理:
#### 14.1 勾选 FRP 代理
在授权生成界面勾选 **"FRP 代理"** 选项
#### 14.2 端口分配方式
| 方式 | 说明 | 推荐场景 |
|------|------|---------|
| 自动分配 | 系统自动选择可用端口 | 大多数情况 |
| 手动指定 | 自己输入端口号 | 特殊需求 |
#### 14.3 记录分配信息
建议记录二级与端口的对应关系:
| 二级 | 序列号 | 分配端口 | 有效期 |
|------|-------|---------|-------|
| 张三 | b40f-... | 20001 | 2026-12-31 |
| 李四 | a12c-... | 20002 | 2026-12-31 |
### 15. 生成授权
1. 确认所有信息无误
2. 点击"生成"按钮
3. 授权信息保存到本地数据库
### 16. 发送授权给二级
有两种方式将授权发送给二级:
#### 方式 A在线发送推荐
如果二级使用您通过 **工具****主控生成** 分发的程序:
1. 二级程序已硬编码您的地址,启动后会自动连接到您
2. 您在主机列表中可以看到已连接但未授权的二级
3. 右键点击该主机 → **发送授权**
4. 选择已生成的授权记录
5. 通知二级重启程序
**二级重启流程(如授权包含 FRP**
- 第一次重启:验证授权,提示"授权成功",同时收到 FRP 配置
- 第二次重启:应用 FRP 配置,开始使用 FRP 代理
**优点:** 无需传输文件,操作简便
#### 方式 B离线发送lic 文件)
如果二级无法先连接到您(如网络原因),可导出授权文件:
1. 在授权生成界面点击"导出"按钮
2. 生成 `*.lic` 文件并发送给二级
3. 二级通过 **工具****导入口令...** 导入
4. 重启程序使授权生效
**文件命名建议:** `二级名称_日期.lic`,如 `张三_20260407.lic`
---
## 第五部分:二级部署
### 17. 二级获取授权
根据您选择的授权发送方式:
#### 方式 A在线接收推荐
如果二级使用您分发的程序:
1. 运行您分发的程序,自动连接到您
2. 等待您通过右键菜单发送授权
3. 收到授权后重启程序,提示"授权成功"
4. 如授权包含 FRP 配置,需再次重启以应用 FRP
#### 方式 B离线导入lic 文件)
如果收到 lic 文件:
1. 运行 YAMA.exe
2. 点击菜单 **工具****导入口令...**
3. 选择 `*.lic` 文件
4. 看到"导入成功"提示后重启程序
5. 如授权包含 FRP 配置,需再次重启以应用 FRP
### 18. 二级验证
二级需要验证以下内容:
#### 18.1 授权状态
查看状态栏显示的:
- 有效期信息
- 并发数限制
#### 18.2 FRP 连接(如有)
如果授权包含 FRP 配置:
- 状态栏应显示 FRP 连接地址
- 连接成功会显示分配的端口
#### 18.3 地址和端口
检查设置页面(菜单栏 **菜单 → 设置**
- 地址应为 FRP 服务器地址或自有公网地址
- 端口应为分配的 FRP 端口或自有监听端口
### 19. 二级生成受管程序
二级需要生成自己的受管程序:
1. 打开 **工具****主控生成**
2. 地址和端口已自动填入(来自 FRP 配置或设置)
3. 选择载荷类型
4. 生成并分发给目标电脑
**重要**:二级生成的受管程序会连接到二级的地址端口,而非您的服务器。
---
## 第六部分:日常管理
### 20. 监控网络状态
#### 20.1 查看二级在线状态
二级程序运行时会连接到您这里进行授权验证,您可以在主机列表中看到已连接的二级数量。
**主机列表显示内容:**
- 直接连接到您的受管端
- 已连接的二级控制端
#### 20.2 FRP 连接监控
如需查看更详细的连接记录,可查看 FRP 日志:
- 日志位置:程序目录下的 `frps.log`
- 包含二级连接/断开的时间记录
### 21. 授权管理
#### 21.1 查看已发放授权
点击菜单 **工具****授权管理**
列表显示:
- 序列号
- 备注
- 有效期
- 并发数
- FRP 端口(如有)
#### 21.2 授权续期
**一级给二级续期:**
1. 在列表中找到需要续期的授权
2. 双击或点击"编辑"
3. 修改结束日期并保存
**二级更新授权:**
- 二级重启程序即可自动获取新的有效期(通过网络验证)
- 通常**无需重新发送 lic 文件**
> **注意**只有当二级的序列号SN发生变化时才需要重新生成并发送授权文件。
---
#### 21.3 全链条续期(开发者续期一级)
如果您是从开发者获得授权的一级用户,当开发者给您续期后:
**自动续期机制:**
```
开发者给一级续期
一级重启程序V2 验证获取新的 Authorization
二级连接一级时,自动获取新的 Authorization
全链条有效期自动延长
```
**关键点:**
| 层级 | 授权来源 | 续期方式 |
|------|---------|---------|
| 一级 | 开发者V2 ECDSA | 开发者续期后,一级重启程序自动获取 |
| 二级 | 一级V1 HMAC + Authorization | 一级更新后,二级重启程序自动获取 |
**双重控制机制:**
二级用户同时受两个授权控制:
1. **一级的 V1 HMAC 授权** - 一级直接签发给二级
- 控制二级是否能连接到一级
- 有效期由一级设定
2. **开发者的 Authorization** - 通过一级传递给二级
- 控制整个授权链的最终有效期
- 有效期由开发者设定
两个授权都必须有效,二级才能正常运行。
**续期示例:**
| 场景 | 一级 V1 有效期 | Authorization 有效期 | 二级状态 |
|------|--------------|---------------------|---------|
| 正常 | 2027-12-31 | 2027-12-31 | ✅ 正常运行 |
| 一级未给二级续期 | 2026-12-31 (过期) | 2027-12-31 | ❌ 连接失败 |
| 开发者未给一级续期 | 2027-12-31 | 2026-12-31 (过期) | ❌ 验证失败 |
> **提示**:当开发者给一级续期后,一级需要重启程序以获取新的 Authorization。之后二级重启程序即可自动获取更新。
#### 21.4 授权撤销
如需撤销二级授权:
1. 在列表中选择目标授权
2. 点击"删除"或"禁用"
3. 二级下次连接验证时将失效
### 22. 故障处理
| 问题 | 排查步骤 |
|------|---------|
| 二级无法连接 FRP | 1. 检查 FRPS 是否运行<br>2. 检查防火墙<br>3. 检查端口是否正确 |
| 授权显示无效 | 1. 确认序列号匹配<br>2. 检查有效期<br>3. 重新导出导入 |
| 并发数超限 | 1. 检查当前连接数<br>2. 升级授权或释放连接 |
---
## 第七部分:扩展网络
### 23. 添加新二级
重复第四部分步骤:
1. 获取新二级序列号
2. 生成授权(端口自动递增)
3. 导出并发送
4. 指导二级部署
### 24. 二级发展三级
如果您的授权允许,二级也可以发展下级:
```
您(一级)
└── 二级 A
├── 三级 A1 ──→ 受管端
└── 三级 A2 ──→ 受管端
```
**限制条件:**
- 需要您的授权支持多级
- 三级的权限不超过二级
- 二级需要为三级提供 FRP如需要
### 25. 网络容量规划
| 规模 | 建议配置 |
|------|---------|
| 小型(<50 二级) | 单服务器即可 |
| 中型50-200 二级) | 考虑带宽升级 |
| 大型(>200 二级) | 分布式部署,多服务器 |
**带宽估算:**
- 每个活跃远程桌面1-5 Mbps
- 每个空闲连接:<10 Kbps
- 建议预留 50% 余量
---
## 附录
### A. 部署检查清单
**总控端**
- [ ] YAMA.exe 已部署
- [ ] 您的授权已导入
- [ ] 防火墙已开放主控端口
- [ ] FRPS 已启动(如需要)
- [ ] 防火墙已开放 FRPS 端口范围
**二级控制端**
- [ ] 二级已获取序列号
- [ ] 您已生成授权
- [ ] 授权文件已发送给二级
- [ ] 二级已导入授权并重启
- [ ] 二级 FRP 已连接(如需要)
- [ ] 二级已生成受管程序
**受管端**
- [ ] 受管程序已生成
- [ ] 已传输到目标电脑
- [ ] 已成功上线
### B. 网络拓扑示例
**完整示例:集中式 FRP 方案**
```
┌──────────────────────────────────────────────────┐
│ 您的服务器 │
│ 公网 IP: 1.2.3.4 │
│ │
│ ┌─────────┐ ┌─────────┐ ┌───────────────┐ │
│ │ YAMA │ │ FRPS │ │ 防火墙 │ │
│ │ (主控) │ │ :7000 │ │ 开放端口说明 │ │
│ └─────────┘ └─────────┘ │───────────────│ │
│ │ │ │ 6543 主控端口│ │
│ │ │ │ 7000 FRPS端口│ │
│ │ │ │ 20000+ 映射端口│ │
│ │ │ └───────────────┘ │
└───────┼──────────────┼───────────────────────────┘
│ │
│ │
─────┴──────────────┴─────────────────
互联网
──────────────────────────────────────
│ │ │
│ │ │
┌───────┴───┐ ┌─────┴─────┐ ┌─────┴─────┐
│ 二级 A │ │ 二级 B │ │ 二级 C │
│ FRP :20001 │ │ FRP :20002 │ │ FRP :20003 │
└───────┬───┘ └─────┬─────┘ └─────┬─────┘
│ │ │
┌────┴────┐ ┌────┴────┐ ┌────┴────┐
│ 受管端 │ │ 受管端 │ │ 受管端 │
│ A1-A10 │ │ B1-B20 │ │ C1-C5 │
└─────────┘ └─────────┘ └─────────┘
```
### C. 常见问题
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 二级无法连接 FRP | 端口未开放 | 检查防火墙和安全组,开放 7000 和映射端口 |
| 授权显示无效 | 序列号不匹配 | 确认二级发送的序列号正确 |
| 并发数超限 | 超过授权限制 | 升级授权或减少同时在线的受管端数量 |
| FRP 连接不稳定 | 网络问题 | 检查服务器带宽,考虑分布式部署 |
| 二级生成的受管端连不上 | 地址端口错误 | 检查二级设置,确认 FRP 配置正确 |
---
## 技术支持
| 渠道 | 联系方式 |
|------|---------|
| QQ | 962914132 |
| Telegram | [@doge_grandfather](https://t.me/doge_grandfather) |
---
## 相关文档
- [快速部署指南](QuickStart.md) - 首次部署
- [日常使用手册](UserManual.md) - 远程管理功能详解
- [代理商运营手册](AgentManual.md) - 授权管理详解
- [定制化开发指南](CustomizationGuide.md) - 二次开发与品牌定制

View File

@@ -0,0 +1,431 @@
# 通知功能设计方案
## 概述
在主对话框菜单"菜单(&F)"下增加"通知(&N)"功能,支持配置 SMTP 邮件服务,当主机上线且匹配指定条件时自动发送邮件通知。
## 系统要求
- **PowerShell 5.1+**: 内置于 Windows 10 / Server 2016+
- Windows 7/8.1 需手动安装 WMF 5.1
- 不支持 PowerShell 的系统将禁用此功能(对话框控件灰显)
## 核心特性
1. **SMTP 配置**: 支持 Gmail 等主流邮件服务(需应用专用密码)
2. **触发规则**: 主机上线时匹配指定列的文本
3. **备注优先**: 匹配"计算机名"列时,优先使用备注(如有)
4. **多关键词匹配**: 分号分隔,任一匹配即触发(大小写不敏感)
5. **频率控制**: 同一主机 60 分钟内只通知一次
6. **线程安全**: 使用 mutex 保护配置访问
7. **异步发送**: 邮件发送不阻塞主线程
8. **多语言支持**: 支持简体中文、英文、繁体中文
---
## 数据结构
### 常量定义
```cpp
#define NOTIFY_COOLDOWN_MINUTES 15 // 同一主机通知冷却时间(分钟)
```
### 通知类型枚举
```cpp
enum NotifyTriggerType {
NOTIFY_TRIGGER_NONE = 0,
NOTIFY_TRIGGER_HOST_ONLINE = 1, // 主机上线
// 未来可扩展:
// NOTIFY_TRIGGER_HOST_OFFLINE = 2,
// NOTIFY_TRIGGER_FILE_TRANSFER = 3,
};
```
### 单条通知规则
```cpp
struct NotifyRule {
bool enabled; // 是否启用
NotifyTriggerType triggerType; // 触发类型
int columnIndex; // 列编号 (0-based)
std::string matchPattern; // 匹配字符串,分号分隔
};
```
### SMTP 配置
```cpp
struct SmtpConfig {
std::string server; // smtp.gmail.com
int port; // 587
bool useSSL; // true
std::string username; // 发件人邮箱
std::string password; // 应用专用密码 (XOR 加密存储)
std::string recipient; // 收件人 (可选,为空则发给自己)
// 获取实际收件人
std::string GetRecipient() const {
return recipient.empty() ? username : recipient;
}
};
```
### 完整通知配置
```cpp
struct NotifyConfig {
SmtpConfig smtp;
std::vector<NotifyRule> rules; // 规则列表,支持多条
// 频率控制:记录每个主机最后通知时间 (仅内存,不持久化)
std::unordered_map<uint64_t, time_t> lastNotifyTime;
};
```
---
## 配置对话框 UI
```
┌─ 通知设置 ──────────────────────────────────────────────────┐
│ │
│ ⚠️ Warning: Requires Windows 10 or later with PowerShell │
│ 5.1+ (仅不支持时显示) │
│ │
│ ┌─ SMTP 配置 ────────────────────────────────────────────┐ │
│ │ 服务器: [smtp.gmail.com ] 端口: [587 ] │ │
│ │ ☑ 使用 SSL/TLS │ │
│ │ 用户名: [your@gmail.com ] │ │
│ │ 密码: [**************** ] │ │
│ │ (Gmail 需使用应用专用密码) │ │
│ │ 收件人: [ ] [测试] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌─ 通知规则 ────────────────────────────────────────────┐ │
│ │ ☑ 启用通知 │ │
│ │ 触发条件: [主机上线 ▼] │ │
│ │ 匹配列: [3 - 计算机名 ▼] │ │
│ │ 关键词: [CEO;CFO;财务;服务器 ] │ │
│ │ (多个关键词用分号分隔,匹配任一项即触发通知) │ │
│ │ 提示: 同一主机 60 分钟内仅通知一次 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ [确定] [取消] │
└──────────────────────────────────────────────────────────────┘
```
**备注说明**: 收件人为空时,邮件发送给发件人自己(自我通知)。
---
## 邮件内容格式
邮件使用 HTML 格式发送 (`-BodyAsHtml`)。
### 主题
```
[SimpleRemoter] Host Online: WIN-PC01 matched "服务器"
```
### 正文 (HTML)
```html
<b>Host Online Notification</b><br><br>
Trigger Time: 2026-03-11 15:30:45<br>
Match Rule: ComputerName contains "服务器"<br><br>
<b>Host Information:</b><br>
&nbsp;&nbsp;IP Address: 192.168.1.100<br>
&nbsp;&nbsp;Location: 中国-北京<br>
&nbsp;&nbsp;Computer Name: WIN-SERVER-01<br>
&nbsp;&nbsp;OS: Windows Server 2022<br>
&nbsp;&nbsp;Version: 1.2.7<br>
<br>--<br><i>This email was sent automatically by SimpleRemoter</i>
```
---
## 配置持久化
**存储位置**: `%APPDATA%\YAMA\notify.ini`
```ini
[SMTP]
Server=smtp.gmail.com
Port=587
UseSSL=1
Username=your@gmail.com
Password=<XOR加密后的字符串>
Recipient=
[Rule_0]
Enabled=1
TriggerType=1
ColumnIndex=3
MatchPattern=CEO;CFO;服务器
; 未来扩展多规则
;[Rule_1]
;Enabled=1
;...
```
**注意**: `lastNotifyTime` 仅保存在内存中,服务端重启后清空。
---
## 核心逻辑
### PowerShell 检测
```cpp
bool DetectPowerShellSupport() {
std::string cmd = "powershell -NoProfile -Command "
"\"Get-Command Send-MailMessage -ErrorAction SilentlyContinue\"";
DWORD exitCode = 1;
ExecutePowerShell(cmd, &exitCode, true); // hidden
return (exitCode == 0);
}
```
### 频率控制 + 匹配检查 (线程安全)
```cpp
bool NotifyManager::ShouldNotify(context* ctx, std::string& outMatchedKeyword,
const CString& remark)
{
if (!m_powerShellAvailable) return false;
std::lock_guard<std::mutex> lock(m_mutex);
const NotifyRule& rule = m_config.GetRule();
if (!rule.enabled) return false;
if (rule.matchPattern.empty()) return false;
if (!m_config.smtp.IsValid()) return false;
uint64_t clientId = ctx->GetClientID();
time_t now = time(nullptr);
// 冷却检查
auto it = m_config.lastNotifyTime.find(clientId);
if (it != m_config.lastNotifyTime.end()) {
if (now - it->second < NOTIFY_COOLDOWN_MINUTES * 60) {
return false;
}
}
// 获取匹配文本 (计算机名列优先使用备注)
CString colText;
if (rule.columnIndex == ONLINELIST_COMPUTER_NAME && !remark.IsEmpty()) {
colText = remark;
} else {
colText = ctx->GetClientData(rule.columnIndex);
}
if (colText.IsEmpty()) return false;
// 大小写不敏感匹配
std::string colLower = ToLower(CT2A(colText, CP_UTF8));
for (const auto& kw : SplitString(rule.matchPattern, ';')) {
std::string kwLower = ToLower(Trim(kw));
if (!kwLower.empty() && colLower.find(kwLower) != std::string::npos) {
outMatchedKeyword = Trim(kw);
m_config.lastNotifyTime[clientId] = now;
return true;
}
}
return false;
}
```
### 异步邮件发送
```cpp
void NotifyManager::SendNotifyEmailAsync(const std::string& subject,
const std::string& body)
{
if (!m_powerShellAvailable) return;
SmtpConfig smtp;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_config.smtp.IsValid()) return;
smtp = m_config.smtp;
}
std::thread([this, smtp, subject, body]() {
std::ostringstream ps;
ps << "powershell -NoProfile -ExecutionPolicy Bypass -Command \"";
ps << "$pass = ConvertTo-SecureString '" << EscapePowerShell(smtp.password)
<< "' -AsPlainText -Force; ";
ps << "$cred = New-Object PSCredential('" << smtp.username << "', $pass); ";
ps << "Send-MailMessage ";
ps << "-From '" << smtp.username << "' ";
ps << "-To '" << smtp.GetRecipient() << "' ";
ps << "-Subject '" << EscapePowerShell(subject) << "' ";
ps << "-Body '" << EscapePowerShell(body) << "' ";
ps << "-SmtpServer '" << smtp.server << "' ";
ps << "-Port " << smtp.port << " ";
if (smtp.useSSL) ps << "-UseSsl ";
ps << "-Credential $cred -Encoding UTF8 -BodyAsHtml\"";
DWORD exitCode;
ExecutePowerShell(ps.str(), &exitCode, true); // hidden
}).detach();
}
```
---
## 集成点
`2015RemoteDlg.cpp``AddList()` 函数中:
```cpp
// 现有代码
m_ClientIndex[id] = m_HostList.size();
m_HostList.push_back(ContextObject);
// ========== 新增:通知检查 (带异常保护) ==========
try {
std::string matchedKeyword;
CString remark = m_ClientMap->GetClientMapData(ContextObject->GetClientID(), MAP_NOTE);
if (GetNotifyManager().ShouldNotify(ContextObject, matchedKeyword, remark)) {
std::string subject, body;
GetNotifyManager().BuildHostOnlineEmail(ContextObject, matchedKeyword, subject, body);
GetNotifyManager().SendNotifyEmailAsync(subject, body);
}
} catch (...) {
TRACE("[Notify] Exception in notification check\n");
}
// =================================================
ShowMessage(_TR("操作成功"), ...);
```
初始化在 `OnInitDialog()` 中:
```cpp
m_ClientMap->LoadFromFile(GetDbPath());
// 初始化通知管理器
GetNotifyManager().Initialize();
```
---
## 文件改动清单
| 文件 | 改动类型 | 说明 |
|------|---------|------|
| `resource.h` | 修改 | 新增菜单ID、对话框ID、控件ID (33028-33029, 332, 2408-2432) |
| `2015Remote.rc` | 修改 | 添加菜单项、对话框模板、右键菜单"上线提醒" |
| `NotifyConfig.h` | **新增** | 配置结构体定义 |
| `NotifySettingsDlg.h` | **新增** | 配置对话框类声明 (继承 CDialogLangEx) |
| `NotifySettingsDlg.cpp` | **新增** | 配置对话框实现,显式设置控件文本支持翻译 |
| `NotifyManager.h` | **新增** | 通知管理器声明 (单例,线程安全) |
| `NotifyManager.cpp` | **新增** | 检测、发送、配置读写逻辑 |
| `2015RemoteDlg.h` | 修改 | 添加 `OnMenuNotifySettings()`, `OnOnlineLoginNotify()` 方法 |
| `2015RemoteDlg.cpp` | 修改 | 菜单处理 + 上线时调用检查 + 右键快捷添加主机 |
| `lang/en_US.ini` | 修改 | 添加英文翻译 |
| `lang/zh_TW.ini` | 修改 | 添加繁体中文翻译 |
**实际代码量**: 约 1400+ 行
---
## 多语言支持
### 语言文件编码
**重要**: 语言文件使用 **GB2312** 编码,不是 UTF-8
- 位置: `server/2015Remote/lang/*.ini`
- 格式: `简体中文=翻译文本`
### 源文件编码
含中文字符的 C++ 源文件必须使用 **UTF-8 with BOM**,否则 MSVC 会报错 `C2001: 常量中有换行符`
### 翻译示例
```ini
; en_US.ini
通知设置=Notify Settings
SMTP 配置=SMTP Configuration
测试邮件发送成功!=Test email sent successfully!
测试邮件发送失败请检查SMTP配置=Test email failed. Please check SMTP settings.
; zh_TW.ini
通知设置=通知設定
SMTP 配置=SMTP 配置
测试邮件发送成功!=測試郵件發送成功!
```
---
## 测试邮件
- **成功**: 显示 "测试邮件发送成功!"
- **失败**: 显示 "测试邮件发送失败请检查SMTP配置"
- 详细错误信息使用 `TRACE()` 记录到调试日志
---
## 安全考虑
| 风险 | 处理方式 |
|------|---------|
| 密码明文存储 | XOR 混淆后存储 (非安全加密,仅防止直接可见) |
| PowerShell 注入 | 转义单引号 `'``''`,转义换行 |
| 邮件发送失败 | 静默失败,异步执行不影响主流程 |
| 并发访问 | 使用 `std::mutex` 保护配置读写 |
| 通知异常 | try-catch 包裹,异常不影响主机上线 |
---
## 列编号与匹配说明
| 列编号 | 列名称 | 匹配说明 |
|--------|--------|----------|
| 0 | IP地址 | 匹配原始 IP |
| 1 | 地址 | 匹配端口号 |
| 2 | 地理位置 | 匹配位置信息 |
| 3 | 计算机名 | **优先匹配备注**,无备注时匹配计算机名 |
| 4 | 操作系统 | 匹配 OS 信息 |
| 5 | CPU | 匹配 CPU 信息 |
| 6 | 摄像头 | 匹配有/无 |
| 7 | 延迟 | 匹配 RTT 值 |
| 8 | 版本 | 匹配客户端版本 |
| 9 | 安装时间 | 匹配安装时间 |
| 10 | 活动窗口 | 匹配当前窗口标题 |
| 11 | 客户端类型 | 匹配类型标识 |
---
## 使用示例
1. 打开菜单 "菜单(&F)" → "通知(&N)"
2. 配置 SMTP:
- 服务器: `smtp.gmail.com`
- 端口: `587`
- 勾选 SSL/TLS
- 用户名: 你的 Gmail 地址
- 密码: [Gmail 应用专用密码](https://myaccount.google.com/apppasswords)
3. 点击"测试"验证配置
4. 配置规则:
- 勾选"启用通知"
- 选择"计算机名"列
- 输入关键词: `CEO;CFO;财务部`
5. 点击确定保存
当有主机上线且其备注或计算机名包含任一关键词时,你将收到邮件通知。
### 快捷添加主机
右键点击在线主机列表中的主机,选择"上线提醒"可快速将该主机添加到通知关键词列表:
- 自动使用主机备注(如有)或计算机名作为关键词
- 自动去重,已存在的主机不会重复添加
- 添加后自动启用通知规则

327
docs/QuickStart.md Normal file
View File

@@ -0,0 +1,327 @@
# YAMA 快速部署指南
> 10 分钟完成首次部署
---
## 目标读者
- 已有公网服务器的客户
- 首次使用 YAMA
- 希望快速验证功能
---
## 开始之前
### 您需要准备
| 项目 | 说明 |
|------|------|
| 公网服务器 | Windows 服务器,能从外网访问 |
| 服务器公网 IP | 受管端将连接此地址 |
| 授权文件 | `*.lic` 文件,由您的上级提供 |
| YAMA.exe | 主控程序,从上级或官方渠道获取 |
### 系统要求
| 场景 | 操作系统要求 |
|------|-------------|
| 使用 FRP 代理 | Windows 10 / Server 2016 或更高版本64位 |
| 不使用 FRP自有公网服务器 | Windows 7 / Server 2008 R2 或更高版本 |
| 受管端(被控电脑) | Windows 7 或更高版本 |
> **提示**:如果您的授权文件包含 FRP 配置,说明您将使用 FRP 代理,请确保使用 64 位的 Windows 10 或更高版本。
---
## 第一步部署主控端3 分钟)
### 1.1 上传程序
`YAMA.exe` 上传到您的服务器,建议放置到独立目录:
```
C:\YAMA\
└── YAMA.exe
```
程序运行后会自动创建以下数据:
- **配置信息** - 存储在注册表 `HKCU\Software\YAMA`
- **数据文件** - 存储在 `%APPDATA%\YAMA\` 目录(如 `licenses.db` 授权数据库)
- **语言文件** - 程序同目录下的 `lang\` 文件夹(可选)
### 1.2 首次运行
双击运行 `YAMA.exe`,首次运行时程序会提示**未授权**状态,部分功能不可用。
> 如果您使用的是上级分发的程序,程序会自动连接到上级,您可以直接等待上级在线发送授权,跳过以下序列号获取步骤。
**获取序列号(离线授权方式):**
1. 点击菜单 **其他****申请授权**
2. 首次会显示使用条款,确认本软件仅限合法正当使用
3. 点击"确认"后显示序列号(格式:`XXXX-XXXX-XXXX-XXXX`
4. 复制序列号发送给上级以获取授权
> **提示**:导入授权后,该菜单会变为 **其他** → **序列号**
### 1.3 获取授权
根据您上级的分发方式,有两种获取授权的方式:
**方式 A在线接收如使用上级分发的程序**
1. 运行上级分发给您的程序,自动连接到上级
2. 等待上级通过在线方式发送授权
3. 收到授权后重启程序,提示"授权成功"
4. 如授权包含 FRP 配置,需再次重启以应用 FRP
**方式 B离线导入如收到 lic 文件)**
1. 点击菜单栏 **工具****导入口令...**
2. 选择您的 `*.lic` 授权文件
3. 看到"导入成功"提示后,**关闭并重新启动程序**
4. 如授权包含 FRP 配置,需再次重启以应用 FRP
### 1.4 验证授权状态
重启后,检查窗口底部的状态栏:
| 状态栏位置 | 显示内容 |
|-----------|---------|
| 左侧 | 当前分组主机数 / 总主机数 |
| 中间 | FRP 连接地址(如授权包含 FRP 配置) |
| 右侧 | 运行信息和授权信息(有效期、并发数) |
如果状态栏右侧显示授权有效期信息,说明授权已生效。
---
## 第二步配置网络2 分钟)
### 2.1 检查地址设置
1. 点击菜单栏 **菜单****设置**
2. 查看"公网地址"栏位
**地址设置说明:**
| 情况 | 地址显示 | 说明 |
|------|---------|------|
| 使用 FRP 代理 | FRP 服务器地址(自动填入) | 受管端将连接 FRP 服务器 |
| 自有公网服务器 | 您服务器的公网 IP | 受管端直连您的服务器 |
> **提示**:如果您的授权包含 FRP 配置,地址和端口会自动从授权文件中读取,通常无需手动修改。
### 2.2 确认监听端口
默认监听端口为 `6543`。如需修改,请在设置页面更改。
### 2.3 开放防火墙
**Windows 防火墙:**
1. 打开"Windows Defender 防火墙"
2. 点击"高级设置"
3. 选择"入站规则" → "新建规则"
4. 选择"端口" → "TCP" → 输入端口号(如 `6543`
5. 选择"允许连接" → 完成
**云服务器安全组(阿里云/腾讯云等):**
1. 登录云控制台
2. 找到安全组设置
3. 添加入站规则:协议 TCP端口 `6543`(或您设置的端口)
> **注意**:如果使用 FRP 代理,受管端连接的是 FRP 服务器而非您的服务器,此时您的服务器防火墙无需开放监听端口。
---
## 第三步生成受管程序2 分钟)
### 3.1 打开生成对话框
点击菜单栏 **工具****主控生成**
### 3.2 确认连接参数
生成对话框会自动填入地址和端口:
| 参数 | 来源 |
|------|------|
| IP 地址 | 自动从 FRP 配置或设置中读取 |
| 端口 | 自动从 FRP 配置或设置中读取 |
> **重要**:请确认这些参数正确。受管端将使用这些参数连接主控。
### 3.3 选择载荷类型
| 类型 | 适用场景 | 推荐度 |
|------|---------|-------|
| ghost.exe | 独立运行程序,便于测试 | **新手推荐** |
| TestRun - Windows 服务 | 开机自启,稳定运行 | **生产环境推荐** |
| TestRun - 内存DLL | 内存运行,隐蔽性更好 | 进阶 |
| ghost.exe - Windows 服务 | 开机自启,独立程序 | 备选 |
**首次使用建议选择 "ghost.exe"**,便于测试和排查问题。
**生产环境建议选择 "TestRun - Windows 服务"**,支持开机自启且运行稳定。
### 3.4 生成文件
1. 确认参数无误后,点击"确定"
2. 选择保存位置
3. 记录生成的文件路径
生成完成后,在指定位置会看到受管程序文件。
---
## 第四步部署受管端3 分钟)
### 4.1 传输文件
将生成的受管程序文件复制到目标电脑。传输方式可以是:
- U 盘拷贝
- 网络共享
- 远程桌面拖拽
- 其他文件传输方式
### 4.2 运行受管程序
在目标电脑上双击运行受管程序。
**运行后的预期行为:**
- TestRun / ghost.exe拷贝自身到目标目录 → 创建开机启动项 → 自我删除原文件 → 后台运行
- 服务类型:需要管理员权限,会注册为 Windows 服务并自动启动
### 4.3 验证连接
回到主控端,查看主机列表:
- 等待几秒钟后,主机列表中出现新主机即表示部署成功
**如果受管端未上线,请检查:**
| 可能原因 | 解决方案 |
|---------|---------|
| 网络不通 | 检查目标电脑能否访问主控地址和端口 |
| 防火墙拦截 | 确认主控端口已开放 |
| IP/端口错误 | 重新生成受管程序,确认参数正确 |
| FRP 未启动 | 检查 FRP 服务状态 |
| 文件被占用 | 目标电脑已部署过旧版本,新文件无法覆盖。用任务管理器结束占用进程后重新部署(重启无效,旧版本会自动运行) |
---
## 第五步:测试远程功能
### 5.1 远程桌面
1. 在主机列表中**右键单击**在线主机,选择"远程桌面"(或点击工具栏图标)
2. 将打开远程桌面窗口
3. 测试鼠标移动和点击
4. 测试键盘输入
**画质调整**:点击窗口左上角系统菜单图标,选择"屏幕质量"调整画面质量。
### 5.2 文件管理
1. 选中在线主机,点击**工具栏**的文件管理图标
2. 双栏界面:上方本地、下方远程
3. 测试文件上传和下载
### 5.3 远程终端
1. 选中在线主机,点击**工具栏**的远程终端图标
2. 输入命令测试(如 `dir``ipconfig`
---
## 部署完成
恭喜!您已成功完成 YAMA 的首次部署。
### 快速回顾
```
主控端部署
├── 上传 YAMA.exe
├── 导入授权(工具 → 导入口令)
├── 重启程序
└── 验证授权状态
网络配置
├── 检查地址设置(菜单 → 设置)
└── 开放防火墙端口
生成受管程序
├── 打开生成(工具 → 主控生成)
├── 确认参数
└── 选择类型并生成
部署受管端
├── 传输文件到目标电脑
├── 运行程序
└── 验证上线
```
### 下一步
| 需求 | 推荐文档 |
|------|---------|
| 搭建多级网络(管理下级) | [多级网络搭建指南](NetworkSetup.md) |
| 了解所有远程功能 | [日常使用手册](UserManual.md) |
| 为下级分配授权 | [代理商运营手册](AgentManual.md) |
| 定制化开发 | [定制化开发指南](CustomizationGuide.md) |
---
## 常见问题速查
### 授权相关
| 问题 | 可能原因 | 解决方案 |
|------|---------|---------|
| 导入授权后无变化 | 未重启程序 | 关闭程序后重新启动 |
| 授权文件无法导入 | 文件损坏或格式错误 | 联系上级重新获取授权文件 |
| 授权显示已过期 | 有效期已到 | 联系上级续期 |
### 连接相关
| 问题 | 可能原因 | 解决方案 |
|------|---------|---------|
| 受管端无法上线 | 防火墙阻止 | 检查并开放对应端口 |
| 受管端无法上线 | 地址或端口错误 | 重新生成受管程序,确认参数 |
| FRP 连接失败 | 网络问题 | 检查网络,确认能访问 FRP 服务器 |
| FRP 连接失败 | 授权不包含 FRP | 联系上级获取包含 FRP 的授权 |
| 连接不稳定 | 网络波动 | 检查网络质量 |
### 远程桌面相关
| 问题 | 可能原因 | 解决方案 |
|------|---------|---------|
| 画面卡顿 | 带宽不足 | 降低画质(系统菜单 → 屏幕质量) |
| 画面黑屏 | 受管端问题 | 重启受管端程序 |
| 无法控制 | 权限问题 | 确认受管端以管理员运行 |
---
## 技术支持
如遇到无法解决的问题,请通过以下渠道联系技术支持:
| 渠道 | 联系方式 |
|------|---------|
| QQ | 962914132 |
| Telegram | [@doge_grandfather](https://t.me/doge_grandfather) |
---
## 相关文档
- [多级网络搭建指南](NetworkSetup.md) - 构建总控 → 二级 → 受管端的网络
- [日常使用手册](UserManual.md) - 远程管理功能详解
- [代理商运营手册](AgentManual.md) - 下级授权管理与 FRP 代理配置
- [定制化开发指南](CustomizationGuide.md) - 二次开发与品牌定制

321
docs/TestPlan.md Normal file
View File

@@ -0,0 +1,321 @@
# SimpleRemoter 测试计划
本文档定义了项目的测试策略、优先级和实施路线图。
## 测试目标
1. **发现潜在隐患** - 边界条件、异常情况、内存问题
2. **回归保护** - 确保修改不引入新 bug
3. **提升代码质量** - 通过测试驱动改进代码
## 模块概览
### 代码复杂度分析
| 模块 | 客户端文件 | 服务端文件 | LOC | 复杂度 |
|------|-----------|-----------|-----|--------|
| 网络核心 | IOCPClient.cpp | IOCPServer.cpp | 1500+ | 高 |
| 文件传输 | FileManager.cpp | FileManagerDlg.cpp | 4400+ | 高 |
| 远程桌面 | ScreenManager.cpp | ScreenSpyDlg.cpp | 4700+ | 高 |
| 缓冲区 | Buffer.cpp | Buffer.cpp | 500+ | 中 ✅ 已测试 |
| 系统管理 | SystemManager.cpp | SystemDlg.cpp | 800+ | 中 |
| 终端 | ShellManager/ConPTY | ShellDlg/TerminalDlg | 600+ | 中 |
| 注册表 | RegisterManager.cpp | RegisterDlg.cpp | 400+ | 低 |
| 服务管理 | ServicesManager.cpp | ServicesDlg.cpp | 400+ | 低 |
### 协议数据结构
| 结构体 | 文件 | 大小 | 用途 |
|--------|------|------|------|
| FileChunkPacket | file_upload.h | 41B | V1 文件传输 |
| FileChunkPacketV2 | file_upload.h | 77B | V2 文件传输 |
| FileResumePacketV2 | file_upload.h | 49B+ | 断点续传 |
| FileCompletePacketV2 | file_upload.h | 69B | 完成校验 |
| C2CPreparePacket | file_upload.h | 17B | C2C 准备 |
| HeaderFlag | header.h | - | 包头标识 |
| ZstaHeader | ZstdArchive.h | - | 压缩头 |
## 测试优先级
### P0 - 关键(必须测试)
这些是系统核心,问题会导致功能完全失效:
| 模块 | 测试类型 | 测试重点 | 用例数 | 状态 |
|------|---------|---------|--------|------|
| Buffer | 单元测试 | 读写、边界、下溢 | 73 | ✅ 完成 |
| 协议编解码 | 单元测试 | 数据包序列化/反序列化 | 58 | ✅ 完成 |
| 协议头验证 | 单元测试 | 包头加密/解密/版本 | 29 | ✅ 完成 |
| 路径处理 | 单元测试 | 路径拼接、相对路径、特殊字符 | 包含在协议测试 | ✅ 完成 |
### P1 - 高优先级
核心功能,问题会严重影响用户体验:
| 模块 | 测试类型 | 测试重点 | 用例数 | 状态 |
|------|---------|---------|--------|------|
| 文件传输逻辑 | 单元测试 | V2协议、分块、传输选项 | 37 | ✅ 完成 |
| 分块管理 | 单元测试 | 范围管理、合并、缺失检测 | 36 | ✅ 完成 |
| SHA-256 校验 | 单元测试 | 流式哈希、完整性验证 | 28 | ✅ 完成 |
| 断点续传 | 单元测试 | 状态序列化、恢复逻辑 | 26 | ✅ 完成 |
| 粘包/分包 | 单元测试 | 数据包边界处理 | 24 | ✅ 完成 |
| HTTP 伪装 | 单元测试 | 协议伪装/解除 | 27 | ✅ 完成 |
| 屏幕捕获 | 单元测试 | 图像压缩、差分算法 | 425 | ✅ 完成 |
### P2 - 中优先级
重要功能,但问题影响范围有限:
| 模块 | 测试类型 | 测试重点 | 预计用例 |
|------|---------|---------|---------|
| 系统管理 | 单元测试 | 进程列表解析 | 20+ |
| 终端 | 单元测试 | 命令解析、输出处理 | 15+ |
| 压缩模块 | 单元测试 | zstd 压缩/解压 | 15+ |
### P3 - 低优先级
辅助功能,可延后测试:
| 模块 | 测试类型 | 测试重点 | 预计用例 |
|------|---------|---------|---------|
| 注册表 | 单元测试 | 路径解析 | 10+ |
| 服务管理 | 单元测试 | 状态解析 | 10+ |
| 音视频 | 集成测试 | 数据流 | 10+ |
## 实施路线图
### Phase 1: 协议层测试 ✅ 已完成
**目标:** 测试所有数据包的序列化/反序列化
**状态:** 131 个测试全部通过
```
test/
└── unit/
├── client/
│ └── BufferTest.cpp # 客户端 Buffer (33 用例) ✅
├── server/
│ └── BufferTest.cpp # 服务端 Buffer (40 用例) ✅
└── protocol/
├── PacketTest.cpp # 文件传输包、命令解析 (39 用例) ✅
└── PathUtilsTest.cpp # 路径处理工具 (19 用例) ✅
```
**发现并修复的问题:**
- Buffer ULONG 下溢漏洞:当 `Skip(len)``len > m_nDataLength` 时导致下溢
**测试覆盖:**
- V2 文件传输包结构 (FileChunkPacketV2, FileResumePacketV2 等)
- 路径处理工具 (相对路径、特殊字符、边界条件)
- Buffer 读写操作、边界处理、线程安全
### Phase 2: 文件传输测试 ✅ 已完成
**目标:** 确保文件传输逻辑正确
**状态:** 127 个测试全部通过
```
test/
└── unit/
└── file/
├── FileTransferV2Test.cpp # V2 传输逻辑 (37 用例) ✅
├── ChunkManagerTest.cpp # 分块管理 (36 用例) ✅
├── SHA256VerifyTest.cpp # 文件校验 (28 用例) ✅
└── ResumeStateTest.cpp # 断点续传状态 (26 用例) ✅
```
**测试覆盖:**
| 类别 | 测试内容 | 用例数 |
|------|---------|--------|
| V2 传输选项 | FFV2_* 标志组合、TransferID 生成 | 12 |
| 数据包构建 | FileChunkPacketV2、FileQueryResumeV2 等 | 25 |
| 范围管理 | 添加、合并、缺失检测、边界条件 | 36 |
| SHA-256 | 流式计算、空数据、大数据、已知向量 | 28 |
| 断点续传 | 状态序列化/反序列化、恢复请求/响应 | 26 |
**关键发现:**
- 测试使用独立实现验证协议设计正确性
- RangeManager 相邻范围自动合并 (0-100 + 100-200 → 0-200)
### Phase 3: 网络通信测试 ✅ 已完成
**目标:** 验证网络层协议处理的正确性
**状态:** 80 个测试全部通过
```
test/
└── unit/
└── network/
├── HeaderTest.cpp # 协议头验证 (29 用例) ✅
├── PacketFragmentTest.cpp # 粘包/分包处理 (24 用例) ✅
└── HttpMaskTest.cpp # HTTP 伪装 (27 用例) ✅
```
**测试覆盖:**
| 类别 | 测试内容 | 用例数 |
|------|---------|--------|
| 协议头 | FLAG 生成、加密/解密、多版本验证 | 29 |
| 粘包/分包 | 完整包、分片、多包粘连、边界条件 | 24 |
| HTTP 伪装 | GET/POST 伪装、解除、边界检测 | 27 |
**协议细节验证:**
- 包头结构: FLAG(8B) + PackedLen(4B) + OrigLen(4B) = 16 bytes
- 位置相关 XOR 加密 (key ^ (i * 31))
- 支持 HeaderEncV0-V6 多版本兼容
### Phase 4: 图像处理测试 ✅ 已完成
**目标:** 验证屏幕捕获和压缩算法
**状态:** 425 个测试全部通过
```
test/
└── unit/
└── screen/
├── DiffAlgorithmTest.cpp # 差分算法 (32 用例) ✅
├── RGB565Test.cpp # RGB565 压缩 (286 用例) ✅
├── ScrollDetectorTest.cpp # 滚动检测 (43 用例) ✅
└── QualityAdaptiveTest.cpp # 质量自适应 (64 用例) ✅
```
**测试覆盖:**
| 类别 | 测试内容 | 用例数 |
|------|---------|--------|
| 差分算法 | 帧比较、差分编码/解码、SSE2 优化验证 | 32 |
| RGB565 | BGRA↔RGB565 转换、量化误差、批量处理 | 286 |
| 滚动检测 | CRC32 行哈希、垂直滚动检测、边缘区域 | 43 |
| 质量自适应 | RTT→等级映射、防抖逻辑、冷却时间 | 64 |
**关键实现细节:**
- RGB565 量化误差: 5-bit 最大误差 76-bit 最大误差 3
- 滚动检测: MIN_SCROLL_LINES=16, MATCH_THRESHOLD=85%
- 质量自适应: 降级 2 次稳定,升级 5 次稳定,分辨率变化冷却 30 秒
### Phase 5: 其他模块 (按需)
根据实际情况逐步扩展测试覆盖。
## 测试基础设施
### 目录结构
```
test/
├── CMakeLists.txt # 构建配置
├── test.bat # Windows 测试脚本
├── unit/ # 单元测试
│ ├── client/ # 客户端测试
│ │ └── BufferTest.cpp # ✅ 已完成
│ ├── server/ # 服务端测试
│ │ └── BufferTest.cpp # ✅ 已完成
│ ├── protocol/ # 协议测试 (Phase 1)
│ ├── file/ # 文件传输测试 (Phase 2)
│ └── screen/ # 屏幕测试 (Phase 4)
├── integration/ # 集成测试
│ └── network/ # 网络测试 (Phase 3)
├── fixtures/ # 测试数据
│ ├── images/ # 测试图像
│ └── files/ # 测试文件
└── mocks/ # Mock 对象
└── MockSocket.h
```
### 测试工具函数
需要创建的辅助函数:
```cpp
// test/TestUtils.h
// 创建临时测试目录
std::wstring CreateTempTestDir();
// 创建测试文件
void CreateTestFile(const std::wstring& path, size_t size);
// 比较文件内容
bool CompareFiles(const std::wstring& file1, const std::wstring& file2);
// 加载测试图像
std::vector<BYTE> LoadTestImage(const std::string& name);
// 模拟网络延迟
void SimulateNetworkDelay(int ms);
```
## 代码覆盖率目标
| 阶段 | 覆盖率目标 | 说明 |
|------|-----------|------|
| Phase 1 | 90%+ | 协议层应接近完全覆盖 |
| Phase 2 | 80%+ | 文件传输核心逻辑 |
| Phase 3 | 70%+ | 网络层关键路径 |
| Phase 4 | 60%+ | 图像处理算法 |
## 持续集成
建议在 GitHub Actions 中自动运行测试:
```yaml
name: Unit Tests
on: [push, pull_request]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Build and Test
run: |
cd test
test.bat rebuild
test.bat run
```
## 当前进度
**总计: 763 个测试用例,全部通过**
| 阶段 | 模块 | 用例数 | 状态 |
|------|------|--------|------|
| Phase 1 | Buffer (客户端+服务端) | 73 | ✅ |
| Phase 1 | 协议层 (Packet + PathUtils) | 58 | ✅ |
| Phase 2 | 文件传输 | 127 | ✅ |
| Phase 3 | 网络通信 | 80 | ✅ |
| Phase 4 | 图像处理 | 425 | ✅ |
| **合计** | | **763** | ✅ |
## 下一步行动
1. **Phase 5: 其他模块 (按需)**
- 系统管理模块测试
- 终端模块测试
- 压缩模块测试
2. **可选扩展**
- 集成测试
- 持续集成配置
## 附录:已发现并修复的问题
| 日期 | 阶段 | 模块 | 问题 | 修复 |
|------|------|------|------|------|
| 2026-03-08 | Phase 1 | Buffer | ULONG 下溢:`Skip(len)` 当 len > m_nDataLength 导致下溢 | 添加防护检查 |
**测试设计说明:**
- Phase 2-3 测试使用独立实现(测试副本)验证协议设计
- 这种方式验证的是协议规范正确性,而非生产代码 bug
- 发现的测试问题:`RangeManagerTest.MergeOutOfOrder` 预期修正(相邻范围自动合并)
---
*文档版本: 2.0*
*最后更新: 2026-03-08*

439
docs/TestingArchitecture.md Normal file
View File

@@ -0,0 +1,439 @@
# SimpleRemoter 测试架构
本文档描述项目的测试架构、框架选择和最佳实践。
## 目录
- [测试框架](#测试框架)
- [目录结构](#目录结构)
- [测试分类](#测试分类)
- [命名规范](#命名规范)
- [编写指南](#编写指南)
- [运行测试](#运行测试)
- [CI/CD 集成](#cicd-集成)
## 测试框架
### Google Test (gtest)
项目采用 [Google Test](https://github.com/google/googletest) 作为主要测试框架。
**选择理由:**
| 特性 | 说明 |
|------|------|
| 功能完整 | 支持断言、Mock、参数化测试、死亡测试 |
| 跨平台 | Windows/Linux 均可使用 |
| VS 集成 | Visual Studio Test Explorer 直接支持 |
| CI 友好 | 输出 JUnit XML 格式CI 系统可直接解析 |
| 社区活跃 | 文档完善,问题易解决 |
### 版本要求
- Google Test: 1.14.0+
- CMake: 3.14+
- C++: 17+ (使用结构化绑定等特性)
- Visual Studio: 2019 或 2022
## 目录结构
```
SimpleRemoter/
├── test/ # 测试根目录
│ ├── CMakeLists.txt # 测试构建配置 (14 个测试可执行文件)
│ ├── test.bat # Windows 测试管理脚本
│ ├── unit/ # 单元测试
│ │ ├── client/ # 客户端模块测试
│ │ │ └── BufferTest.cpp # 客户端 Buffer (33 用例) ✅
│ │ ├── server/ # 服务端模块测试
│ │ │ └── BufferTest.cpp # 服务端 Buffer (40 用例) ✅
│ │ ├── protocol/ # 协议层测试 (Phase 1)
│ │ │ ├── PacketTest.cpp # 文件传输包 (39 用例) ✅
│ │ │ └── PathUtilsTest.cpp # 路径处理 (19 用例) ✅
│ │ ├── file/ # 文件传输测试 (Phase 2)
│ │ │ ├── FileTransferV2Test.cpp # V2 传输逻辑 (37 用例) ✅
│ │ │ ├── ChunkManagerTest.cpp # 分块管理 (36 用例) ✅
│ │ │ ├── SHA256VerifyTest.cpp # SHA-256 校验 (28 用例) ✅
│ │ │ └── ResumeStateTest.cpp # 断点续传状态 (26 用例) ✅
│ │ ├── network/ # 网络通信测试 (Phase 3)
│ │ │ ├── HeaderTest.cpp # 协议头验证 (29 用例) ✅
│ │ │ ├── PacketFragmentTest.cpp # 粘包/分包 (24 用例) ✅
│ │ │ └── HttpMaskTest.cpp # HTTP 伪装 (27 用例) ✅
│ │ └── screen/ # 图像处理测试 (Phase 4)
│ │ ├── DiffAlgorithmTest.cpp # 差分算法 (32 用例) ✅
│ │ ├── RGB565Test.cpp # RGB565 压缩 (286 用例) ✅
│ │ ├── ScrollDetectorTest.cpp # 滚动检测 (43 用例) ✅
│ │ └── QualityAdaptiveTest.cpp # 质量自适应 (64 用例) ✅
│ ├── integration/ # 集成测试 (待实现)
│ ├── mocks/ # Mock 对象 (待实现)
│ └── fixtures/ # 测试数据/夹具 (待实现)
├── docs/
│ └── TestingArchitecture.md # 本文档
└── ...
```
## 测试分类
### 单元测试 (Unit Tests)
测试单个类或函数的独立功能。
**特点:**
- 快速执行(毫秒级)
- 无外部依赖(网络、文件系统等)
- 高覆盖率目标(>80%
**示例:**
```cpp
TEST(BufferTest, WriteBuffer_ValidData_ReturnsTrue) {
CBuffer buffer;
BYTE data[] = {1, 2, 3};
EXPECT_TRUE(buffer.WriteBuffer(data, 3));
EXPECT_EQ(buffer.GetBufferLength(), 3);
}
```
### 集成测试 (Integration Tests)
测试多个组件的协作。
**特点:**
- 测试组件间交互
- 可能涉及真实资源
- 执行时间较长
### 端到端测试 (E2E Tests)
测试完整的用户场景。
**特点:**
- 模拟真实使用场景
- 客户端 ↔ 服务端完整流程
- 执行时间最长
## 命名规范
### 测试文件
```
<ClassName>Test.cpp
BufferTest.cpp, FileManagerTest.cpp
```
### 测试套件
```
<ClassName>Test
TEST(BufferTest, ...)
```
### 测试用例
采用 `MethodName_StateUnderTest_ExpectedBehavior` 格式:
```cpp
// 好的命名
TEST(BufferTest, ReadBuffer_WhenBufferEmpty_ReturnsZero)
TEST(BufferTest, WriteBuffer_ExceedsCapacity_ReallocatesMemory)
TEST(BufferTest, Skip_ExceedsDataLength_ClampsToAvailable)
// 避免的命名
TEST(BufferTest, Test1)
TEST(BufferTest, ReadWorks)
```
## 编写指南
### AAA 模式
所有测试应遵循 Arrange-Act-Assert 模式:
```cpp
TEST(BufferTest, ReadBuffer_PartialRead_ReturnsRequestedLength) {
// Arrange - 准备测试数据和对象
CBuffer buffer;
BYTE writeData[] = {1, 2, 3, 4, 5};
buffer.WriteBuffer(writeData, 5);
// Act - 执行被测操作
BYTE readData[3];
ULONG bytesRead = buffer.ReadBuffer(readData, 3);
// Assert - 验证结果
EXPECT_EQ(bytesRead, 3);
EXPECT_EQ(readData[0], 1);
EXPECT_EQ(readData[1], 2);
EXPECT_EQ(readData[2], 3);
EXPECT_EQ(buffer.GetBufferLength(), 2);
}
```
### 边界条件测试
确保覆盖边界情况:
```cpp
// 空缓冲区
TEST(BufferTest, ReadBuffer_EmptyBuffer_ReturnsZero)
// 零长度操作
TEST(BufferTest, WriteBuffer_ZeroLength_ReturnsTrue)
// 请求超过可用数据
TEST(BufferTest, ReadBuffer_RequestExceedsAvailable_ReturnsAvailableOnly)
// 大数据量
TEST(BufferTest, WriteBuffer_LargeData_HandlesCorrectly)
```
### 参数化测试
对于需要多组数据验证的场景:
```cpp
class BufferReadTest : public ::testing::TestWithParam<std::tuple<size_t, size_t, size_t>> {};
TEST_P(BufferReadTest, ReadBuffer_VariousLengths) {
auto [writeLen, readLen, expectedRead] = GetParam();
CBuffer buffer;
std::vector<BYTE> data(writeLen, 0x42);
buffer.WriteBuffer(data.data(), writeLen);
std::vector<BYTE> result(readLen);
ULONG actual = buffer.ReadBuffer(result.data(), readLen);
EXPECT_EQ(actual, expectedRead);
}
INSTANTIATE_TEST_SUITE_P(
ReadLengths,
BufferReadTest,
::testing::Values(
std::make_tuple(10, 5, 5), // 正常读取
std::make_tuple(5, 10, 5), // 请求超过可用
std::make_tuple(0, 5, 0), // 空缓冲区
std::make_tuple(100, 0, 0) // 零长度读取
)
);
```
### 线程安全测试(服务端 Buffer
```cpp
TEST(ServerBufferTest, ConcurrentReadWrite_NoDataCorruption) {
CBuffer buffer;
std::atomic<bool> running{true};
// 写线程
std::thread writer([&]() {
BYTE data[100];
while (running) {
buffer.WriteBuffer(data, sizeof(data));
}
});
// 读线程
std::thread reader([&]() {
BYTE data[50];
while (running) {
buffer.ReadBuffer(data, sizeof(data));
}
});
std::this_thread::sleep_for(std::chrono::milliseconds(100));
running = false;
writer.join();
reader.join();
// 验证无崩溃即为成功
SUCCEED();
}
```
## 运行测试
### 使用 test.bat (推荐)
Windows 下最简单的方式是使用 `test.bat` 脚本:
```batch
cd test
test.bat build # 构建测试 (14 个可执行文件)
test.bat run # 运行所有测试 (763 个)
test.bat run client # 只运行客户端 Buffer 测试 (33)
test.bat run server # 只运行服务端 Buffer 测试 (40)
test.bat run protocol # 只运行协议测试 (58)
test.bat run file # 只运行文件传输测试 (127)
test.bat run network # 只运行网络通信测试 (80)
test.bat run screen # 只运行图像处理测试 (425)
test.bat run verbose # 详细模式运行
test.bat clean # 清理构建目录
test.bat rebuild # 清理后重新构建
test.bat help # 显示帮助
```
### CMake 手动构建
```bash
cd test
# 配置
cmake -B build -G "Visual Studio 17 2022"
# 构建
cmake --build build --config Release
# 运行所有测试
ctest --test-dir build -C Release --output-on-failure
# 运行特定测试
ctest --test-dir build -C Release -R Client # 客户端测试
ctest --test-dir build -C Release -R Server # 服务端测试
# 详细输出
ctest --test-dir build -C Release -V
```
### 直接运行可执行文件
```batch
:: Phase 1: Buffer 测试
test\build\Release\client_buffer_test.exe
test\build\Release\server_buffer_test.exe
:: Phase 1: 协议测试
test\build\Release\protocol_test.exe
:: Phase 2: 文件传输测试
test\build\Release\file_transfer_test.exe
test\build\Release\chunk_manager_test.exe
test\build\Release\sha256_verify_test.exe
test\build\Release\resume_state_test.exe
:: Phase 3: 网络通信测试
test\build\Release\header_test.exe
test\build\Release\packet_fragment_test.exe
test\build\Release\http_mask_test.exe
:: Phase 4: 图像处理测试
test\build\Release\diff_algorithm_test.exe
test\build\Release\rgb565_test.exe
test\build\Release\scroll_detector_test.exe
test\build\Release\quality_adaptive_test.exe
:: 使用 gtest 过滤器
header_test.exe --gtest_filter="*Encrypt*"
rgb565_test.exe --gtest_filter="*Conversion*"
:: 列出所有测试(不运行)
quality_adaptive_test.exe --gtest_list_tests
:: 输出 XML 报告
protocol_test.exe --gtest_output=xml:report.xml
```
## CI/CD 集成
### GitHub Actions 示例
```yaml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- name: Configure CMake
working-directory: test
run: cmake -B build -G "Visual Studio 17 2022"
- name: Build
working-directory: test
run: cmake --build build --config Release
- name: Run Tests
working-directory: test
run: ctest --test-dir build -C Release --output-on-failure
- name: Upload Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: test-results
path: test/build/Release/*.xml
```
## 覆盖率
### 使用 gcov/lcov (Linux)
```bash
# 配置(启用覆盖率)
cmake -B build -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_CXX_FLAGS="--coverage"
# 构建并运行测试
cmake --build build
ctest --test-dir build
# 生成报告
lcov --capture --directory build --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
```
## 当前测试状态
**总计: 763 个测试用例,全部通过** ✅ (执行时间 < 3 秒)
### 已覆盖模块
| 阶段 | 模块 | 测试文件 | 用例数 | 状态 |
|------|------|----------|--------|------|
| Phase 1 | 客户端 Buffer | `unit/client/BufferTest.cpp` | 33 | ✅ |
| Phase 1 | 服务端 Buffer | `unit/server/BufferTest.cpp` | 40 | ✅ |
| Phase 1 | 协议/数据包 | `unit/protocol/PacketTest.cpp` | 39 | ✅ |
| Phase 1 | 路径处理 | `unit/protocol/PathUtilsTest.cpp` | 19 | ✅ |
| Phase 2 | V2 文件传输 | `unit/file/FileTransferV2Test.cpp` | 37 | ✅ |
| Phase 2 | 分块管理 | `unit/file/ChunkManagerTest.cpp` | 36 | ✅ |
| Phase 2 | SHA-256 校验 | `unit/file/SHA256VerifyTest.cpp` | 28 | ✅ |
| Phase 2 | 断点续传 | `unit/file/ResumeStateTest.cpp` | 26 | ✅ |
| Phase 3 | 协议头验证 | `unit/network/HeaderTest.cpp` | 29 | ✅ |
| Phase 3 | 粘包/分包 | `unit/network/PacketFragmentTest.cpp` | 24 | ✅ |
| Phase 3 | HTTP 伪装 | `unit/network/HttpMaskTest.cpp` | 27 | ✅ |
| Phase 4 | 差分算法 | `unit/screen/DiffAlgorithmTest.cpp` | 32 | ✅ |
| Phase 4 | RGB565 压缩 | `unit/screen/RGB565Test.cpp` | 286 | ✅ |
| Phase 4 | 滚动检测 | `unit/screen/ScrollDetectorTest.cpp` | 43 | ✅ |
| Phase 4 | 质量自适应 | `unit/screen/QualityAdaptiveTest.cpp` | 64 | ✅ |
| | **合计** | | **763** | ✅ |
### 测试分布概览
| 阶段 | 测试类别 | 用例数 | 说明 |
|------|---------|--------|------|
| Phase 1 | Buffer 操作 | 73 | 读写、跳过、边界、线程安全 |
| Phase 1 | 协议解析 | 58 | V2 数据包、路径处理 |
| Phase 2 | 文件传输 | 127 | 分块、续传、校验 |
| Phase 3 | 网络通信 | 80 | 包头、分包、伪装 |
| Phase 4 | 图像处理 | 425 | 差分、RGB565、滚动、自适应 |
### 待测试模块
| 优先级 | 模块 | 说明 |
|-------|------|------|
| 中 | 系统管理 | 进程列表、服务管理 |
| 中 | 终端 | 命令解析、输出处理 |
| 低 | 压缩模块 | zstd 压缩/解压 |
## 参考资料
- [Google Test 官方文档](https://google.github.io/googletest/)
- [Google Mock 入门](https://google.github.io/googletest/gmock_for_dummies.html)
- [C++ 单元测试最佳实践](https://github.com/cpp-best-practices/cppbestpractices/blob/master/08-Considering_Safety.md)

194
docs/UIBranding_Design.md Normal file
View File

@@ -0,0 +1,194 @@
# UI 定制化指南(编译时贴牌)
本指南面向下级开发商,说明如何定制程序品牌。
## 一、定制能力
- 修改程序品牌名称(窗口标题、启动画面、托盘提示、关于对话框)
- 隐藏不需要的菜单项(主菜单和右键菜单)
- 隐藏不需要的工具栏按钮
- 替换图标资源
所有配置在编译时固化,运行时用户无法修改。
## 二、环境要求
- **编译器**Visual Studio 2019 或更高版本
- **项目文件**`server/2015Remote/2015Remote.vcxproj`
## 三、定制流程
```
1. 用 Visual Studio 打开项目
2. 编辑 UIBranding.h 修改品牌名称和隐藏选项
3. 替换 res/ 目录下的图标资源(可选)
4. 编译项目Build → Build Solution
5. 在 Bin/ 目录获取编译结果
```
## 四、品牌名称配置
编辑 `UIBranding.h`(位于 `server/2015Remote/` 目录):
```cpp
// 程序显示名称(窗口标题、关于对话框、系统菜单)
#define BRAND_APP_NAME "MyRemote"
// 启动画面显示名称(通常为大写 logo 风格)
#define BRAND_SPLASH_NAME "MYREMOTE"
// 托盘图标提示文本
#define BRAND_TRAY_TIP "MyRemote 远程管理"
// 版权信息(关于对话框显示)
#define BRAND_COPYRIGHT "Copyright (C) 2024 MyCompany"
// 服务显示名称services.msc 中显示)
#define BRAND_SERVICE_DISPLAY "MyRemote Control Service"
// 崩溃转储文件前缀(生成 前缀_日期时间.dmp
#define BRAND_DUMP_PREFIX "MYREMOTE"
// 默认可执行文件名(文件对话框建议名)
#define BRAND_EXE_NAME "MYREMOTE.exe"
// 许可证文件类型描述(文件对话框过滤器)
#define BRAND_LICENSE_DESC "MyRemote License"
// 许可证文件名前缀(导出时 前缀_设备ID.lic
#define BRAND_LICENSE_PREFIX "MYREMOTE"
```
> **提示**:只需修改引号内的文本,保持其他格式不变。
## 五、隐藏菜单项
`UIBranding.h` 中找到对应的宏,将值从 `0` 改为 `1` 即可隐藏。
```cpp
// 0 = 显示(默认)
// 1 = 隐藏
#define HIDE_MENU_WALLET 1 // 将钱包菜单隐藏
```
### 5.1 主菜单
| 菜单 | 可隐藏项 |
|------|----------|
| 文件 | 参数设置、提醒设置、钱包、网络 |
| 工具 | 输入密码、导入许可证、PE资源编辑、生成授权、生成Master、许可证管理、V2私钥 |
| 工具→ShellCode | C Code、bin、Load Test、混淆、混淆bin、混淆Load Test、AES C、AES bin、AES Load Test |
| 参数 | 键盘记录、登录通知、启用日志、壁纸隐私、V2文件协议、钩子窗口、作为服务运行、以用户身份运行 |
| 扩展 | 历史客户端、备份数据、导入数据、重新加载插件、插件请求、下级FRP |
| 扩展→语言 | 更改语言、选择语言目录 |
| 扩展→IP定位 | QQWry定位、Ip2Region定位 |
| 帮助 | 重要说明、反馈、什么是这个、主控跟踪、请求授权 |
### 5.2 右键菜单
| 分类 | 可隐藏项 |
|------|----------|
| 基本操作 | 发送消息、更新、删除、分享、主机备注、重新分组 |
| 远程桌面 | 虚拟桌面、灰度桌面、远程桌面、H264桌面 |
| 授权管理 | 授权、取消授权、分配给 |
| 监控功能 | 添加监视、登录通知、私有屏幕 |
| 系统操作 | 以管理员运行、卸载、代理、代理端口、标准代理端口、注入记事本 |
| 机器管理 | 关机、重启、注销 |
| 执行命令 | 下载执行、上传执行、测试运行、Ghost执行 |
完整宏名称见 `UIBranding.h` 文件中的注释。
## 六、隐藏工具栏按钮
```cpp
#define HIDE_TOOLBAR_TERMINAL 0 // 终端管理
#define HIDE_TOOLBAR_PROCESS 0 // 进程管理
#define HIDE_TOOLBAR_WINDOW 0 // 窗口管理
#define HIDE_TOOLBAR_DESKTOP 0 // 桌面管理
#define HIDE_TOOLBAR_FILE 0 // 文件管理
#define HIDE_TOOLBAR_AUDIO 0 // 语音管理
#define HIDE_TOOLBAR_VIDEO 0 // 视频管理
#define HIDE_TOOLBAR_SERVICE 0 // 服务管理
#define HIDE_TOOLBAR_REGISTER 0 // 注册表管理
#define HIDE_TOOLBAR_KEYBOARD 0 // 键盘记录
#define HIDE_TOOLBAR_SETTINGS 0 // 参数设置
#define HIDE_TOOLBAR_BUILD 0 // 生成服务端
#define HIDE_TOOLBAR_SEARCH 0 // 搜索
#define HIDE_TOOLBAR_HELP 0 // 帮助
```
## 七、图标定制
图标资源位于 `res/` 目录,完整列表见 `res/README.md`
### 7.1 常用图标
| 文件 | 用途 | 规格 |
|------|------|------|
| `2015Remote.ico` | 主程序图标 | 多尺寸 ICO |
| `cmdshell.ico` | 远程终端窗口 | ICO |
| `screen.ico` | 远程桌面窗口 | ICO |
| `ToolBar_Main.bmp` | 主工具栏 | 48×48 多图拼接 |
| `Bitmap/*.bmp` | 菜单图标 | 16×16 BMP |
### 7.2 替换步骤
1. 准备相同规格的图标文件
2. 替换对应文件(保持文件名不变)
3. 重新编译
> **注意**:工具栏位图为多图横向拼接,替换时保持图标数量和顺序不变。
## 八、定制示例
以下示例展示如何创建一个简化版本,隐藏高级功能:
```cpp
// ===== 只需修改以下内容,其他保持默认 =====
// 品牌名称
#define BRAND_APP_NAME "MyRemote"
#define BRAND_SPLASH_NAME "MYREMOTE"
#define BRAND_TRAY_TIP "MyRemote 远程管理"
#define BRAND_COPYRIGHT "Copyright (C) 2024 MyCompany"
// 隐藏敏感功能(改为 1
#define HIDE_MENU_WALLET 1
#define HIDE_MENU_GEN_AUTH 1
#define HIDE_MENU_GEN_MASTER 1
#define HIDE_MENU_LICENSE_MGR 1
#define HIDE_MENU_V2_PRIVATEKEY 1
// 隐藏工具栏按钮
#define HIDE_TOOLBAR_KEYBOARD 1
#define HIDE_TOOLBAR_REGISTER 1
// 隐藏右键菜单项
#define HIDE_CTX_AUTHORIZE 1
#define HIDE_CTX_UNAUTHORIZE 1
```
## 九、注意事项
### 9.1 编码要求
`UIBranding.h` 如果包含中文,必须保存为 **UTF-8 with BOM** 编码。
### 9.2 子菜单自动清理
当子菜单所有项目都被隐藏时,父级菜单会自动消失。
### 9.3 多语言支持
修改品牌名称后,如需多语言支持,请在 `lang/*.ini` 中添加对应翻译:
```ini
; lang/en_US.ini
MyRemote=MyRemote
MyRemote 远程管理=MyRemote Remote Management
```
### 9.4 运行时状态
部分菜单项有勾选状态(如"键盘记录"),隐藏后状态显示不可用,但不影响程序运行。

576
docs/UserManual.md Normal file
View File

@@ -0,0 +1,576 @@
# YAMA 日常使用手册
> 远程管理功能详解
---
## 目标读者
- 已完成部署的用户
- 需要了解各项远程管理功能
- 日常运维参考
> 首次部署请先阅读[快速部署指南](QuickStart.md)
---
## 第一部分:界面概览
### 1. 主界面布局
YAMA 主界面由以下部分组成:
```
┌─────────────────────────────────────────────────┐
│ 菜单栏(主控、工具、扩展、参数、帮助) │
├─────────────────────────────────────────────────┤
│ 工具栏(常用功能快捷按钮) │
├─────────────────────────────────────────────────┤
│ │
│ │
│ 主机列表区域 │
│ (显示所有受管端信息) │
│ │
│ │
├─────────────────────────────────────────────────┤
│ 状态栏(主机统计 | FRP状态 | 授权信息) │
└─────────────────────────────────────────────────┘
```
### 2. 主机列表
主机列表显示所有已连接的受管端信息:
| 列名 | 说明 |
|------|------|
| IP 地址 | 受管端的网络地址 |
| 计算机名 | 受管端的主机名 |
| 操作系统 | Windows 版本信息 |
| 用户名 | 当前登录用户 |
| 分组 | 自定义分组标签 |
| 备注 | 自定义说明 |
| 上线时间 | 受管端连接时间 |
**列表操作:**
- 单击:选中主机
- 双击:显示主机详细信息
- 右键:显示操作菜单
- Ctrl+单击:多选
- Shift+单击:范围选择
### 3. 右键菜单
右键单击主机列表可打开操作菜单:
| 菜单项 | 功能 |
|--------|------|
| 远程控制 | 远程桌面子菜单 |
| 机器管理 | 注销/关机/重启 |
| 执行程序 | 下载执行、上传执行 |
| 修改备注 | 编辑主机备注 |
| 修改分组 | 更改所属分组 |
> **注意**:文件管理、远程终端、进程管理等功能通过**工具栏图标**打开
---
## 第二部分:远程桌面
### 4. 打开远程桌面
**方式一:右键菜单**
右键单击主机 → 远程控制 → 高速屏幕
**方式二:工具栏**
选中主机后点击工具栏的远程桌面按钮
### 5. 画面质量控制
#### 5.1 打开质量设置
点击远程桌面窗口左上角的系统图标(或按 Alt+空格),选择 **"屏幕质量"**
#### 5.2 质量等级
| 等级 | 帧率 | 分辨率 | 算法 | 适用场景 |
|------|------|--------|------|---------|
| Ultra | 25 FPS | 原始 | DIFF | 局域网办公 |
| High | 20 FPS | 原始 | RGB565 | 一般办公 |
| Good | 20 FPS | 1080P | H264 | 跨网/偶尔视频 |
| Medium | 15 FPS | 900P | H264 | 一般网络 |
| Low | 12 FPS | 720P | H264 | 较差网络 |
| Minimal | 8 FPS | 540P | H264 | 极差网络 |
| 自适应 | 自动 | 自动 | 自动 | **推荐** |
**推荐使用"自适应"模式**,系统会根据网络延迟自动选择最佳画质。
#### 5.3 画质与网络延迟对应
系统根据往返延迟(RTT)自动调整画质,直连和 FRP 代理模式使用不同阈值:
| 等级 | 直连模式 RTT | FRP 代理模式 RTT |
|------|-------------|-----------------|
| Ultra | < 30ms | < 60ms |
| High | 30-80ms | 60-160ms |
| Good | 80-150ms | 160-300ms |
| Medium | 150-250ms | 300-500ms |
| Low | 250-400ms | 500-800ms |
| Minimal | > 400ms | > 800ms |
> FRP 代理模式下,阈值翻倍以适应额外的中转延迟。
### 6. 鼠标键盘操作
#### 6.1 鼠标控制
| 操作 | 说明 |
|------|------|
| 移动 | 鼠标在窗口内移动,远程光标同步移动 |
| 左键单击 | 远程执行左键单击 |
| 右键单击 | 远程执行右键单击 |
| 双击 | 远程执行双击 |
| 滚轮 | 远程执行滚动 |
| 拖拽 | 远程执行拖拽操作 |
#### 6.2 键盘输入
键盘输入会直接发送到远程电脑。
**特殊按键:**
| 按键组合 | 说明 |
|---------|------|
| Ctrl+Alt+Del | 通过系统菜单发送 |
| Windows 键 | 正常发送 |
| Alt+Tab | 在远程电脑切换窗口 |
| Print Screen | 远程截图 |
### 7. 显示选项
#### 7.1 窗口模式
- **普通窗口**:可调整大小,有边框
- **全屏模式**:占满整个屏幕,按 Esc 退出
**切换全屏**:系统菜单 → 全屏模式
#### 7.2 缩放选项
| 选项 | 说明 |
|------|------|
| 自适应缩放 | 自动缩放以适应窗口大小 |
| 原始尺寸 | 1:1 显示,可能需要滚动 |
### 8. 多显示器支持
如果远程电脑有多个显示器:
1. 打开系统菜单
2. 选择"显示器"子菜单
3. 选择要查看的显示器
显示器列表会显示各显示器的分辨率信息。
### 9. 剪贴板同步
远程桌面支持剪贴板双向同步:
| 方向 | 操作 |
|------|------|
| 本地 → 远程 | 在本地复制,在远程窗口中粘贴 |
| 远程 → 本地 | 在远程复制,在本地粘贴 |
**支持内容类型:**
- 文本
- 文件(会自动传输)
---
## 第三部分:文件管理
### 10. 打开文件管理器
选中主机后,点击**工具栏**的文件管理按钮
### 11. 界面布局
文件管理器采用上下双栏布局:
```
┌───────────────────────────────────────────┐
│ 本地文件 - 地址栏 │
├───────────────────────────────────────────┤
│ │
│ 本地文件列表 │
│ │
├───────────────────────────────────────────┤
│ 远程文件 - 地址栏 │
├───────────────────────────────────────────┤
│ │
│ 远程文件列表 │
│ │
├───────────────────────────────────────────┤
│ 传输进度/状态 │
└───────────────────────────────────────────┘
```
### 12. 文件浏览
#### 12.1 目录导航
| 操作 | 方法 |
|------|------|
| 进入目录 | 双击目录 |
| 返回上级 | 点击 ".." 或工具栏返回按钮 |
| 跳转路径 | 在地址栏输入路径后回车 |
| 刷新 | 点击刷新按钮或按 F5 |
#### 12.2 查看选项
| 选项 | 说明 |
|------|------|
| 显示隐藏文件 | 显示以 "." 开头的文件 |
| 详细信息 | 显示大小、修改时间等 |
### 13. 文件传输
#### 13.1 上传文件(本地 → 远程)
**方法一:拖拽**
从上方本地面板拖拽文件到下方远程面板
**方法二:右键菜单**
1. 在本地面板选中文件
2. 右键 → 上传
**方法三:工具栏**
选中文件后点击上传按钮
#### 13.2 下载文件(远程 → 本地)
**方法一:拖拽**
从下方远程面板拖拽文件到上方本地面板
**方法二:右键菜单**
1. 在远程面板选中文件
2. 右键 → 下载
#### 13.3 传输进度
文件传输时底部显示进度条:
- 当前文件名
- 已传输/总大小
- 传输速度
- 剩余时间估算
### 14. 文件操作
| 操作 | 快捷键 | 说明 |
|------|--------|------|
| 新建文件夹 | Ctrl+Shift+N | 在当前目录创建 |
| 重命名 | F2 | 修改文件/目录名 |
| 删除 | Delete | 删除选中项 |
| 复制 | Ctrl+C | 复制到剪贴板 |
| 粘贴 | Ctrl+V | 从剪贴板粘贴 |
### 15. 大文件与断点续传
#### 15.1 大文件支持
支持传输超过 4GB 的大文件,无特殊限制。
#### 15.2 断点续传
如果传输中断:
- 再次传输同一文件时自动检测
- 从断点位置继续传输
- 减少重复传输浪费
**注意**:断点续传适用于文件管理器传输,剪贴板文件传输不支持断点续传。
---
## 第四部分:远程终端
### 16. 打开远程终端
选中主机后,点击**工具栏**的远程终端按钮
### 17. 界面说明
远程终端提供命令行交互界面:
```
┌─────────────────────────────────────────────┐
│ 192.168.1.100 - 远程终端 │
├─────────────────────────────────────────────┤
│ C:\Users\Admin> │
│ │
│ │
│ │
│ 命令输出区域 │
│ │
│ │
├─────────────────────────────────────────────┤
│ > 命令输入 │
└─────────────────────────────────────────────┘
```
### 18. 命令执行
#### 18.1 输入命令
在命令输入框中输入命令,按 Enter 执行。
**常用命令示例:**
| 命令 | 用途 |
|------|------|
| `dir` | 列出目录内容 |
| `cd /d D:` | 切换到 D 盘 |
| `ipconfig` | 查看网络配置 |
| `tasklist` | 查看进程列表 |
| `systeminfo` | 查看系统信息 |
| `net user` | 查看用户列表 |
#### 18.2 命令历史
使用上下箭头键可以浏览历史命令。
### 19. 交互式操作
#### 19.1 交互式程序
支持运行交互式程序,如:
- 文本编辑器
- 需要用户输入的程序
#### 19.2 中断命令
**Ctrl+C** 可以中断正在执行的命令。
#### 19.3 长时间运行命令
长时间运行的命令会持续显示输出,直到完成或被中断。
---
## 第五部分:进程管理
### 20. 打开进程管理器
选中主机后,点击**工具栏**的进程管理按钮
### 21. 界面说明
进程列表显示以下信息:
| 列名 | 说明 |
|------|------|
| 进程名称 | 可执行文件名 |
| PID | 进程 ID |
| 父进程 | 父进程 ID |
| 内存使用 | 占用内存大小 |
| 路径 | 可执行文件路径 |
### 22. 进程操作
| 操作 | 说明 |
|------|------|
| 刷新列表 | 点击刷新按钮或按 F5 |
| 结束进程 | 选中进程后点击"结束进程" |
| 结束进程树 | 结束进程及其所有子进程 |
| 搜索进程 | 在搜索框输入关键词筛选 |
**警告**:结束系统关键进程可能导致远程电脑不稳定。
---
## 第六部分:系统信息
### 23. 查看系统信息
在主机列表中可以看到基本系统信息。详细信息可通过远程终端执行 `systeminfo` 命令获取。
**可查看的信息:**
| 类别 | 信息 |
|------|------|
| 操作系统 | Windows 版本、内部版本号 |
| 硬件信息 | CPU、内存、磁盘 |
| 网络信息 | IP 地址、MAC 地址 |
| 用户信息 | 当前登录用户 |
---
## 第七部分:批量操作
### 24. 多选主机
| 方法 | 说明 |
|------|------|
| Ctrl+单击 | 添加/取消单个选择 |
| Shift+单击 | 选择范围内所有主机 |
| Ctrl+A | 全选所有主机 |
| 框选 | 拖动鼠标框选多个主机 |
### 25. 批量命令
选中多个主机后,可以执行批量操作:
1. 选中多个主机
2. 右键 → 选择操作
3. 操作将在所有选中主机上执行
**支持的批量操作:**
- 批量执行程序
- 批量重启/关机
- 批量发送消息
---
## 第八部分:分组管理
### 26. 创建分组
1. 右键单击主机
2. 选择 **修改分组**
3. 输入新的分组名称
4. 点击确定
### 27. 管理分组
| 操作 | 方法 |
|------|------|
| 移动主机到分组 | 右键 → 修改分组 → 选择/输入分组名 |
| 查看某分组 | 在筛选栏选择分组 |
| 删除分组 | 将所有主机移出该分组 |
### 28. 分组筛选
在主界面的筛选栏可以:
- 显示所有主机
- 仅显示某分组的主机
- 仅显示在线/离线主机
---
## 第九部分:设置与配置
### 29. 打开设置页面
点击菜单 **菜单****设置**
### 30. 网络设置
| 设置项 | 说明 |
|--------|------|
| 公网地址 | 受管端连接的地址 |
| 监听端口 | 主控监听的端口(默认 6543 |
### 31. 生成受管程序
点击菜单 **工具****主控生成**
#### 31.1 载荷类型说明
| 类型 | 说明 | 推荐场景 |
|------|------|---------|
| ghost.exe | 独立可执行文件 | **新手推荐**,便于测试 |
| TestRun - Windows 服务 | 作为服务安装,开机自启 | **生产环境推荐** |
| TestRun - 内存DLL | DLL 在内存中运行 | 进阶,隐蔽性需求 |
| ghost.exe - Windows 服务 | 独立程序,作为服务安装 | 备选 |
| ghost - Linux x64 | Linux 版本 | Linux 系统 |
#### 31.2 高级选项
| 选项 | 说明 |
|------|------|
| 协议 | TCP/UDP/HTTP/KCP |
| 加密 | Shine/HELL 加密方式 |
| 加壳 | UPX/ShellCode 等保护 |
---
## 第十部分:常见问题
### 32. 连接问题
| 现象 | 可能原因 | 解决方案 |
|------|---------|---------|
| 受管端无法上线 | 防火墙阻止 | 检查并开放端口 |
| 受管端无法上线 | 地址端口错误 | 重新生成受管程序 |
| 连接超时 | 网络不通 | 检查网络连通性 |
| 频繁断开 | 网络不稳定 | 检查网络质量 |
| FRP 连接失败 | FRP 配置错误 | 检查授权中的 FRP 配置 |
### 33. 远程桌面问题
| 现象 | 可能原因 | 解决方案 |
|------|---------|---------|
| 画面卡顿 | 带宽不足 | 降低画质等级 |
| 画面黑屏 | 驱动问题 | 重启受管端 |
| 鼠标延迟高 | 网络延迟大 | 检查网络,使用低画质 |
| 颜色异常 | 色彩模式问题 | 切换画质等级 |
| 无法控制 | 权限不足 | 确认受管端有足够权限 |
### 34. 文件传输问题
| 现象 | 可能原因 | 解决方案 |
|------|---------|---------|
| 传输失败 | 磁盘空间不足 | 清理磁盘空间 |
| 速度慢 | 带宽限制 | 检查网络带宽 |
| 中断后无法恢复 | 文件已更改 | 删除部分传输文件,重新传输 |
| 拒绝访问 | 权限不足 | 检查文件/目录权限 |
### 35. 远程终端问题
| 现象 | 可能原因 | 解决方案 |
|------|---------|---------|
| 命令无响应 | 命令执行中 | 等待或按 Ctrl+C 中断 |
| 乱码 | 编码问题 | 使用英文命令测试 |
| 无法执行 | 权限不足 | 以管理员身份运行受管端 |
---
## 附录
### A. 快捷键列表
| 快捷键 | 功能 |
|--------|------|
| F5 | 刷新主机列表 |
| Ctrl+F | 搜索主机 |
| Delete | 删除选中主机 |
| Enter | 打开远程桌面 |
| Ctrl+A | 全选主机 |
### B. 状态栏说明
| 位置 | 内容 |
|------|------|
| 左侧 | 当前分组主机数 / 总主机数 |
| 中间 | FRP 状态:优先显示上级配置的 FRPC 地址,其次显示本机 FRPS 端口 |
| 右侧 | 运行信息和授权信息(有效期、并发数) |
### C. 主机列表说明
主机列表中显示的是当前在线的受管端。受管端出现在列表中即表示在线连接正常,断开连接后会从列表中移除。
---
## 技术支持
| 渠道 | 联系方式 |
|------|---------|
| QQ | 962914132 |
| Telegram | [@doge_grandfather](https://t.me/doge_grandfather) |
---
## 相关文档
- [快速部署指南](QuickStart.md) - 首次部署
- [多级网络搭建指南](NetworkSetup.md) - 搭建多级网络
- [代理商运营手册](AgentManual.md) - 授权管理
- [定制化开发指南](CustomizationGuide.md) - 二次开发

624
docs/WebHTTPS.md Normal file
View File

@@ -0,0 +1,624 @@
# Web 远程桌面 HTTPS 配置指南(新手完整版)
本指南帮助你从零配置 HTTPS实现手机/平板通过浏览器访问远程桌面。
## 方案架构图
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ 互联网 │
│ │
│ ┌──────────┐ ┌─────────────────────────────────────────────┐ │
│ │ 手机 │ │ 公网服务器 (Linux/Windows) │ │
│ │ 平板 │ HTTPS │ ┌─────────────┐ ┌─────────────────┐ │ │
│ │ 电脑 │ ──────── │ │ Nginx │ ──── │ FRP Server │ │ │
│ │ (浏览器) │ :8080 │ │ (SSL证书) │ :9000│ (frps) │ │ │
│ └──────────┘ │ └─────────────┘ └────────┬────────┘ │ │
│ │ 域名: your.domain.com │ │ │
│ └────────────────────────────────┼────────────┘ │
│ │ │
│ TCP 隧道 │
│ │ │
└───────────────────────────────────────────────────────────┼────────────────┘
┌───────────────────────────────────────────────────────────┼────────────────┐
│ 本地网络 │ │
│ │ │
│ ┌─────────────────────────────────────┐ │ │
│ │ Windows 电脑 (主控) │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ │ ┌────────────▼───────────┐ │
│ │ │ Yama │───│ FRP Client │◄─┼───│ FRP 隧道 │ │
│ │ │ Web服务 │ │ (frpc) │ │ └────────────────────────┘ │
│ │ │ :9000 │ └─────────────┘ │ │
│ │ └──────┬──────┘ │ │
│ └─────────┼───────────────────────────┘ │
│ │ │
│ │ 远程控制 │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ 被控设备 (支持 Windows / Linux) │ │
│ │ │ │
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
│ │ │ Windows A │ │ Windows B │ │ Linux A │ │ Linux B │ ... │ │
│ │ └───────────┘ └───────────┘ └───────────┘ └───────────┘ │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└───────────────────────────────────────────────────────────────────────────┘
```
**数据流向**:手机浏览器 → HTTPS → Nginx → FRP Server → FRP Client → Yama → 被控设备
---
## Web 远程桌面的优势
### 随时随地,即开即用
- **无需安装客户端** - 打开浏览器就能用,手机、平板、任意电脑都行
- **跨平台支持** - iOS、Android、Windows、Mac、Linux 通吃
- **移动办公** - 出差、外出时用手机就能处理紧急事务
### 多人协作,团队共享
- **多人同时在线** - 传统 Windows 客户端一人使用时他人无法登录Web 端支持多人同时访问
- **权限分离** - 不同成员可同时监控不同设备,互不干扰
- **便于展示** - 分享链接即可让同事/客户查看,无需安装任何软件
### 降低成本,提升效率
- **零部署成本** - 无需在每台电脑上安装客户端软件
- **快速响应** - 收到告警通知,掏出手机就能处理
- **安全加密** - HTTPS 传输,数据安全有保障
---
## 前置条件
在开始配置前,请确认你已具备以下条件:
| 条件 | 说明 | 必需 |
|------|------|------|
| **Yama 程序授权** | 需要有效的软件授权才能使用 Web 功能 | ✅ |
| **Windows 电脑** | 用于运行 Yama 主控程序(见下方说明) | ✅ |
| **公网服务器** | 用于运行 FRP + Nginx需有固定公网 IP | ✅ |
| **域名** | 用于申请 HTTPS 证书,可购买或免费获取 | ✅ |
> 💡 **关于 Windows 电脑**Yama 主控程序需要在 Windows 上运行。如果你的公网服务器是 Windows 系统,可以直接在上面运行 Yama如果公网服务器是 Linux则需要另外准备一台 Windows 电脑。
### 没有这些条件?
如果你有 Web 远程桌面的需求,但缺少上述条件,欢迎联系作者:
- **咨询方案** - 我们可以提供服务器、域名等一站式解决方案
- **购买授权** - 获取 Yama 程序授权,解锁全部功能
- **技术支持** - 协助完成配置,快速上手使用
📬 **Telegram**[@doge_grandfather](https://t.me/doge_grandfather)
---
## 目录
1. [为什么需要 HTTPS](#1-为什么需要-https)
2. [技术准备](#2-技术准备)
3. [获取域名](#3-获取域名)
4. [配置域名解析](#4-配置域名解析)
5. [申请 SSL 证书](#5-申请-ssl-证书)
6. [安装配置 Nginx](#6-安装配置-nginx)
7. [配置 FRP](#7-配置-frp)
8. [启动服务](#8-启动服务)
9. [手机访问测试](#9-手机访问测试)
10. [常见问题](#10-常见问题)
---
## 1. 为什么需要 HTTPS
浏览器出于安全考虑H264 视频解码功能只能在**安全上下文**中使用:
| 访问方式 | H264 远程桌面 |
|----------|---------------|
| `https://任意地址` | ✅ 支持 |
| `http://localhost``http://127.0.0.1` | ✅ 支持 |
| `http://其他地址`(包括内网 IP、公网 IP、域名 | ❌ 不支持 |
**简单说:**
- **没有 HTTPS** → 只能在本机浏览器访问localhost
- **配置 HTTPS** → 手机、平板、其他电脑都能访问
如果用 HTTP 从非本机访问,会提示:
```
Browser does not support H264: VideoDecoder not available
```
本指南帮你配置 HTTPS实现随时随地访问。
---
## 2. 技术准备
确认你已完成以下准备:
| 项目 | 说明 |
|------|------|
| FRP 基础配置 | 已配置好 FRP 内网穿透frps 在服务器运行frpc 在本机运行) |
| SSH 工具 | 如 PuTTY、Xshell、Windows Terminal用于连接服务器执行命令 |
| Yama Web 服务 | 已在 Yama 中启用 Web 服务(菜单 → 设置 → Web 服务) |
> 💡 如果还没配置 FRP请先参考 FRP 官方文档完成基础配置。
**最终架构:**
```
手机浏览器 ──HTTPS──> 你的域名:端口 ──> Nginx ──> FRP ──> 你的电脑
```
---
## 3. 获取域名
### 方案一:购买域名(推荐)
便宜的域名一年只要几块钱:
| 注册商 | 网址 | 说明 |
|--------|------|------|
| 阿里云 | https://wanwang.aliyun.com | 国内,需实名 |
| 腾讯云 | https://dnspod.cloud.tencent.com | 国内,需实名 |
| Cloudflare | https://www.cloudflare.com | 国外,无需实名 |
| Namesilo | https://www.namesilo.com | 国外,便宜 |
> 💡 **提示**`.icu`、`.top`、`.xyz` 等后缀很便宜,首年通常 ¥5-10。
### 方案二:免费域名
| 服务 | 网址 | 说明 |
|------|------|------|
| DuckDNS | https://www.duckdns.org | 免费子域名,如 `xxx.duckdns.org` |
| No-IP | https://www.noip.com | 免费子域名 |
| Freenom | https://www.freenom.com | 免费顶级域名(不稳定) |
---
## 4. 配置域名解析
将域名指向你的**公网服务器 IP**。
### 4.1 登录域名管理面板
去你购买域名的网站找到「DNS 解析」或「域名解析」。
### 4.2 添加 A 记录
| 字段 | 填写 |
|------|------|
| 记录类型 | A |
| 主机记录 | `@`(代表根域名)或 `www` |
| 记录值 | 你的服务器公网 IP`123.45.67.89` |
| TTL | 600或默认 |
### 4.3 验证解析生效
等待 1-5 分钟,然后测试:
```bash
ping your.domain.com
```
如果返回你的服务器 IP说明解析成功。
---
## 5. 申请 SSL 证书
使用 Let's Encrypt 免费证书。
### 5.1 SSH 连接到服务器
使用 SSH 工具连接到你的公网服务器。
### 5.2 安装 Certbot
```bash
# Ubuntu / Debian
apt update && apt install certbot -y
# CentOS
yum install certbot -y
```
### 5.3 申请证书
使用 DNS 验证方式,**不需要占用 80/443 端口**
```bash
certbot certonly --manual --preferred-challenges dns -d your.domain.com
```
> ⚠️ 将 `your.domain.com` 替换为你的实际域名
### 5.4 按提示操作
1. **输入邮箱**:用于接收证书到期提醒
2. **同意条款**:输入 `Y`
3. **分享邮箱**:输入 `N`(可选)
### 5.5 添加 DNS TXT 记录
Certbot 会显示类似信息:
```
Please deploy a DNS TXT record under the name:
_acme-challenge.your.domain.com
with the following value:
aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890
```
**去域名管理面板添加 TXT 记录:**
| 字段 | 填写 |
|------|------|
| 记录类型 | TXT |
| 主机记录 | `_acme-challenge` |
| 记录值 | Certbot 显示的那串字符 |
| TTL | 600 |
### 5.6 验证 TXT 记录生效
```bash
nslookup -type=TXT _acme-challenge.your.domain.com
```
看到记录值后,**回到 Certbot 按回车**继续。
### 5.7 证书申请成功
成功后显示:
```
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/your.domain.com/fullchain.pem
Key is saved at: /etc/letsencrypt/live/your.domain.com/privkey.pem
```
**记住这两个路径,后面要用。**
### 5.8 设置自动续期(可选)
证书有效期 90 天,建议设置自动续期:
```bash
# 方法一:直接添加定时任务(推荐)
(crontab -l 2>/dev/null; echo "0 0 1 * * certbot renew --quiet && systemctl reload nginx") | crontab -
# 方法二:手动编辑
crontab -e
# 添加这行0 0 1 * * certbot renew --quiet && systemctl reload nginx
# 按 Ctrl+O 保存Ctrl+X 退出
```
> 💡 这会在每月 1 号自动检查并续期证书。
---
## 6. 安装配置 Nginx
### 6.1 安装 Nginx
```bash
# Ubuntu / Debian
apt update && apt install nginx -y
# CentOS
yum install nginx -y && systemctl enable nginx
```
### 6.2 删除默认配置(避免端口冲突)
```bash
# Ubuntu / Debian
rm -f /etc/nginx/sites-enabled/default
# CentOS - 跳过此步(我们用 8080 端口,不冲突)
```
### 6.3 创建配置文件
> ⚠️ **重要**:把命令中的 `your.domain.com` 替换为你的实际域名(共 4 处)
**Ubuntu / Debian**
```bash
cat > /etc/nginx/sites-available/yama << 'EOF'
server {
listen 8080 ssl;
server_name your.domain.com;
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
# WebSocket 支持(重要)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 长连接超时
proxy_read_timeout 86400;
}
}
EOF
```
**CentOS**
```bash
cat > /etc/nginx/conf.d/yama.conf << 'EOF'
server {
listen 8080 ssl;
server_name your.domain.com;
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:9000;
proxy_http_version 1.1;
# WebSocket 支持(重要)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 长连接超时
proxy_read_timeout 86400;
}
}
EOF
```
> 💡 **端口说明**:这里用 `8080`,你可以改成其他未被占用的端口(如 9443
### 6.4 启用配置(仅 Ubuntu/Debian 需要)
```bash
ln -s /etc/nginx/sites-available/yama /etc/nginx/sites-enabled/
```
> CentOS 已在上一步直接创建到 `conf.d` 目录,无需此步骤。
### 6.5 测试并启动
```bash
nginx -t && systemctl restart nginx
```
看到以下输出说明配置正确:
```
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
```
如果报错,检查域名是否替换正确、证书路径是否存在。
---
## 7. 配置 FRP
### 7.1 FRP 服务端配置 (frps.toml)
在公网服务器上:
```toml
bindPort = 7000
```
### 7.2 FRP 客户端配置
#### 方式一:通过 Yama 程序配置(推荐)
1. 打开 Yama 主控程序
2. 菜单 → 设置 → FRP 设置
3. 启用 FRP
4. 设置 Web 端口为 `9000`
5. 保存
无需手动编辑配置文件。
#### 方式二:手动编辑 frpc.toml
在你的本机Windows
```toml
serverAddr = "你的服务器IP或域名"
serverPort = 7000
[[proxies]]
name = "yama-web"
type = "tcp"
localPort = 9000
remotePort = 9000
```
> 💡 **说明**FRP 把服务器的 9000 端口转发到你本机的 9000 端口。
---
## 8. 启动服务
### 8.1 开放防火墙端口(重要!)
很多新手卡在这一步。**服务器防火墙必须开放以下端口**
```bash
# 查看防火墙状态
ufw status # Ubuntu
firewall-cmd --state # CentOS
# Ubuntu - 开放端口
ufw allow 8080 # Nginx HTTPS 端口
ufw allow 7000 # FRP 端口
ufw allow 9000 # FRP Web 转发端口
# CentOS - 开放端口
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=7000/tcp
firewall-cmd --permanent --add-port=9000/tcp
firewall-cmd --reload
```
> ⚠️ **云服务器还需要在控制台安全组中开放这些端口!**(阿里云、腾讯云等)
### 8.2 启动顺序
按以下顺序启动:
1. **服务器上**:启动 FRP Server
```bash
./frps -c frps.toml
```
2. **服务器上**:确认 Nginx 在运行
```bash
systemctl status nginx
```
3. **本机上**:启动 Yama 主控程序
> 💡 **提示**:如果在 Yama 中启用了 FRP 功能,程序启动时会自动连接 FRP Server无需手动启动 frpc。
### 8.3 流量路径
```
手机浏览器
│ HTTPS (端口 8080)
Nginx (SSL 终结)
│ HTTP (端口 9000)
FRP Server
│ TCP 隧道
FRP Client
│ 本地 (端口 9000)
Yama WebService
```
---
## 9. 手机访问测试
### 9.1 打开浏览器
在手机浏览器输入:
```
https://your.domain.com:8080
```
> ⚠️ 注意是 `https://`,不是 `http://`
### 9.2 登录
输入用户名密码登录。
### 9.3 连接远程桌面
选择设备,点击连接,享受远程桌面。
---
## 10. 常见问题
### Q: 浏览器提示"不安全"或证书错误
A: 检查域名是否和证书匹配,证书是否过期:
```bash
certbot certificates
```
### Q: 无法访问,连接超时
A: 检查各服务是否运行:
```bash
systemctl status nginx
netstat -tlnp | grep 8080
netstat -tlnp | grep 9000
```
### Q: WebSocket 连接失败
A: 确认 Nginx 配置中有 `Upgrade` 和 `Connection` 头配置。
### Q: 仍然提示 VideoDecoder not available
A:
1. 确认使用 `https://` 访问
2. 清除浏览器缓存后重试
3. 换个浏览器试试(推荐 Chrome/Edge
### Q: 证书过期了
A: 手动续期:
```bash
certbot renew
systemctl reload nginx
```
### Q: FRP 连接不上
A: 参考 [8.1 开放防火墙端口](#81-开放防火墙端口重要) 检查防火墙配置。
### Q: 云服务器(阿里云/腾讯云)无法访问
A: 除了服务器防火墙,还需要在**云控制台的安全组**中开放端口:
1. 登录云服务器控制台
2. 找到「安全组」设置
3. 添加入站规则,开放 8080、7000、9000 端口
### Q: 如何测试各环节是否正常
A: 逐步排查:
```bash
# 1. 测试 Nginx 是否监听
curl -k https://127.0.0.1:8080
# 2. 测试 FRP 是否转发
curl http://127.0.0.1:9000
# 3. 测试域名是否解析正确
ping your.domain.com
# 4. 测试外部访问(在手机上)
# 打开 https://your.domain.com:8080
```
---
## 总结
| 步骤 | 操作位置 | 说明 |
|------|----------|------|
| 获取域名 | 域名注册商 | 购买或免费获取 |
| 域名解析 | 域名管理面板 | A 记录指向服务器 IP |
| 申请证书 | 服务器 | Let's Encrypt 免费证书 |
| 配置 Nginx | 服务器 | SSL 终结 + 反向代理 |
| 开放防火墙 | 服务器 + 云控制台 | 开放 8080、7000、9000 端口 |
| 配置 FRP | 服务器 + 本机 | 内网穿透 |
| 访问测试 | 手机 | `https://域名:端口` |
配置完成后,你就可以随时随地用手机访问远程桌面了!
---
**遇到问题?** 查看 [常见问题](#10-常见问题) 或逐步排查各环节。